人人都能看得懂的動態化加載插件技術模型實現

基本概念

插件化其實也就是 模塊化->組件化 演變而來, 屬於動態加載技術,主要用於解決應用越來越龐大以及功能模塊的解耦,小項目中一般用的不多。
原理: 插件化的原理其實就是在 APP 殼運行過程中,動態加載一些程序中原本不存在的可執行文件並運行這些文件中的代碼邏輯。可執行文件總的來說分爲兩個,其一是動態鏈接庫 so,其二是 dex 相關文件包括 jar/apk 文件。

發展歷史

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

時間 框架名稱 作者 框架簡介
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 月 RePlugin 360手機衛士 RePlugin 是一套完整的、穩定的、適合全面使用的,佔坑類插件化方案,由 360 手機衛士的RePlugin Team 研發,也是業內首個提出”全面插件化“(全面特性、全面兼容、全面使用)的方案。
2019 Shadow 騰訊 Shadow 是一個騰訊自主研發的 Android 插件框架,經過線上億級用戶量檢驗。 Shadow 不僅開源分享了插件技術的關鍵代碼,還完整的分享了上線部署所需要的所有設計(零反射)

插件化必備知識

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

實現簡易版本插件化框架

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

先來看一個我們最終實現的效果
在這裏插入圖片描述

加載插件 APK

在加載 APK 之前我們先來了解下 ClassLoader 家族,繼承關係圖
在這裏插入圖片描述
DexClassLoader 加載流程

在這裏插入圖片描述從上面 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

實現流程在這裏插入圖片描述代碼實現流程

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());
        }
    }
}
  1. 重寫代理類中的 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);
    }
  1. 插件 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

流程圖

在這裏插入圖片描述

代碼實現
  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);
}
  1. 加載插件中需要註冊的廣播全路徑
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());
    }
}
  1. 接收到消息返回給插件中
@Override
public void onReceive(Context context, Intent intent) {
    iBroadcast.onReceive(context, intent);
}
  1. 插件中廣播註冊
 /**
     * 動態註冊廣播
     */
    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);
    }
  1. 插件中實現代理廣播中的生命週期並實現接收函數
public class BaseBroadReceiverImp extends BroadcastReceiver implements IBroadcast {
  //代理廣播中綁定成功插件廣播
    @Override
    public void attach(Context context) {

    }

  //代理廣播接收到數據轉發給插件中
    @Override
    public void onReceive(Context context, Intent intent) {

    }
}

加載插件中 Service

流程圖

在這裏插入圖片描述

代碼實現

  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);
    }
}
  1. 插件服務實現 IService
public class BaseServiceImp extends Service implements IService {
  ...
}
  1. 插件中重寫 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 來通知插件。
動態加載插件我們這篇文章就講到這裏了,感興趣的可以參考項目地址 ,這個實現方案不適合線上商業項目,使用需謹慎。如果項目中只用到了插件中的生命週期可以選擇性的使用。
感謝閱覽本篇文章,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章