插件化——插樁式實現(xiàn)Activity跳轉(zhuǎn)

代碼(已適配android10)已上傳github中,親測可用,對你有用的話,記得star,謝謝

先上效果圖

ezgif.com-video-to-gif_gaitubao_598x1023.gif

前言

關(guān)于插件化網(wǎng)上比比皆是,但很遺憾之前開發(fā)一直沒有真正遇到過插件化的公司項目。由于疫情原因換了家新公司并且提前轉(zhuǎn)正,這個項目也是我們用組件化從0開始重構(gòu),目前已開發(fā)完成。最近領(lǐng)導(dǎo)說apk包體積太大了,而且里面有個模塊,可以根據(jù)接口類型動態(tài)加載,所以這篇文章誕生了。

插件化概念

將整個app拆分成很多模塊,每個模塊都是一個apk,最終打包的時候?qū)⑺拗鱝pk和插件apk分開打包,插件apk通過動態(tài)下發(fā)到宿主apk,實現(xiàn)了動態(tài)加載插件并大大減少了包體積。

插件化優(yōu)點

  • 提高編譯速度:開發(fā)過程中,每個模塊都是獨立開發(fā)的,編譯的時候每次運行不需要都編譯所有的業(yè)務(wù)邏輯代碼,所以會適當?shù)奶岣呶覀兊拈_發(fā)速度;

  • 業(yè)務(wù)模塊完全解耦:每個業(yè)務(wù)都是完全獨立的,這樣開發(fā)過程中每個模塊的功能改變和其他模塊沒有任何關(guān)系,甚至可以隨意的去掉某一部分功能;

  • 利于團隊開發(fā):插件開發(fā)是團隊開發(fā)中用的最多的一種開發(fā)模式,可以更加的去分工,每個組只需要負責自己的功能,減少溝通成本提高開發(fā)效率;

  • 動態(tài)更新插件,按需下載模塊:對于一些不怎么常用的功能,可以讓用戶按需下載模塊,從而減少工程的大小,讓用戶在下載的時候能夠節(jié)省流量以及等待時間,而且功能升級的時候可以不更新主應(yīng)用只更新插件;

  • 解決android 655535問題。

插件化誕生

舉個美團的例子,你就懂了


image.png
image.png

美食頁面有那么多應(yīng)用,如果單純的用webview實現(xiàn)那里面的支付,地圖和圖片瀏覽等有點不切實際,或者全部寫一個app里面,那包體積少說也有200M,可是你去應(yīng)用市場看到,也才80M左右,這時插件化出場了


image.png

實現(xiàn)插件化的方式:

  • 插樁式(本篇文章講的就是這種方式)
  • Hook方式,這個到時也會學(xué)習(xí)一個Hook的效果。
  • 反射,但是在Android9.0中有很多反射是用不了了,所以這種基本上不會用了。
插樁式原理

一圖勝千言,看圖


結(jié)構(gòu)流程圖.png

上圖右邊美團外賣是以一個單獨的apk(可以這樣理解:一個apk就是一個插件)存在的,宿主App(美團)想要打開插件(美團外賣)中的某一個Activity,但是美團外賣這個插件很顯然是沒有上下文對象的【原因:因為此插件沒有安裝到手機上】,要想啟動Activity必須要解決上下文這個東西,所以此時就需要在宿主APP中插一個樁,聲明一個代理的Activity,如下:

流程圖.png

此時ProxyActivity是一個空殼,可是沒有顯示插件的東西呀,怎么辦?其實是這樣的:
流程圖.png

如何將一個未安裝的插件apk的Activity能顯示在這個代理ProxyActivity中呢?其實要想插件Activity顯示出來肯定得要調(diào)用它里面的生命周期方法,而對于插件而言就是將自己Activity中的各種生命周期方法通過接口對外暴露給宿主的ProxyActivity,然后插件Activity中需要的Context則是借用ProxyActivity,這樣最終就能達到我們調(diào)用的目的,目的達成最終插件化也就這實現(xiàn)了。
所以實現(xiàn)宿主Activity跳轉(zhuǎn)插件化Activity,需要2樣?xùn)|西:
①暴露代理Activity的生命周期給插件化;
②提供上下文給插件化

開干

新建項目

宿主是app,插件是orderfood,這里注意orderfood也是application

image.png

宿主app插件orderfood新建完成,此時需要一個接口來暴露插件orderfood Activity的生命周期,所以還需要定義一個宿主app插件orderfood之間公共的library,里面會定義各種公共接口,這里起名library為:lib_plugin ,如下:

image.png

添加依賴

然后添加對它的依賴


宿主app的依賴.png
插件app的依賴.png

①暴露生命周期

然后在library中定義Activity生命周期的公共接口,如下:


image.png

然后插件Activity得要將其生命周期方法對外暴露,所以需要實現(xiàn)這個接口:


image.png

但是如圖并未對接口中的方法進行重寫,因為這樣寫是不合適的,插件中肯定會有n個Activity的,所以需要抽取一個BaseActivity出來,然后再由它來實現(xiàn)抽象接口才靠譜,所以:
image.png

image.png

image.png

②提供上下文給插件化app
上面提到過,插件是不會裝在手機上的apk,那么插件中的Activity是沒有上下文的

image.png

所以需要在BaseActivity中來先重寫一個這個方法
image.png

我們已經(jīng)在插件化把相應(yīng)的方法進行重寫,此時需要把代理的上下文傳給插件app,我們在宿主App新建一個代理Activity,并在清單文件注冊:
image.png

image.png

接下來就是把代理Activity上下文傳給插件化Activity中去,也就是如何調(diào)用BaseActivity中的attach()方法,這里就要用到反射了,這里需要知道要跳轉(zhuǎn)插件Activity的全類名,所以這里通過Intent的參數(shù)傳進來,如下:
image.png

然后通過反射來獲取到要跳轉(zhuǎn)插件Activity的對象,由于插件的所有Activity都繼承了BaseActivity了,而BaseActivity又實現(xiàn)了公共模塊的PluginInterface接口,所以最終就可以調(diào)用attach方法,如下:
image.png

所以代理Activity中改成如下:
image.png

我只重寫了onStart() 和 onReusme(),剩余的方法是一樣的。

在宿主中加載插件

對于加載插件一般有2種:內(nèi)置和外置。
內(nèi)置:就是插件的apk放在assert文件目錄中
外置:從服務(wù)器進行下載到手機sd卡上
不管哪種方式,都需要將插件的類加載進來才行,所以對宿主app的進行修改:

image.png

image.png

接下來就是加載插件了,新建一個插件管理器

 public class PluginManager {
      private Context mContext;//插件的資源對象
      private Resources pluginResource;

      //插件的類加載器
      private DexClassLoader dexClassLoader;

      //插件的包信息類
      private PackageInfo packageInfo;

      private static PluginManager pluginManager = new PluginManager();

      private PluginManager() {
      }

      public static PluginManager getInstance() {
          return pluginManager;
      }

      public void setContext(Context context) {
          this.mContext = context;
      }

      //加載插件apk
      public void loadPlugin(String pluginPath) {
          //獲取包管理器
          PackageManager packageManager = mContext.getPackageManager();
          //獲取插件的包信息類
          packageInfo = packageManager.getPackageArchiveInfo(pluginPath, PackageManager.GET_ACTIVITIES);

          //插件解壓后的目錄
          File pluginFile = mContext.getDir("plugin", Context.MODE_PRIVATE);

          //獲取到類加載器
          dexClassLoader = new DexClassLoader(pluginPath, pluginFile.getAbsolutePath(), null, mContext.getClassLoader());

          //獲取到插件的資源對象
          try {
              AssetManager assetManager = AssetManager.class.newInstance();
              Method addAssetPath = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
              addAssetPath.invoke(assetManager, pluginPath);
              pluginResource = new Resources(assetManager,mContext.getResources().getDisplayMetrics(),mContext.getResources().getConfiguration());
          } catch (Exception e) {
              e.printStackTrace();
          }
      }

      public Resources getPluginResource() {
          return pluginResource;
      }

      public DexClassLoader getDexClassLoader() {
          return dexClassLoader;
      }

      public PackageInfo getPackageInfo() {
          return packageInfo;
      }

}

接下來打包插件,放到sd卡中:


image.png

打包成功后,如下:


image.png

然后改個名字為:orderfood.apk上傳到sd卡根目錄下:

image.png

image.png

接下來在宿主Activity中實現(xiàn)跳轉(zhuǎn)插件代碼

 public class MainActivity extends AppCompatActivity {

      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
      }


      //跳轉(zhuǎn)插件
      public void skipPlugin(View view) {
          PluginManager.getInstance().setContext(this);
          PluginManager.getInstance().loadPlugin(Environment.getExternalStorageDirectory() + "/orderfood.apk");
          PackageInfo packageInfo = PluginManager.getInstance().getPackageInfo();
          Intent intent = new Intent(MainActivity.this, ProxyActivity.class);
          //由于插件只有一個activity,所以取數(shù)組第0個
          intent.putExtra("className", packageInfo.activities[0].name);
          startActivity(intent);
      }
  }

記得加權(quán)限


image.png

接下來運行app,由于我的手機是Android10.0,所以對于sdcard的權(quán)限得要主動申請一下,這里就不寫申請的代碼了,主動到權(quán)限管理中先將其打開,如下:

image.png

代碼(已適配android10)已上傳github
中,親測可用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容