移動架構 (八) 人人都能看得懂的動態化加載插件技術模型實現

基本概念

插件化其實也就是 模塊化->組件化 演變而來, 屬於動態加載技術,主要用於解決應用愈來愈龐大以及功能模塊的解耦,小項目中通常用的很少。html

原理: 插件化的原理其實就是在 APP 殼運行過程當中,動態加載一些程序中本來不存在的可執行文件並運行這些文件中的代碼邏輯。可執行文件總的來講分爲兩個,其一是動態連接庫 so,其二是 dex 相關文件包括 jar/apk 文件。java

發展歷史

很早之前插件化這項技術已經有公司在研究了,淘寶,支付寶作的是比較早,可是淘寶這項技術一直都是保密的,直到 2015 年左右市面上纔出現了一些關於插件化的框架,Android 插件化分爲不少技術流派,實現的方式都不太同樣。下面我就簡單以時間線來舉例幾個比較有表明性的插件框架:git

時間 框架名稱 做者 框架簡介
2014年末 dynamic-load-apk 主席任玉剛 動態加載技術 + 代理實現
2015年 8 月 DroidPlugin 360 手機助手 能夠直接運行第三方的獨立 APK 文件,徹底不須要對 APK 進行修改或安裝。一種新的插件機制,一種免安裝的運行機制,是一個沙箱(可是不徹底的沙箱。就是對於使用者來講,並不知道他會把 apk 怎麼樣), 是模塊化的基礎。
2015年末 Small wequick Small 是一種實現輕巧的跨平臺插件化框架,基於「輕量、透明、極小化、跨平臺」的理念
2017年 6 月 VirtualAPK 滴滴 VirtualAPK 對插件沒有額外的約束,原生的 apk 便可做爲插件。插件工程編譯生成 apk 後,便可經過宿主 App 加載,每一個插件 apk 被加載後,都會在宿主中建立一個單獨的 LoadedPlugin 對象。經過這些 LoadedPlugin 對象,VirtualAPK 就能夠管理插件並賦予插件新的意義,使其能夠像手機中安裝過的 App 同樣運行。
2017年 7 月 RePlgin 360手機衛士 RePlugin 是一套完整的、穩定的、適合全面使用的,佔坑類插件化方案,由 360 手機衛士的RePlugin Team 研發,也是業內首個提出」全面插件化「(全面特性、全面兼容、全面使用)的方案。
2019 Shadow 騰訊 Shadow 是一個騰訊自主研發的 Android 插件框架,通過線上億級用戶量檢驗。 Shadow 不只開源分享了插件技術的關鍵代碼,還完整的分享了上線部署所須要的全部設計(零反射)

插件化必備知識

  1. Binder
  2. APP 打包流程
  3. APP 安裝流程
  4. APP 啓動流程
  5. 資源加載機制
  6. 反射,ClassLoader
  7. ...

實現簡易版本插件化框架

今天咱們這裏就以動態加載技術,ClassLoader + 反射 + 代理模式 等基本技術來實現動態加載 APK 中的(Activity, Broadcast, Service ,資源)項目地址github

先來看一個咱們最終實現的效果數組

cFKq7.gif

加載插件 APK

在加載 APK 以前咱們先來了解下 ClassLoader 家族,繼承關係圖框架

cFg8c.png

DexClassLoader 加載流程ide

DexClassLoader.png

從上面 2 張圖中,咱們得知動態加載 APK 須要用到 DexClassLoader ,既然知道了用 DexClassLoader 來加載 APK , 那麼native 中將 apk -> dex 解析出來,class 又怎麼加載勒? 經過 DexClassLoader 流程圖得知能夠直接調用 loadClass(String classPath) 來加載,下面咱們就正式進行今天的主題了。模塊化

代碼實現加載 APK函數

/** * 加載插件 APK */
    public boolean loadPlugin(Context context, String filePath) {
        if (context == null || filePath == null || filePath.isEmpty())
            throw new NullPointerException("context or filePath is null ?");
        this.mContext = context.getApplicationContext();
        this.apkFilePath = filePath;
        //拿到 包管理
        packageManager = mContext.getPackageManager();

        if (getPluginPackageInfo(apkFilePath) == null) {
            return false;
        }
        //從包裏獲取 Activity
        pluginPackageInfo = getPluginPackageInfo(apkFilePath);

        //存放 DEX 路徑
        mDexPath = new File(Constants.IPluginPath.PlugDexPath);
        if (mDexPath.exists())
            mDexPath.delete();
        else
            mDexPath.mkdirs();

        //經過 DexClassLoader 加載 apk 並經過 native 層解析 apk 輸出 dex
        //第二個參數能夠爲 null
        if (getPluginClassLoader(apkFilePath, mDexPath.getAbsolutePath()) == null || getPluginResources(filePath) == null)
            return false;
        this.mDexClassLoader = getPluginClassLoader(apkFilePath, mDexPath.getAbsolutePath());
        this.mResources = getPluginResources(filePath);
        return true;

    }
複製代碼
/**
 * @return 獲得對應插件 APK 的 Resource 對象
 */
public Resources getPluginResources() {
    return getPluginResources(apkFilePath);
}

/**
 * 獲得對應插件 APK 中的 加載器
 *
 * @param apkFile
 * @param dexPath
 * @return
 */
public DexClassLoader getPluginClassLoader(String apkFile, String dexPath) {
    return new DexClassLoader(apkFile, dexPath, null, mContext.getClassLoader());
}


/**
 * 獲得對應插件 APK 中的 加載器
 *
 * @return
 */
public DexClassLoader getPluginClassLoader() {
    return getPluginClassLoader(apkFilePath, mDexPath.getAbsolutePath());
}


/**
 * 獲得插件 APK 中 包信息
 */
public PackageInfo getPluginPackageInfo(String apkFilePath) {
    if (packageManager != null)
        return packageManager.getPackageArchiveInfo(apkFilePath, PackageManager.GET_ACTIVITIES);
return null;
}

/**
 * 獲得插件 APK 中 包信息
 */
public PackageInfo getPluginPackageInfo() {
    return getPluginPackageInfo(apkFilePath);
}
複製代碼

加載插件中 Activity

實現流程組件化

--APK.png

代碼實現流程

  1. 代理類 ProxyActivity 實現

    public class ProxyActivity extends AppCompatActivity {
    
        /** * 須要加載插件的全類名 */
        protected String activityClassName;
    
        private String TAG = this.getClass().getSimpleName();
        private IActivity iActivity;
        private ProxyBroadcast receiver;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            activityClassName = getLoadClassName();
    
            //拿到加載插件的的全類名 經過反射實例化
            try {
                Class<?> pluginClassName = getClassLoader().loadClass(activityClassName);
                //拿到構造函數
                Constructor<?> constructor = pluginClassName.getConstructor(new Class[]{});
                //實例化 拿到插件 UI
                Object pluginObj = constructor.newInstance(new Object[]{});
                if (pluginObj != null) {
                    iActivity = (IActivity) pluginObj;
                    iActivity.onActivityCreated(this, savedInstanceState);
                }
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
        }
    }
    複製代碼
  2. 重寫代理類中的 startActivity

    /** * 這裏的 startActivity 是插件促使調用的 */
        @Override
        public void startActivity(Intent intent) {
          	//須要開啓插件 Activity 的全類名
            String className = getLoadClassName(intent);
            Intent proxyIntent = new Intent(this, ProxyActivity.class);
            proxyIntent.putExtra(Constants.ACTIVITY_CLASS_NAME, className);
            super.startActivity(proxyIntent);
        }
    複製代碼
  3. 插件 Activity 實現 IActivity 的生命週期而且重寫一些重要函數,都交於插件中處理

    public class BaseActivityImp extends AppCompatActivity implements IActivity {
    
        private final String TAG = getClass().getSimpleName();
    
        /** * 代理 Activity */
        protected Activity that;
    
        @Override
        public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle bundle) {
            this.that = activity;
    
            Log.i(TAG, " onActivityCreated");
            onCreate(bundle);
        }
    
        /** * 經過 View 方式加載 * * @param view */
        @Override
        public void setContentView(View view) {
            Log.i(TAG, " setContentView --> view");
            if (that != null) {
                that.setContentView(view);
            } else {
                super.setContentView(view);
            }
        }
    
        /** * 經過 layoutID 加載 * * @param layoutResID */
        @Override
        public void setContentView(int layoutResID) {
            Log.i(TAG, " setContentView --> layoutResID");
            if (that != null) {
                that.setContentView(layoutResID);
            } else {
                super.setContentView(layoutResID);
            }
        }
    
        /** * 經過代理 去找佈局 ID * * @param id * @param <T> * @return */
        @Override
        public <T extends View> T findViewById(int id) {
            if (that != null)
                return that.findViewById(id);
            return super.findViewById(id);
        }
    
        /** * 經過 代理去開啓 Activity * * @param intent */
        @Override
        public void startActivity(Intent intent) {
            if (that != null) {
                Intent tempIntent = new Intent();
                tempIntent.putExtra(Constants.ACTIVITY_CLASS_NAME, intent.getComponent().getClassName());
                that.startActivity(tempIntent);
            } else
                super.startActivity(intent);
        }
    
    
    
        @Override
        public String getPackageName() {
            return that.getPackageName();
        }
    
        @Override
        public void onActivityStarted(@NonNull Activity activity) {
            Log.i(TAG, " onActivityStarted");
            onStart();
    
    
        }
    
        @Override
        public void onActivityResumed(@NonNull Activity activity) {
            Log.i(TAG, " onActivityResumed");
            onResume();
        }
    
        @Override
        public void onActivityPaused(@NonNull Activity activity) {
            Log.i(TAG, " onActivityPaused");
            onPause();
        }
    
        @Override
        public void onActivityStopped(@NonNull Activity activity) {
            Log.i(TAG, " onActivityStopped");
            onStop();
        }
    
        @Override
        public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) {
            onSaveInstanceState(bundle);
            Log.i(TAG, " onActivitySaveInstanceState");
        }
    
        @Override
        public void onActivityDestroyed(@NonNull Activity activity) {
            Log.i(TAG, " onActivityDestroyed");
            onDestroy();
    
        }
    
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
    
        }
    
    
        @Override
        protected void onStart() {
    
        }
    
        @Override
        protected void onResume() {
    
        }
    
        @Override
        protected void onStop() {
    
        }
    
        @Override
        protected void onPause() {
    
        }
    
        @Override
        protected void onSaveInstanceState(Bundle outState) {
    
        }
    
        @Override
        protected void onDestroy() {
    
        }
    
        @Override
        public void onBackPressed() {
    
        }
    }
    複製代碼

加載插件中 Broadcast

流程圖

-.png


代碼實現

  1. 代理 ProxyActivity 中重寫註冊廣播

    @Override
        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
            IntentFilter proxyIntentFilter = new IntentFilter();
            for (int i = 0; i < filter.countActions(); i++) {
                //內部是一個數組
                proxyIntentFilter.addAction(filter.getAction(i));
            }
            //交給代理廣播去註冊
            this.receiver = new ProxyBroadcast(receiver.getClass().getName(), this);
            return super.registerReceiver(this.receiver, filter);
        }
    複製代碼
  2. 加載插件中須要註冊的廣播全路徑

    public ProxyBroadcast(String broadcastClassName, Context context) {
            this.broadcastClassName = broadcastClassName;
            this.iBroadcast = iBroadcast;
    
            //經過加載插件的 DexClassLoader loadClass
            try {
                Class<?> pluginBroadcastClassName = PluginManager.getInstance().getPluginClassLoader().loadClass(broadcastClassName);
                Constructor<?> constructor = pluginBroadcastClassName.getConstructor(new Class[]{});
                iBroadcast = (IBroadcast) constructor.newInstance(new Object[]{});
              //返回給插件中廣播生命週期
                iBroadcast.attach(context);
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, e.getMessage());
            }
        }
    複製代碼
  3. 接收到消息返回給插件中

    @Override
        public void onReceive(Context context, Intent intent) {
            iBroadcast.onReceive(context, intent);
        }
    複製代碼
  4. 插件中廣播註冊

    /** * 動態註冊廣播 */
        public void register() {
            //動態註冊廣播
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction("_DevYK");
            receiver = new PluginBroadReceiver();
            registerReceiver(receiver, intentFilter);
        }
    
        /** * 經過代理去註冊廣播 * * @param receiver * @param filter * @return */
        @Override
        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
            if (that != null) {
                return that.registerReceiver(receiver, filter);
            } else
                return super.registerReceiver(receiver, filter);
        }
    複製代碼
  5. 插件中實現代理廣播中的生命週期並實現接收函數

    public class BaseBroadReceiverImp extends BroadcastReceiver implements IBroadcast {
      //代理廣播中綁定成功插件廣播
        @Override
        public void attach(Context context) {
    
        }
    
      //代理廣播接收到數據轉發給插件中
        @Override
        public void onReceive(Context context, Intent intent) {
    
        }
    }
    複製代碼

加載插件中 Service

流程圖

-service.png

代碼實現

  1. ProxyAcitivy 開啓插件中服務

    /** * 加載插件中 啓動服務 * @param service * @return */
        @Override
        public ComponentName startService(Intent service) {
            String className = getLoadServiceClassName(service);
            Intent intent = new Intent(this,ProxyService.class);
            intent.putExtra(Constants.SERVICE_CLASS_NAME,className);
            return super.startService(intent);
        }
    複製代碼

    ProxyService.java

    public class ProxyService extends Service {
    
        private IService iService;
    
        @Override
        public IBinder onBind(Intent intent) {
            return iService.onBind(intent);
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
    
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            if (iService == null)
                init(intent);
            return iService.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public void onStart(Intent intent, int startId) {
            super.onStart(intent, startId);
           iService.onStart(intent,startId);
        }
    
        @Override
        public boolean onUnbind(Intent intent) {
            iService.onUnbind(intent);
            return super.onUnbind(intent);
        }
    
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            iService.onDestroy();
        }
    
    
        //初始化
        public void init(Intent proIntent) {
            //拿到須要啓動服務的全類名
            String serviceClassName = getServiceClassName(proIntent);
            try {
                Class<?> pluginService = PluginManager.getInstance().getPluginClassLoader().loadClass(serviceClassName);
                Constructor<?> constructor = pluginService.getConstructor(new Class[]{});
                iService = (IService) constructor.newInstance(new Object[]{});
                iService.onCreate(getApplicationContext());
            } catch (Exception e) {
                //加載 class
            }
        }
    
        @Override
        public ClassLoader getClassLoader() {
            return PluginManager.getInstance().getPluginClassLoader();
        }
    
        public String getServiceClassName(Intent intent) {
            return intent.getStringExtra(Constants.SERVICE_CLASS_NAME);
        }
    }
    複製代碼
  2. 插件服務實現 IService

    public class BaseServiceImp extends Service implements IService {
      ...
    }
    複製代碼
  3. 插件中重寫 startService 交於代理中處理

    /** * 加載插件中服務,交於代理處理 * @param service * @return */
        @Override
        public ComponentName startService(Intent service) {
            String className = getLoadServiceClassName(service);
            Intent intent = new Intent(this,ProxyService.class);
            intent.putExtra(Constants.SERVICE_CLASS_NAME,className);
            return super.startService(intent);
        }
    複製代碼

總結

動態加載 Activity, Broadcast , Service 其實基本原理就是將插件中須要啓動四大組件的信息告訴代理類中,讓代理類來負責處理插件中的邏輯,代理類中處理完以後經過 IActivity, IBroadcast, IService 來通知插件。

動態加載插件咱們這篇文章就講到這裏了,感興趣的能夠參考項目地址 ,這個實現方案不適合線上商業項目,使用需謹慎。若是項目中只用到了插件中的生命週期能夠選擇性的使用。

感謝閱覽本篇文章,謝謝!

參考文章

《Android 插件化開發指南》

Android插件化框架總結

深刻理解Android插件化技術

DroidPlugin

相關文章
相關標籤/搜索