成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

深入理解Android插件化技術原理

移動開發 Android
支持插件化的app可以在運行時加載和運行插件,這樣便可以將app中一些不常用的功能模塊做成插件,一方面減小了安裝包的大小,另一方面可以實現app功能的動態擴展;

[[431328]]

前言

插件化技術最初源于免安裝運行apk的想法,這個免安裝的apk可以理解為插件。

支持插件化的app可以在運行時加載和運行插件,這樣便可以將app中一些不常用的功能模塊做成插件,一方面減小了安裝包的大小,另一方面可以實現app功能的動態擴展;

今天我們就來講下插件化

一、插件化介紹

1、插件化介紹

在 Android 系統中,應用是以 Apk 的形式存在的,應用都需要安裝才能使用。但實際上 Android 系統安裝應用的方式相當簡單,其實就是把應用 Apk 拷貝到系統不同的目錄下、然后把 so 解壓出來而已;

常見的應用安裝目錄有:

  • /system/app:系統應用
  • /system/priv-app:系統應用
  • /data/app:用戶應用

Apk 的構成,一個常見的 Apk 會包含如下幾個部分:

  • classes.dex:Java 代碼字節碼
  • res:資源目錄
  • lib:so 目錄
  • assets:靜態資產目錄
  • AndroidManifest.xml:清單文件

其實 Android 系統在打開應用之后,也只是開辟進程,然后使用 ClassLoader 加載 classes.dex 至進程中,執行對應的組件而已;

那大家可能會想一個問題,既然 Android 本身也是使用類似反射的形式加載代碼執行,憑什么我們不能執行一個 Apk 中的代碼呢?

這其實就是插件化的目的,讓 Apk 中的代碼(主要是指 Android 組件)能夠免安裝運行,這樣能夠帶來很多收益,最顯而易見的優勢其實就是通過網絡熱更新、熱修復;

2、插件化技術難點

  • 反射并執行插件 Apk 中的代碼(ClassLoader Injection)
  • 讓系統能調用插件 Apk 中的組件(Runtime Container)
  • 正確識別插件 Apk 中的資源(Resource Injection)

3、雙親委托機制

ClassLoader調用loadClass方法加載類,代碼如下:

  1. protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {  
  2.        //首先從已經加載的類中查找 
  3.         Class<?> clazz = findLoadedClass(className);     
  4.     if (clazz == null) { 
  5.             ClassNotFoundException suppressed = null;      
  6.            try {    
  7.                 //如果沒有加載過,先調用父加載器的loadClass 
  8.                 clazz = parent.loadClass(className, false); 
  9.             } catch (ClassNotFoundException e) { 
  10.                 suppressed = e; 
  11.             }       
  12.         if (clazz == null) {         
  13.                 try {            
  14.                   //父加載器都沒有加載,則嘗試加載 
  15.                     clazz = findClass(className); 
  16.                 } catch (ClassNotFoundException e) { 
  17.                     e.addSuppressed(suppressed);        
  18.                      throw e; 
  19.                 } 
  20.             } 
  21.         }     
  22.             return clazz; 
  23.     } 

可以看出ClassLoader加載類時,先查看自身是否已經加載過該類,如果沒有加載過會首先讓父加載器去加載,如果父加載器無法加載該類時才會調用自身的findClass方法加載,該機制很大程度上避免了類的重復加載;

二、插件化詳解

1、ClassLoader Injection

簡單來說,插件化場景下,會存在同一進程中多個 ClassLoader 的場景:

  • 宿主 ClassLoader:宿主是安裝應用,運行即自動創建
  • 插件 ClassLoader:使用 new DexClassLoader 創建

我們稱這個過程叫做 ClassLoader 注入;

完成注入后,所有來自宿主的類使用宿主的 ClassLoader 進行加載,所有來自插件 Apk 的類使用插件 ClassLoader 進行加載;

而由于 ClassLoader 的雙親委派機制,實際上系統類會不受 ClassLoader 的類隔離機制所影響,這樣宿主 Apk 就可以在宿主進程中使用來自于插件的組件類了;

2、Runtime Container

ClassLoader 注入后,就可以在宿主進程中使用插件 Apk 中的類,但是我們都知道 Android 組件都是由系統調用啟動的,未安裝的 Apk 中的組件,是未注冊到 AMS 和 PMS 的,就好比你直接使用 startActivity 啟動一個插件 Apk 中的組件,系統會告訴你無法找到;

我們的解決方案很簡單,即運行時容器技術,簡單來說就是在宿主 Apk 中預埋一些空的 Android 組件,以 Activity 為例,我預置一個 ContainerActivity extends Activity 在宿主中,并且在 AndroidManifest.xml 中注冊它;

它要做的事情很簡單,就是幫助我們作為插件 Activity 的容器,它從 Intent 接受幾個參數,分別是插件的不同信息,如:

  • pluginName;
  • pluginApkPath;
  • pluginActivityName等,其實最重要的就是 pluginApkPath 和 pluginActivityName,當 ContainerActivity 啟動時,我們就加載插件的 ClassLoader、Resource,并反射 pluginActivityName 對應的 Activity 類;

當完成加載后,ContainerActivity 要做兩件事:

  • 轉發所有來自系統的生命周期回調至插件 Activity
  • 接受 Activity 方法的系統調用,并轉發回系統

我們可以通過復寫 ContainerActivity 的生命周期方法來完成第一步,而第二步我們需要定義一個 PluginActivity,然后在編寫插件 Apk 中的 Activity 組件時,不再讓其集成 android.app.Activity,而是集成自我們的 PluginActivity,后面再通過字節碼替換來自動化完成這部操作,后面再說為什么,我們先看偽代碼;

  1. public class ContainerActivity extends Activity { 
  2.     private PluginActivity pluginActivity; 
  3.     @Override 
  4.     protected void onCreate(Bundle savedInstanceState) { 
  5.         String pluginActivityName = getIntent().getString("pluginActivityName"""); 
  6.         pluginActivity = PluginLoader.loadActivity(pluginActivityName, this); 
  7.         if (pluginActivity == null) { 
  8.             super.onCreate(savedInstanceState); 
  9.             return
  10.         } 
  11.         pluginActivity.onCreate(); 
  12.     } 
  13.     @Override 
  14.     protected void onResume() { 
  15.         if (pluginActivity == null) { 
  16.             super.onResume(); 
  17.             return
  18.         } 
  19.         pluginActivity.onResume(); 
  20.     } 
  21.     @Override 
  22.     protected void onPause() { 
  23.         if (pluginActivity == null) { 
  24.             super.onPause(); 
  25.             return
  26.         } 
  27.         pluginActivity.onPause(); 
  28.     } 
  29.     // ... 
  30. public class PluginActivity { 
  31.     private ContainerActivity containerActivity; 
  32.     public PluginActivity(ContainerActivity containerActivity) { 
  33.         this.containerActivity = containerActivity; 
  34.     } 
  35.     @Override 
  36.     public <T extends View> T findViewById(int id) { 
  37.         return containerActivity.findViewById(id); 
  38.     } 
  39.     // ... 
  40. // 插件 `Apk` 中真正寫的組件 
  41. public class TestActivity extends PluginActivity { 
  42.     // ...... 

但大概原理就是這么簡單,啟動插件組件需要依賴容器,容器負責加載插件組件并且完成雙向轉發,轉發來自系統的生命周期回調至插件組件,同時轉發來自插件組件的系統調用至系統;

3、Resource Injection

最后要說的是資源注入,其實這一點相當重要,Android 應用的開發其實崇尚的是邏輯與資源分離的理念,所有資源(layout、values 等)都會被打包到 Apk 中,然后生成一個對應的 R 類,其中包含對所有資源的引用 id;

資源的注入并不容易,好在 Android 系統給我們留了一條后路,最重要的是這兩個接口:

  • PackageManager#getPackageArchiveInfo:根據 Apk 路徑解析一個未安裝的 Apk 的 PackageInfo;
  • PackageManager#getResourcesForApplication:根據 ApplicationInfo 創建一個 Resources 實例;

我們要做的就是在上面 ContainerActivity#onCreate 中加載插件 Apk 的時候,用這兩個方法創建出來一份插件資源實例。具體來說就是先用 PackageManager#getPackageArchiveInfo 拿到插件 Apk 的 PackageInfo,有了 PacakgeInfo 之后我們就可以自己組裝一份 ApplicationInfo,然后通過 PackageManager#getResourcesForApplication 來創建資源實例,大概代碼像這樣:

  1. PackageManager packageManager = getPackageManager(); 
  2. PackageInfo packageArchiveInfo = packageManager.getPackageArchiveInfo( 
  3.     pluginApkPath, 
  4.     PackageManager.GET_ACTIVITIES 
  5.     | PackageManager.GET_META_DATA 
  6.     | PackageManager.GET_SERVICES 
  7.     | PackageManager.GET_PROVIDERS 
  8.     | PackageManager.GET_SIGNATURES 
  9. ); 
  10. packageArchiveInfo.applicationInfo.sourceDir = pluginApkPath; 
  11. packageArchiveInfo.applicationInfo.publicSourceDir = pluginApkPath; 
  12. Resources injectResources = null
  13. try { 
  14.     injectResources = packageManager.getResourcesForApplication(packageArchiveInfo.applicationInfo); 
  15. } catch (PackageManager.NameNotFoundException e) { 
  16.     // ... 

拿到資源實例后,我們需要將宿主的資源和插件資源 Merge 一下,編寫一個新的 Resources 類,用這樣的方式完成自動代理:

  1. public class PluginResources extends Resources { 
  2.     private Resources hostResources; 
  3.     private Resources injectResources; 
  4.     public PluginResources(Resources hostResources, Resources injectResources) { 
  5.         super(injectResources.getAssets(), injectResources.getDisplayMetrics(), injectResources.getConfiguration()); 
  6.         this.hostResources = hostResources; 
  7.         this.injectResources = injectResources; 
  8.     } 
  9.     @Override 
  10.     public String getString(int id, Object... formatArgs) throws NotFoundException { 
  11.         try { 
  12.             return injectResources.getString(id, formatArgs); 
  13.                     } catch (NotFoundException e) { 
  14.  
  15.             return hostResources.getString(id, formatArgs); 
  16.         } 
  17.     } 
  18.     // ... 

然后我們在 ContainerActivity 完成插件組件加載后,創建一份 Merge 資源,再復寫 ContainerActivity#getResources,將獲取到的資源替換掉:

  1. public class ContainerActivity extends Activity { 
  2.     private Resources pluginResources; 
  3.     @Override 
  4.     protected void onCreate(Bundle savedInstanceState) { 
  5.         // ... 
  6.         pluginResources = new PluginResources(super.getResources(), PluginLoader.getResources(pluginApkPath)); 
  7.         // ... 
  8.     } 
  9.     @Override 
  10.     public Resources getResources() { 
  11.         if (pluginActivity == null) { 
  12.             return super.getResources(); 
  13.         } 
  14.         return pluginResources; 
  15.     } 

這樣就完成了資源的注入

4、解決資源沖突

合并式的資源處理方式,會引入資源沖突,原因在于不同插件中的資源id可能相同,所以解決方法就是使得不同的插件資源擁有不同的資源id;

資源id是由8位16進制數表示,表示為0xPPTTNNNN。PP段用來區分包空間,默認只區分了應用資源和系統資源,TT段為資源類型,NNNN段在同一個APK中從0000遞增;

總結

市面上的插件化框架實際很多,如 Tecent 的 Shadow、Didi 的 VirtualApk、360 的 RePlugin。他們各有各的長處,不過大體上差不多;

他們大體原理其實都差不多,運行時會有一個宿主 Apk 在進程中跑,宿舍 Apk 是真正被安裝的應用,宿主 Apk 可以加載插件 Apk 中的組件和代碼運行,插件 Apk 可以任意熱更新;

 

責任編輯:武曉燕 來源: Android開發編程
相關推薦

2024-03-12 00:00:00

Sora技術數據

2024-04-15 00:00:00

技術Attention架構

2012-11-22 13:02:24

jQuery插件Web

2023-06-29 08:41:02

2022-11-04 09:43:05

Java線程

2021-03-10 10:55:51

SpringJava代碼

2022-09-05 08:39:04

kubernetesk8s

2024-11-01 08:57:07

2020-08-10 18:03:54

Cache存儲器CPU

2011-04-28 11:01:40

Android消息處理LooperHandler

2021-09-10 07:31:54

AndroidAppStartup原理

2020-03-26 16:40:07

MySQL索引數據庫

2023-09-19 22:47:39

Java內存

2022-09-26 08:01:31

線程LIFO操作方式

2022-01-14 12:28:18

架構OpenFeign遠程

2023-11-13 16:33:46

2020-11-04 15:35:13

Golang內存程序員

2020-03-17 08:36:22

數據庫存儲Mysql

2019-07-01 13:34:22

vue系統數據

2023-10-13 13:30:00

MySQL鎖機制
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲一区二区三区免费在线观看 | 欧美一区二区三区电影 | av日韩精品| 日日操av | 中文字幕在线观看 | 国产精品美女久久久久久免费 | 色综合一区二区 | 欧美日韩国产一区二区三区 | 天天综合网天天综合色 | 日韩免费在线 | 日韩久久综合网 | 日韩一区二区免费视频 | 亚洲嫩草| 欧美精品在线一区二区三区 | 国产精品日本一区二区在线播放 | 欧美黑人又粗大 | 亚洲成人自拍网 | 三级在线视频 | 91在线影院 | 精品欧美二区 | 国产一区二区在线免费观看 | 日韩国产一区二区三区 | 亚洲成人午夜电影 | 日韩三级一区 | 在线观看成年人视频 | 三级在线免费 | av入口| 夜夜草视频 | 国产1区2区 | 久久久久电影 | 日本天天操| 伊人狼人影院 | 成年人视频免费在线观看 | 中文字幕二区 | 国产精品久久一区二区三区 | 女人毛片a毛片久久人人 | 日韩视频在线免费观看 | 亚洲不卡在线观看 | 欧美成年黄网站色视频 | 欧美一区二区综合 | 欧美精品片 |