Android架構設計之插件化、組件化

現在移動app市場已是百花齊放,其中有不乏有不少大型公司、巨型公司都是經過app創業發展起來的;app類型更加豐富,有電子商務、有視頻、有社交、有工具等等,基本上涵蓋了各行各業每一個角落,爲了更加具備競爭力app不只功能上有創性,內容也更加多元化,更加飽滿,因此出現了巨大的工程。這些工程代碼不停添加若是沒有一個好的架構全部代碼將會強耦合在一塊兒,功能直接也會有不少依賴,那麼就會出現不少問題;例如:html

一、修改功能困難,牽一髮動全身。不少地方若是api寫的很差,封裝不優雅,那麼就會出現改一個地方須要改不少地方的調用。
二、更新迭代工做中冗餘廢棄代碼資源過多形成刪除冗餘變得很複雜,而且極可能出現不少bug。html5

大型app有哪些架構解決方案?
在編碼架構上有:java

mvc
mvp
mvvm

從項目結構上有:linux

插件化
組件化

這裏咱們一個個來分析說明:android

首先咱們來看看編碼設計模式,上面模式的模式都是抽象模式,因此這個具象界定沒有官方一致的規定。因此要根據本身的理解很解釋都有本身的一套mvc模式,不必定是具象到什麼細節這類的,下面討論的也是會舉出例子來講明本身的理解。ios

代碼設計模式
MVC全名是Model View Controller,是模型(model)-視圖(view)-控制器(controller)的縮寫,一種軟件設計典範,用一種業務邏輯、數據、界面顯示分離的方法組織代碼,將業務邏輯彙集到一個部件裏面,在改進和個性化定製界面及用戶交互的同時,不須要從新編寫業務邏輯。MVC被獨特的發展起來用於映射傳統的輸入、處理和輸出功能在一個邏輯的圖形化用戶界面的結構中。git

舉個栗子:具備生命週期的activity至關於Controller, 本身開發封裝用於獲取數據(網絡數據、本地數據、數據處理邏輯等)的api至關與Model,xml控件和自定義控制控件顯示數據的邏輯至關與view。
mvc模式是很是常見的模式基本上有基本概念就能按照這個模式進行開發,這裏就不過多討論了。github

MVP 全稱:Model-View-Presenter ;MVP 是從經典的模式MVC演變而來,它們的基本思想有相通的地方:Controller/Presenter負責邏輯的處理,Model提供數據,View負責顯示。web

舉個栗子:Adapter至關與Presenter控制控制數據與顯示的分離,向Adapter餵食數據的api獲取處理數據至關與Model,支持Adapter的顯示的控件至關於View層。ajax

mvp是從mvc基礎上衍生出來的,mvp看上去與mvc好像沒有什麼差異,可是實際否則,mvc model數據與view層組合是直接組合,不免會產生耦合,這樣model複用性有必定缺失。mvp優化這種結構,他抽象出來一個接口規則,那麼view須要支持這個規則,而model按照這個規則向裏面餵食數據。這樣解開耦合model view,這樣model與view連接邏輯都是用Presenter控制。

MVVM是Model-View-ViewModel的簡寫。微軟的WPF帶來了新的技術體驗,如Silverlight、音頻、視頻、3D、動畫……,這致使了軟件UI層更加細節化、可定製化。同時,在技術層面,WPF也帶來了 諸如Binding、Dependency Property、Routed Events、Command、DataTemplate、ControlTemplate等新特性。MVVM(Model-View-ViewModel)框架的由來即是MVP(Model-View-Presenter)模式與WPF結合的應用方式時發展演變過來的一種新型架構框架。它立足於原有MVP框架而且把WPF的新特性糅合進去,以應對客戶日益複雜的需求變化。

舉個栗子:使用databing能夠搭建mvvm架構,獲取網絡數據封裝api至關於Model,數據處理後分給databing設置界面綁定數據源和和界面上綁定的邏輯至關於ViewModel層,用於最終現實的控件至關於view層。
其實看到上面的彷佛有點模糊不清楚,用mvp做爲參照,只是p層替換成了vm層,增長xml功能屬性,可以利於view層屬性方法來擴張功能,將一些與界面相關邏輯處理加入這層,更加細分了抽象層次。

android組件化方案
組件化:
Android studio改變了項目構建方式,eclipse環境下的工做空間和project變成如今的module和項目,這樣類別雖然不精確可是這個不是重點,重點他加入項目構建工具gradle使得咱們項目構建變得很是簡單了。接下來用一個項目組件化方案來體會一下項目組件化的。

組件化好處:
一、架構清晰業務組件間完成接耦合。
二、每一個業務組件均可以根據BU需求完成獨立app發佈。
三、開發中使開發者更加輕鬆,開發中加快功能開發調試的速度。
四、業務組件總體刪除添加替換變得很是輕鬆,減小工程中的代碼資源等冗餘文件。
五、業務降級,業務組件在促銷高峯期間能夠業務爲單元關閉,保證核心業務組件的順利執行。

項目組件化方案
概述:
一、module library 切換。
二、組件間跳轉uri跳轉。
三、組件間通信 binder機制。

首先看看項目中的角色:

從上圖能夠發現有一根業務總線講全部組件個串聯起來,其中組件總線至關於主工程(殼工程mudule),而業務組件至關於工程中(mudule/library)。能夠看出組件化實現能夠有本身認定的維度,這裏只是使用了最經常使用的維度按照業務區分組件。
上面是從抽象角來描述的一張圖,下面咱們從具體角度來來看看工程結構:


從圖片能夠看出,主要有三個角色:
一、主工程(殼工程mudele):主要負責事情不塞入任何具體業務邏輯,主要用於使用組合業務組件、初始化配置和發佈應用配置等操做。

二、組件(module/library):主要實現具體業務邏輯,儘量保證業務獨立性,例如如今手淘這樣一個大型的app幾乎每一個bu功能塊都可以拿出來做爲一個獨立業務app。可是沒有這麼大型也能夠按照小一些的業務邏輯來區分組件,例如:購物車組件、收銀臺組件、用戶中心組件等,具體更具本身的項目須要來劃分。

三、公共庫(library):公共使用的工具類、sdk等庫,例如eventbus、xutils、rxandroid、自定義工具類等等,這些庫能夠作成一個公共common sdk、也能夠實現抽離很細按照需求依賴使用。
他們之間的關係則是 主工程依賴組件、組件依賴公共庫。

組件開發中分爲兩種模式一種開發測試模式、一種是發佈模式:
一、開發測試模式:這種模式下面組件應該是獨立module模式,module是能夠獨立運行的,只要保證他對其餘業務沒有依賴就能夠獨立開發測試。
二、發佈模式:這時候組件應該library模式被主工程依賴組合,發佈運行,全部業務將組合成完整app發佈運行。

上面模式提出了個幾個問題咱們能夠一一來解決;
問題一:上面兩種模式要求組件一會是module,一會是library這樣切換是如何實現的?
問題二:業務之間跳轉如何進行跳轉?
問題三:雖然業務組件相對獨立,可是若是有時候必定須要獲取其餘組件運行是某些狀態下數據,也就是組件數據間的數據互通如何實現?

問題一:業務組件module/library切換解決方法
是用gradle輕鬆能夠解決這個問題;每當咱們用AndroidStudio建立一個Android項目後,就會在項目的根目錄中生成一個文件 gradle.properties,咱們將使用這個文件的一個重要屬性:在Android項目中的任何一個build.gradle文件中均可以把gradle.properties中的常量讀取出來;那麼咱們在上面提到解決辦法就有了實際行動的方法,首先咱們在gradle.properties中定義一個常量值 isPlugin(是不是組件開發模式,true爲是,false爲否):

isPlugin=false

而後咱們在業務組件的build.gradle中讀取 isPlugin,可是 gradle.properties 還有一個重要屬性: gradle.properties 中的數據類型都是String類型,使用其餘數據類型須要自行轉換;也就是說咱們讀到 isPlugin 是個String類型的值,而咱們須要的是Boolean值,代碼以下:

if (isPlugin.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

這樣能夠輕鬆設置isModule就能夠變成切換module、library。

接下來要解決的是就是包名,要知道library是不須要包名的,那麼就能夠這樣操做:

 defaultConfig {
        if (isPlugin.toBoolean()){
            applicationId 'com.example.rspluginmodule' 
        }

    ....

    }

最後還要處理AndroidManifest.xml問題,由於library、module的主配置文件是有區別的:

能夠這樣處理首先在main文件家中建立release文件夾而後拷貝一份AndroidManifest.xml進入release文件夾,那麼發佈模式下使用的就是release文件夾下面的AndroidManifest.xml,而開發模式下用的就是默認的AndroidManifest.xml,這樣就要對release文件夾下面的AndroidManifest.xml進行修改由於開發模式下release文件夾下面是用來給library使用的。

結構如圖:

修改內容release文件夾AndroidManifest.xml內容爲:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.rspluginmodule">

    .........

    <application>

        <activity
            android:name="com.example.rspluginmodule.RSPluginTestActivity"
            android:exported="false"
            android:screenOrientation="portrait">
            <intent-filter>
                <data
                    android:host="sijienet"
                    android:path="/plugin_uri_path"
                    android:scheme="app_schem" />
                <action android:name="cn.com.bailian.plugin.VIEW_ACTION" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

    ........

    </application>

</manifest>

能夠發現上面去掉了application不少module使用的屬性。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tool="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.rspluginmodule">


    ........

    <application
        android:allowBackup="true"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:replace="android:allowBackup">

    <!--測試入口activity 只有在module環境下配置-->
        <activity android:name=".RSMainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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


        <activity
            android:name="com.example.rspluginmodule.RSPluginTestActivity"
            android:exported="false"
            android:screenOrientation="portrait">
            <intent-filter>
                <data
                    android:host="sijienet"
                    android:path="/plugin_uri_path"
                    android:scheme="app_schem" />
                <action android:name="cn.com.bailian.plugin.VIEW_ACTION" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

    ..........


    </application>

</manifest>

那麼AndroidManifest.xml文件已經創建好了,接下來就要修改gradle配置了;

  sourceSets {
        main {
            if (isPlugin.toBoolean()){
                manifest.srcFile 'src/main/AndroidManifest.xml'
            }else {
                manifest.srcFile 'src/main/release/AndroidManifest.xml'
            }
        }
    }

這樣問題一就所有解決了,咱們能夠輕鬆使用isPlugin來切換業務組件的module和library。接下來咱們看看業務組件間如何進行跳轉問題。

問題二:業務組件間跳轉解決方法
無論是開發模式或者是發佈模式咱們都須要處理界面間跳轉問題,在業務運行階段常常會有跳轉到不一樣業務組件的界面的需求,咱們用什麼方法是能夠解決這個問題,其實android自己提供了這種機制,並且在不少地方都在使用。例如:調用系統拍照功能、電話功能等這些功能都是在不一樣app當中,你也能夠理解爲不一樣的module當中,他們之間的調用底層都是進程通信,實現手段很是簡單,就是是使用意圖篩選器。
能夠看到上面的AndroidManifest.xml中配置組件activity時候都有配置意圖篩選器;

            <intent-filter>
                <data
                    android:host="sijienet"
                    android:path="/plugin_uri_path"
                    android:scheme="app_schem" />
                <action android:name="cn.com.bailian.plugin.VIEW_ACTION" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>

咱們就能夠經過隱式意圖方式打開新的其餘組件activity;舉個例子我要打開RSPluginTestActivity類;就能夠調用下面的方法。

public RMRouter jump(Activity activity,String url, String parm, int animId){
        if (url==null)
            return this;

        Log.i(TAG,"jump page=="+url);
        Log.i(TAG,"jump page parm=="+parm);

        Intent intent=new Intent(RMConfig.ROUTER_URL_ACTION, Uri.parse(url));
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        intent.putExtra(RMConfig.ROUTER_PARM, parm);

        PackageManager packageManager=activity.getPackageManager();
        List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, 0);

        if (! resolveInfos.isEmpty())
        {
            activity.startActivity(intent);
            selectTranlateAnim(activity, animId);
        }else {
            Log.i(TAG,"no page");
        }
        return this;
    }

上面使用的是uri跳轉,也能夠簡單點使用跳轉。

這裏還有一個就是規範問題:
一、組件命名規範,java類名加大些前綴,例如RSPluginTestActivity RS就是前綴,相似ios要求的代碼約定,xml、image等資源文件使用對應前綴例如 rs_ 。
二、組件內的activity、service系統組件要遵照rest風格(rest風格把業務對象看做資源用惟一uri標識調用),組件間儘可能可以經過uri惟一標識調用,不用過多業務bean傳遞依賴。

這樣問題二組件跳轉問題就解決了。接下來就來解決最後一座大山問題了:

問題三:組件間通信問題
組件間若是按照規範應該業務邏輯獨立,對其餘模塊沒有耦合的狀況,可是有時候要發生那麼數據交換的話要怎麼解決?若是嚴格按照rest風格業務組件每塊只須要經過界面間跳轉的方式就能夠輕鬆經過intent將數據傳輸過去,基本上能夠知足組件間數據傳遞的問題。可是這個只是簡單誇界面數據傳遞,那麼若是要是沒有界面跨越也想組件間數據傳遞那麼要怎麼解決?相似web開發http協議能夠經過get post傳遞數據,那麼不跳頁的時候數據應該如何通信,web提供了ajax機制。那麼android提供什麼機制知足咱們需求?

若是按照地耦合的方式開發,咱們業務組件間是能夠獨立存在,而且不須要依賴其餘業務組件,若是公共部分就能夠提取成公共業務組件工具庫library,可是開發需求老是很是多變,若是有時候有着狀況時候咱們就要用到進程通信aidl。首先要知道組件開發模式下的組件都是獨立module,那麼每一個獨立的module都是獨立進程;在發佈模式下面每一個業務組件又是library,那麼進程變成了一個。aidl解決進程間通信、系統組件activity、service通信問題的方案。aidl其實是android提供生成binder接口的方法而已,實際上底層使用的都是binder機制。

binder機制這裏簡單介紹一下,他基本上貫通了了怎麼android系統和應用,首先他是android首選進程通信機制(IPC),android是創建在linux kernel之上的因此他支持linux的IPC方式,例如:網絡連接進程通信(Internet Process Connection): 管道(Pipe)、信號(Signal)和跟蹤(Trace)、插口(Socket)、報文隊列(Message)、共享內存(Share Memory)和信號量(Semaphore)。那麼爲何還要出現binder機制那是由於它是針對移動端這種時效性快、資源消耗低而設計出來了,是移動端首選的進程通信方式。從binder應用的範圍就知道他重要性,除了Zygote進程和SystemServer進程之間使用的socket通信以外,基本上其餘進程通信都是用的是binder方式。

首先咱們來看一張圖:

能夠發現每一個module都provider屬於本身提供出去的action,這樣這些能夠在提供其餘業務組件調用。這時候provider端至關於服務端,提供處理後數據,調用至關於客戶端。

下面看看binder機制實現方法:
首先第一步建立進程通信接口:
CommonProvider.java

/**
 * 做者: 李一航
 * 時間: 18-1-4.
 */

public interface CommonProvider extends IInterface {
    String getJsonData(String jsonParm) throws RemoteException;
}

這裏只是建立一個action function 例子,能夠根據本身須要建立多個action function。

接下來建立service端實現基類:

CommonStub.java

/**
 * 做者: 李一航
 * 時間: 18-1-4.
 */

public abstract class CommonStub extends Binder implements CommonProvider {

    public static final String DESCRIPTOR="com.ffmpeg.bin.CommonProvider";
    public static final int ACTION_1 = IBinder.FIRST_CALL_TRANSACTION;

    public CommonStub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    @Override
    public IBinder asBinder() {
        return this;
    }

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code)
        {
            case INTERFACE_TRANSACTION:
            {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case ACTION_1:
            {
                data.enforceInterface(DESCRIPTOR);
                String parm = data.readString();
                String jsonData = getJsonData(parm);
                reply.writeNoException();
                reply.writeString(jsonData);
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }
}

service端實現了binder機制回調動做:onTransact 方法。這裏將會調用繼承類的接口實現法方法:getJsonData

接下來建立遠程代理類:

CommonProxy.java

/**
 * 做者: 李一航
 * 時間: 18-1-4.
 */

public class CommonProxy implements CommonProvider {

    private IBinder binder;

    public CommonProxy(IBinder binder) {
        this.binder = binder;
    }

    public static CommonProvider asInterface(IBinder iBinder){
        if (iBinder==null)
            return null;
        IInterface iInterface = iBinder.queryLocalInterface(CommonStub.DESCRIPTOR);
        if (iInterface!=null && iInterface instanceof CommonProvider)
            return (CommonProvider)iInterface;
        return new CommonProxy(iBinder);
    }

    @Override
    public String getJsonData(String jsonParm) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        String result=null;
        try {
            data.writeInterfaceToken(CommonStub.DESCRIPTOR);
            data.writeString(jsonParm);
            binder.transact(CommonStub.ACTION_1, data, reply, 0);
            reply.readException();
            result=reply.readString();
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            data.recycle();
            reply.recycle();
        }
        return result;
    }

    @Override
    public IBinder asBinder() {
        return binder;
    }
}

使用代理能夠拿到接口代理對象完成action操做。

三個binder使用的類、接口能夠做爲基礎類庫使用。接下來能夠完成遠程調用例子:

建立service業務實現類: 
CommonProviderImp.java

/**
 * 做者: 李一航
 * 時間: 18-1-4.
 */

public class CommonProviderImp extends CommonStub {
    @Override
    public String getJsonData(String jsonParm) throws RemoteException {

        // TODO: 18-1-4 provider action logic

        return "result data string+parm:"+jsonParm;
    }
}

這裏只完成了簡單的輸入輸出,方便理解測試;

建立調用service類: 
PluginProviderService.java

/**
 * 做者: 李一航
 * 時間: 18-1-4.
 */

public class PluginProviderService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new CommonProviderImp();
    }
}

在AndroidManifest.xml配置service信息:

 <service android:name="com.ffmpeg.bin.PluginProviderService">
            <intent-filter>
                <action android:name="android.intent.action.PROVIDER_MAIN_ACTION"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>

上面provider暴露的action function完成了,接下來就在其餘業務組件中完成調用測試。 
建立調用測試activity調用: 
ClientActivity.java

/**
 * 做者: 李一航
 * 時間: 18-1-4.
 */

public class ClientActivity extends AppCompatActivity implements ServiceConnection {

    private CommonProvider provider;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Intent intent=new Intent();
        intent.setComponent(new ComponentName(getPackageName(), PluginProviderService.class.getName()));

        bindService(intent,this, BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        unbindService(this);
    }

    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        provider = CommonProxy.asInterface(iBinder);
        try {
            Log.i(ClientActivity.class.getSimpleName(), provider.getJsonData("input"));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        provider=null;
    }
}

組件間通信就這樣完成了,這裏爲了好理解代碼都是使用了最簡單樣子,能夠在項目進行封裝優化來適合項目複用。

組件化總結
組件化是大型app開發中很是重要架構設計,其實上面貢獻的只是組件化方案的核心部分,要是一套完整組件化還須要處理不少細節問題,在開發中還要不斷封裝、優化、複用等纔可以使得架構清晰健壯。上面組件化方案中主要涉及到的知識點並不複雜,歸納一下有gradle項目配置、router uri、 binder進程間通信等。組件化重要的思想,如今不少文章有各類各樣的方案,理清思路選擇適合本身項目的架構纔是最重要的。

android插件化方案
有了上面組件化方案理解以後,插件化理解也不是難事,首先咱們仍是用業務爲界限來劃分組件內容;開發模式下面module原本就是一個獨立app,只是發佈模式下變成library,那麼插件化就是不存在發佈模式開發模式,每一個組件業務就是一個獨立apk開發,而後經過主工程app動態加載部署業務組件apk。

插件化帶來的好處:

1、業務組件解耦合,可以實現業務組件熱拔插。
2、更改了公司開發的迭代模式,從之前以整個app發版流程更改成app發版和業務插件發版流程。
3、實現了用戶不須要更新app版本完成bug修復和業務組件升級等熱修復功能。
4、組件化有的好處插件化都有,由於插件化創建在組件化架構之上。

那麼插件化帶來都是好處,有沒有缺點呢?如今不少各式各樣的開源插件框架都有各自優勢和缺點;接下來咱們仍是一問題的形式來解決這樣問題。

首先咱們來了解一下插件化實現的原理,因爲插件化原理涵蓋內容太多這裏只是介紹一下核心內容;咱們瞭解一下app打包過程。請看下圖:

 


上面是android打包造成apk的一個過程,能夠發現android開發主要的部分是整合編譯代碼、整合編譯資源,而後就是安全簽名保證apk完整性。咱們再看一張圖:

上面是一個apk解壓以後的文件,能夠看出,裏面幾個比較重要的部分:

1、dex文件java編譯以後的代碼文件。
2、app中使用資源文件。
3、so文件動態連接庫。

而插件化動態加載部署這些內容。加載上面的內容就產生幾個技術問題:dex文件加載、資源文件加載、so文件加載部署、activity、service等android組件的動態註冊。

首先是dex文件加載:

public static DexClassLoader readDexFile(Context context, String apkPath, String outDexPath){
        DexClassLoader dexClassLoader=null;
        try {
            dexClassLoader=new DexClassLoader(apkPath, getOutDexpaPath(context, outDexPath), context.getApplicationInfo().nativeLibraryDir, context.getClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
            Log.i(tag,""+e.getMessage());
        }
        return dexClassLoader;
    }

夾在apk中dex就是經過DexClassLoader api來加載的,經過DexClassLoader就能夠調用apk中java代碼,接下來就是經過反射機制去替換app的ClassLoader,這一步步驟對android framework源碼依賴很是大,而後經過ClassLoader的雙親機制將主工程app的ClassLoader設置成父級ClassLoader,這樣加載dex步驟就完成了,具體實現技術篇幅太大之後有空會專門出一篇文章。

而後是加載apk中資源文件:

public static Resources readApkRes(Context context, String apkPath){
        Resources resources1=null;
        try {
            AssetManager assetManager=AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, apkPath);
            Resources resources = context.getResources();
            resources1 = new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
            Log.i(tag,""+e.getMessage());
        }
        return resources1;
    }

經過上面的方法能夠加載出資源內容,接下來也是經過反射替換app默認夾在的mResources對象,這樣資源加載完成。

上面比較核心的功能思路,因爲篇幅還有不少細節這裏不可以所有列出,好比so文件加載部署、activity、service等android組件的動態註冊都是很是依賴源碼的操做,都是要使用大量的反射來修改系統的成員變量等,因此其實插件化實施起來最大的困難就是適配機型的源碼,全部成本就在這裏。那麼咱們該不應用插件化?首先根據公司項目實際狀況認定,需不須要插件化提供的熱更新功能,若是須要能夠選擇插件化。接下來咱們來看看市面上插件化框架對比!

每一款框架都是本身優勢和缺點,可是彷佛都不可以知足全部功能,這裏我總結一下何時應該用到插件化,首先不是全部地方都是插件化而是局部使用,符合一下條件能夠考慮使用:

1、項目一些偏向ui界面具備更新要求快的模塊可使用,例如一些出銷頁面更新較快的地方。
2、activity爲主,大部分框架對activity支持較好。
3、對so等第三方庫依賴較少,so對插件化兼容性穩定考驗比較大。
4、沒有使用aop切面編程的代碼。

 

speed-tools插件化框架使用
這裏本身寫了一個對源碼侵入性小的插件化框架speed tools。

github: speed-tools
能夠的話能夠 star 一下 ^-^

首先看看項目結構:

lib_speed_tools: 插件化核心功能library
module_host_main:宿主工程主工程,負責加載部署apk
module_client_one:測試業務apk 1
module_client_two:測試業務apk 2
lib_img_utils:測試imageloader圖片框架

注意:須要使用speed tools 只須要依賴lib_speed_tools便可,而後開始配置插件化步驟:

首先在module_client_one中建立業務邏輯類:TestClass.java

/**
 *  by liyihang
 */
public class TestClass extends SpeedBaseInterfaceImp {

    private Activity activity;

    @Override
    public void onCreate(Bundle savedInstanceState, final Activity activity) {
        this.activity=activity;

        activity.setContentView(R.layout.activity_client_main);

        activity.findViewById(R.id.jump).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SpeedUtils.goActivity(activity,"first_apk", "two_class");
            }
        });

        ImageView imageView= (ImageView) activity.findViewById(R.id.img_view);
        imageView.setVisibility(View.VISIBLE);
        ImgUtils.getInstance(activity).showImg("http://img.my.csdn.net/uploads/201309/01/1378037235_3453.jpg", imageView);

    }
}

SpeedBaseInterfaceImp業務組件中業務activity代理類,他是實現了主要的生命週期方法,至關於組件的activity類。

而後建立hock類每一個業務組件中只建立一個:ClientMainActivity.java

public class ClientMainActivity extends SpeedClientBaseActivity {

    @Override
    public SpeedBaseInterface getProxyBase() {
        return new TestClass();
    }


}

這個類在組件中是惟一的,做用就是hock在獨立測試時候使用。

接下來配置配置組件的AndroidManifest.xml

<application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/SpeedTheme">

        <!--必須設置root_class-->
        <meta-data
            android:name="root_class"
            android:value="com.example.clientdome.TestClass" />

        <meta-data
            android:name="two_class"
            android:value="com.example.clientdome.TwoClass" />

        <activity
            android:name=".ClientMainActivity"
            android:theme="@style/SpeedTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

            <!--組件意圖-->
            <intent-filter>
                <data android:scheme="speed_tools" android:host="sijienet.com" android:path="/find_class"/>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
    </application>
--------------------- 
做者:一航jason 
來源:CSDN 
原文:https://blog.csdn.net/mhhyoucom/article/details/79000072 
版權聲明:本文爲博主原創文章,轉載請附上博文連接!
<application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/SpeedTheme">

        <!--必須設置root_class-->
        <meta-data
            android:name="root_class"
            android:value="com.example.clientdome.TestClass" />

        <meta-data
            android:name="two_class"
            android:value="com.example.clientdome.TwoClass" />

        <activity
            android:name=".ClientMainActivity"
            android:theme="@style/SpeedTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

            <!--組件意圖-->
            <intent-filter>
                <data android:scheme="speed_tools" android:host="sijienet.com" android:path="/find_class"/>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
    </application>

組件意圖寫死保持一直,root_class 是調用死後使用對於配置的com.example.clientdome.TestClass業務類。這樣業務組件配置完成。

接下來配置宿主工程module_host_main;
建立宿主工程惟一hock類:ApkActivity.java

/**
 *  by liyihang
 */
public class ApkActivity extends SpeedHostBaseActivity {


    @Override
    public String getApkKeyName() {
        return HostMainActivity.FIRST_APK_KEY;
    }

    @Override
    public String getClassTag() {
        return null;
    }
}

整個宿主工程建立一個類便可,用戶是hock activity;而後建立一個開屏頁apk第一次加載時候須要一些時間,放入開屏等待頁面是很是合適的。

HostMainActivity.java

/**
 *  by liyihang
 */
public class HostMainActivity extends AppCompatActivity implements Runnable,Handler.Callback, View.OnClickListener {


    public static final String FIRST_APK_KEY="first_apk";
    public static final String TWO_APK_KEY="other_apk";

    private Handler handler;

    private TextView showFont;
    private ProgressBar progressBar;
    private Button openOneApk;
    private Button openTwoApk;

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

        showFont= (TextView) findViewById(R.id.show_font);
        progressBar= (ProgressBar) findViewById(R.id.progressbar);
        openOneApk= (Button) findViewById(R.id.open_one_apk);
        openTwoApk= (Button) findViewById(R.id.open_two_apk);

        handler=new Handler(this);
        new Thread(this).start();
    }

    @Override
    public void run() {
        String s = "module_client_one-release.apk";
        String dexOutPath="dex_output2";
        File nativeApkPath = SpeedUtils.getNativeApkPath(getApplicationContext(), s);
        SpeedApkManager.getInstance().loadApk(FIRST_APK_KEY, nativeApkPath.getAbsolutePath(), dexOutPath, this);

        String s2 = "module_client_two-release.apk";
        String dexOutPath2="dex_output3";
        File nativeApkPath1 = SpeedUtils.getNativeApkPath(getApplicationContext(), s2);
        SpeedApkManager.getInstance().loadApk(TWO_APK_KEY, nativeApkPath1.getAbsolutePath(), dexOutPath2, this);

        handler.sendEmptyMessage(0x78);
    }

    @Override
    public boolean handleMessage(Message message) {
        showFont.setText("當前是主宿主apk\n插件apk完畢");
        progressBar.setVisibility(View.GONE);
        openOneApk.setVisibility(View.VISIBLE);
        openTwoApk.setVisibility(View.VISIBLE);
        openOneApk.setOnClickListener(this);
        openTwoApk.setOnClickListener(this);
        return false;
    }

    @Override
    public void onClick(View v) {
        if (v.getId()==R.id.open_one_apk)
        {
            SpeedUtils.goActivity(this, FIRST_APK_KEY, null);
        }
        if (v.getId()==R.id.open_two_apk)
        {
            SpeedUtils.goActivity(this, TWO_APK_KEY, null);
        }
    }
}

加載apk核心代碼是:

     String s = "module_client_one-release.apk";
        String dexOutPath="dex_output2";
        File nativeApkPath = SpeedUtils.getNativeApkPath(getApplicationContext(), s);
        SpeedApkManager.getInstance().loadApk(FIRST_APK_KEY, nativeApkPath.getAbsolutePath(), dexOutPath, this

業務apk都是放在assets目錄中。最後配置AndroidManifest.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.hostproject">


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


    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/SpeedTheme">

        <!--啓動activity 加載apk-->
        <activity android:name=".HostMainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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


        <!--組件hack-->
        <activity
            android:name=".ApkActivity"
            android:label="@string/app_name"
            android:theme="@style/SpeedTheme" >
            <intent-filter>
                <data android:scheme="speed_tools" android:host="jason.com" android:path="/find_class"/>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

這樣全部配置結束,插件化實現。

總結:
一、插件化在項目中仍是加入不少機制,若是主工程下載更新機制,配合後臺區分用戶版本發佈可以支持的業務插件等,這些東西加起來是個龐大的工程;

二、如今市面上不少成熟的插件框架都android framework依賴仍是很重,但也有侵入性小的框架,例如speed tools。

三、插件架構不是全局使用就好,而是根據本身的須要結合實際需求來使用,常更新有不知足html5提供用戶體驗的狀況比較合適。

上面是對項目中插件化開發積累的經驗,若是有建議能夠給我留言。

相關文章
相關標籤/搜索