[Android 插件化(一)] DynamicLoadApk的用法

1 簡介

Android大型項目中爲了減小apk的體積,可以採用插件化的方法,即一些不常用的功能獨立成插件,當用戶需要的使用的時候再從服務器上下載回來,動態加載。這樣就避免了爲了滿足所有用戶需求而把功能全部打包到apk,導致apk體積的膨脹。所謂的插件,其實也是一個apk,但是一般都依賴正式對外發布的app,也叫宿主。本篇不討論插件化的原理和實現難點,只介紹怎麼使用以及優缺點。
Android插件化常用實現方案有兩種:

(1) DynamicLoadApk
(2) DroidPlugin

這兩個是目前比較主流的Android插件化實現方案,在Github的星星數很高,兩者的Github地址如下:

DynamicLoadApk的Github地址:https://github.com/singwhatiwanna/dynamic-load-apk
DroidPlugin的Github地址:https://github.com/Qihoo360/DroidPlugin

2 特點

(1) DynamicLoadApk是由團隊維護的,但是目前已經很長時間沒有更新了,途牛用的就是這個插件化框架
優點:

  • 插件不依賴宿主,對宿主開發者透明,提供三種依賴方式
  • 宿主和插件可以頻繁交互,啓動時間短

缺點:

  • 插件apk必須實現DLBasePluginActivity,屬於侵入式的,以及不支持service
  • 宿主調用插件和插件內部的相互調用都要使用DL提供的方法,而不能使用Android原生的api,例如:啓動Activity
  • 插件開發有一套規定,因此造成插件開發門檻高,學習成本高

(2) DroidPlugin是360公司開源的一個框架,已經在360手機助手上使用
優點:

  • 宿主和插件完全隔離,插件不依賴宿主,可以獨立安裝運行
  • 低入侵設計,插件不需要繼承任何類
  • 插件apk和普通apk一樣的,所以插件開發沒有門檻
  • 有大公司維護,有360手機助手這樣的商用app在使用

缺點:

  • 插件啓動速度太慢,而且宿主只能調用插件的LaunchMode的Activity,不能調用其他Activity

3 使用方法

(1) 導入Dynamic-load-apk中的lib。
下載Dynamic-load-apk後解壓,在Android Studio中新建工程DLTest(自己命名) –> new –> import module –>選擇lib所在的目錄:dynamic-load-apk-master\DynamicLoadApk\lib

這裏寫圖片描述

(2) 新建插件模塊plugin,宿主模塊host,這兩個模塊都是application, 最後都要生成apk的。項目目錄如下:

這裏寫圖片描述

編譯lib模塊,命令是build菜單–>make module lib,目的是爲了獲得生成的jar文件,jar文件所在位置是lib\build\intermediates\bundles\debug\class.jar,複製jar文件重命名爲lib.jar

這裏寫圖片描述

(3) 導入lib.jar到plugin項目的libs目錄下,開發plugin項目,注意Activity要繼承DLBasePluginActivity ,R.layout.activity_test上就一個TextView,顯示”這個界面來自Plugin”
插件項目Plugin的MainActivity:

public class MainActivity extends DLBasePluginActivity {

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

注意:plugin模塊的buidle.gradle需要修改爲如下:

dependencies {
    provided fileTree(dir: 'libs', include: ['*.jar'])
    .........
}

provided 意思是編譯時候使用,但不打包到APK中,這樣做是因爲我們的宿主項目host中已經包含了lib.jar,如果插件中也包含的話就會報找不到plugin中的Activity的錯,原因是兩個包重複,必須要用host中的DL框架來加載plugin,而不是plugin自帶的DL框架

(4) 導入lib.jar到host項目的libs目錄下,開發host項目
host項目的MainActivity:

public class MainActivity extends Activity {

    private Button btnTest;
    private TextView tvTip;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.btnTest = (Button) findViewById(R.id.btn_test);
        this.tvTip = (TextView) findViewById(R.id.tv_tip);
        this.init();
    }

    //初始化
    private void init() {
        //獲取插件
        String pluginFolder = "/mnt/sdcard/DynamicLoadHost";
        File file = new File(pluginFolder);
        File[] plugins = file.listFiles();
        //判斷有沒有插件
        if (plugins == null || plugins.length == 0) {
            this.tvTip.setVisibility(View.VISIBLE);
            return;
        }
        //調用第一個插件
        File plugin = plugins[0];
        final PluginItem item = new PluginItem();
        item.pluginPath = plugin.getAbsolutePath();
        item.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);
        //獲取插件的啓動Activity的名稱
        if (item.packageInfo.activities != null && item.packageInfo.activities.length > 0) {
            item.launcherActivityName = item.packageInfo.activities[0].name;
        }
        //獲取插件啓動Service的名稱
        if (item.packageInfo.services != null && item.packageInfo.services.length > 0) {
            item.launcherServiceName = item.packageInfo.services[0].name;
        }
        //顯示插件
        tvTip.setText("檢測到一個插件:" + item.pluginPath);
        //加載插件
        DLPluginManager.getInstance(this).loadApk(item.pluginPath);
        //添加監聽器
        this.btnTest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //提示
                Toast.makeText(getApplicationContext(), "開始調用插件", Toast.LENGTH_SHORT).show();
                //調用插件
                usePlugin(item);
            }
        });
    }

    //調用插件
    private void usePlugin(PluginItem pluginItem) {
        DLPluginManager pluginManager = DLPluginManager.getInstance(this);
        pluginManager.startPluginActivity(this, new DLIntent(pluginItem.packageInfo.packageName, pluginItem.launcherActivityName));
    }

    //插件Bean
    public static class PluginItem {
        public PackageInfo packageInfo;
        public String pluginPath;
        public String launcherActivityName;
        public String launcherServiceName;

        public PluginItem() {
        }
    }
}

host的activity_main.xml:

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <TextView
        android:id="@+id/tv_tip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="沒有檢測到插件"
        />

    <Button
        android:id="@+id/btn_test"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv_tip"
        android:layout_marginTop="10dp"
        android:text="測試調用插件"
        />


</RelativeLayout>

注意:Host的AndroidManifest.xml中需要額外的聲明幾個DL框架中的類,否則運行時候找不到Activity.

Host的AndroidManifest.xml:

<manifest
    package="com.host"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <activity
            android:name="com.ryg.dynamicload.DLProxyActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="com.ryg.dynamicload.proxy.activity.VIEW"/>

                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
        <activity
            android:name="com.ryg.dynamicload.DLProxyFragmentActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="com.ryg.dynamicload.proxy.fragmentactivity.VIEW"/>

                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

        <service android:name="com.ryg.dynamicload.DLProxyService"/>
    </application>

</manifest>

(5) 編譯plugin項目,將生成的plugin-debug.apk文件放入/mnt/sdcard/DynaminLoadHost目錄下,然後運行Host,運行結果如下:
這裏寫圖片描述

這裏寫圖片描述

4 總結

  • 1 宿主和插件沒有任何聯繫,但是插件需要繼承DLBasePluginActivity,這個不太友好
  • 2 侵入式的,對插件apk的開發限制太多,例如:必須繼承DLBasePluginActivity,啓動時候必須調用startPluginActivity(new
    DLIntent(getPackageName(),TestActivity.class))
  • 3 這個框架學習成本高,限制多,聯調不方便,不建議使用
  • 4 目前比較好的插件有360公司的DroidPlugin, 以及類似友盟的第三方解決方案ApkPlug

5 擴展

1 Android Studio中可以不用jar文件嗎?

Host可以,但plugin不可以。宿主項目可以依賴於lib項目,但是plugin必須使用jar文件,原因參見下面第3條

2 Host項目如何直接依賴lib項目,而不用jar文件?

修改host文件的build.gradle文件

dependencies {
    compile project(':lib')
    .....
}

修改lib的build.gradle文件,不修改的話就會和host項目中的support-v4包衝突

dependencies {
    provided fileTree(dir: 'libs', include: ['*.jar'])
}

3 爲什麼plugin必須使用jar文件?

Plugin不能將lib模塊打包到apk中,所以不能使用compile,只能使用provided,所以如果不用jar則plugin模塊的build.gradle只能如下:

dependencies {
    provided project(':lib')
    .....
}

呵呵,可惜這樣是不行的,project只能使用compile ,不能使用provided ,百度了半天沒有解決這個問題,如果你有辦法歡迎留言

6 參考博客:

(1) http://blog.csdn.net/singwhatiwanna/article/details/40283117

7 轉載請註明來自“梧桐那時雨”的博客:http://blog.csdn.net/fuchaosz/article/details/51056947

Tips:
如果覺得這篇博客對你有幫助,就給博主留個言或者頂一下唄,鼓勵一下博主創作出更多優質博客,Thank you

發佈了34 篇原創文章 · 獲贊 338 · 訪問量 90萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章