Android-插件化一插樁實(shí)現(xiàn)Activity的加載

1.插件化定義

插件化,就是把一些核心復(fù)雜依賴度高的業(yè)務(wù)模塊封裝成插件,然后根據(jù)不同的業(yè)務(wù)進(jìn)行不同的組合,動(dòng)態(tài)進(jìn)行替換。

2.插件化跟組件化的差異

組件化:是將一個(gè)app分成多個(gè)模塊,每個(gè)模塊都是一個(gè)組件,開發(fā)過程中我們可以讓這些組件相互依賴或者單獨(dú)調(diào)試部分組件,但是最終發(fā)布的時(shí)候是將這些組件合并成一個(gè)統(tǒng)一的apk,這就是組件化。

插件化:跟組件化不同,插件化開發(fā)就是將整個(gè)app拆分成很多模塊,每個(gè)模塊都是apk(組件化每個(gè)模塊是一個(gè)lib),最終發(fā)布的時(shí)候宿主和插件apk分開打包,插件apk通過動(dòng)態(tài)下發(fā)的方式發(fā)送到宿主apk,這就是插件化。

3.插件化的優(yōu)勢(shì)

  • 插件和宿主分開編譯,減少宿主apk的編譯時(shí)間
  • 并發(fā)開發(fā),宿主和插件apk研發(fā)過程中可以獨(dú)立進(jìn)行,互不影響。
  • 動(dòng)態(tài)更新插件,不需要安裝,下載之后就可以打開
  • 可以解決65535問題

4.下面介紹插樁式插件化研發(fā)的流程

  • 設(shè)計(jì)一套標(biāo)準(zhǔn),讓插件app能夠像安裝的app一樣運(yùn)行起來
  • 根據(jù)標(biāo)準(zhǔn)編寫插件apk
  • 加載插件apk
  • 添加插樁點(diǎn)

編寫插件標(biāo)準(zhǔn),這個(gè)編寫一個(gè)獨(dú)立的model中,方便管理

public interface MSInterfaceActivity {

    public void attach(Activity proxyActivity);
    /**
     * 生命周期
     * @param savedInstanceState
     */
    public void onCreate(Bundle savedInstanceState);
    public void onStart();
    public void onResume();
    public void onPause();
    public void onStop();
    public void onDestroy();
    public void onSaveInstanceState(Bundle outState);
    public boolean onTouchEvent(MotionEvent event);
    public void onBackPressed();

}

根據(jù)這個(gè)標(biāo)準(zhǔn)編寫插件apk應(yīng)用的代碼,這個(gè)里邊包含兩個(gè)java文件一個(gè)是BaseActivity,一個(gè)是MainActivity

class BaseActivity extends AppCompatActivity implements MSInterfaceActivity {

    protected  Activity that;

    @Override
    public void attach(Activity proxyActivity) {
        that =proxyActivity;
    }


    @Override
    public void setContentView(View view) {
        if (that!=null){
            that.setContentView(view);
        }else{
            super.setContentView(view);
        }
    }



    @Override
    public void setContentView(int layoutResID) {
        that.setContentView(layoutResID);
    }


    public View findViewById(int id){
        return that.findViewById(id);
    }


    @Override
    public Intent getIntent() {
        if(that!=null){
            return that.getIntent();
        }
        return super.getIntent();
    }


    @Override
    public ClassLoader getClassLoader() {
        return that.getClassLoader();
    }


    @Override
    public void startActivity(Intent intent) {
        Intent newIntent = new Intent();
        newIntent.setClassName("className",intent.getComponent().getClassName());
        that.startActivity(newIntent);
    }


    @Override
    public void onCreate(Bundle savedInstanceState) {

    }

    @Override
    public void onStart() {

    }

    @Override
    public void onResume() {

    }

    @Override
    public void onPause() {

    }

    @Override
    public void onStop() {

    }

    @Override
    public void onDestroy() {

    }

    @Override
    public void onSaveInstanceState(Bundle outState) {

    }
}

插件的主頁面添加了一個(gè)ImageView,給控件設(shè)置了一個(gè)圖片并添加了一個(gè)點(diǎn)擊事件,點(diǎn)擊之后響應(yīng)Toast提示。

public class MainActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        View viewById = findViewById(R.id.image);
        viewById.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(that,"風(fēng)景",Toast.LENGTH_SHORT).show();
            }
        });
    }
}

下面是宿主apk的內(nèi)容:

這里將插件apk放置在assets里邊,來模擬從網(wǎng)絡(luò)上下載下來的邏輯

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

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

添加文件的讀寫權(quán)限

public class PluginLoadManager {

    private static final String TAG= PluginLoadManager.class.getSimpleName();

    private static final PluginLoadManager ourInstance = new PluginLoadManager();

    public static PluginLoadManager getInstance() {
        return ourInstance;
    }


    private PackageInfo packageInfo;
    private Resources resources;
    //dex 類加載器
    private DexClassLoader dexClassLoader;

    private PluginLoadManager() {
    }

    /**
     * 加載插件
     * 加載插件的目的就是要得到Resource對(duì)象以及dexClassLoader
     * @param context  上下文
     * @param pluginPath plugin文件的絕對(duì)路徑
     */
    public void loadPath(Context context,String pluginPath) {
       // File filesDir = context.getDir("plugin", Context.MODE_PRIVATE);
        Log.d(TAG,"pluginPath="+pluginPath);

        PackageManager packageManager = context.getPackageManager();
        //得到安裝包的信息
        packageInfo = packageManager.getPackageArchiveInfo(pluginPath, PackageManager.GET_ACTIVITIES);

        // activity 名字
        File dexOutFile = context.getDir("dex", Context.MODE_PRIVATE);

        //第一個(gè)參數(shù)是dex的路徑,第二個(gè)是優(yōu)化路徑    最后一個(gè)參數(shù)是父類加載器
        dexClassLoader = new DexClassLoader(pluginPath,
                dexOutFile.getAbsolutePath(), null, context.getClassLoader());

        try {
            //反射得到AssetManager
            AssetManager assetManager = AssetManager.class.newInstance();
            //方法名和參數(shù)類型  addAssetPath官方已經(jīng)不建議使用這個(gè)方法了,推薦使用setApkAssets
            Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);

            addAssetPath.invoke(assetManager,pluginPath);    //方法在assetManager上面 需要傳遞一個(gè)路徑當(dāng)做參數(shù)

            //創(chuàng)建一個(gè)Resource 第一個(gè)參數(shù)就是AssetManager  但是AssetManager的實(shí)例化被隱藏了,所以只能通過反射的方式得到
            resources = new Resources(assetManager,
                    context.getResources().getDisplayMetrics(),
                    context.getResources().getConfiguration());

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    public Resources getResources() {
        return resources;
    }

    public DexClassLoader getDexClassLoader() {
        return dexClassLoader;
    }


    public PackageInfo getPackageInfo() {
        return packageInfo;
    }
}

增加插件加載管理類,這個(gè)里邊的代碼邏輯我做了注釋,加載的過程中插件apk

  • 通過getPackageArchiveInfo這個(gè)方法得到了安裝包的信息
  • 通過DexClassLoader加載了插件apk的源碼,并對(duì)實(shí)例化DexClassLoader的參數(shù)做了介紹
  • 通過反射得到了AssetManager,反射這個(gè)的目的是為了實(shí)例化Resources
public class MainActivity extends AppCompatActivity {

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

    //申請(qǐng)文件讀寫權(quán)限
    private void requestPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                Log.i(TAG,"用戶申請(qǐng)過權(quán)限,但是被拒絕了(不是徹底決絕)");
//               申請(qǐng)權(quán)限
                ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.READ_EXTERNAL_STORAGE},1);

            } else {
                Log.i(TAG,"申請(qǐng)過權(quán)限,但是被用戶徹底決絕了或是手機(jī)不允許有此權(quán)限(依然可以在此再申請(qǐng)權(quán)限)");
                ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.READ_EXTERNAL_STORAGE},1);
            }
        }
    }

    // 拷貝到
    public void load(View view){
        loadPlugin();
    }

    private void loadPlugin() {
        File fileDir=this.getDir("plugin", Context.MODE_PRIVATE);
        String name="plugindemo.apk";
        String filePath=new File(fileDir, name).getAbsolutePath();
        Log.d(TAG,filePath);
        File file = new File(filePath);
        if (file.exists()) {
            file.delete();
        }
        InputStream is = null;
        FileOutputStream os = null;
        try {
            //從assets里邊加載插件apk,模擬網(wǎng)絡(luò)下載
            is = getAssets().open("plugindemo-debug.apk");
            os = new FileOutputStream(filePath);
            int len = 0;
            byte[] buffer = new byte[1024];
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
            File f = new File(filePath);
            if (f.exists()) {
                Toast.makeText(this, "插件下載完成", Toast.LENGTH_SHORT).show();
            }
            PluginLoadManager.getInstance().loadPath(this,filePath);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                os.close();
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    
    //頁面跳轉(zhuǎn)
    public void click(View view) {
        Intent intent=new Intent(this,ProxyActivity.class);
        intent.putExtra("className", PluginLoadManager.getInstance().getPackageInfo().activities[0].name);
        startActivity(intent);
    }
}

頁面里邊兩個(gè)按鈕,一個(gè)是加載插件一個(gè)是跳轉(zhuǎn)按鈕,頁面跳轉(zhuǎn)的部分通過className字段將真正需要加載的插件頁面全名通過參數(shù)進(jìn)行傳遞到插件頁面。

/**
 * 代理Activity,這個(gè)Activity只是一個(gè)殼,插裝式插件化實(shí)現(xiàn)
 */
public class ProxyActivity extends AppCompatActivity {

    //需要加載的類名
    private String className;
    private MSInterfaceActivity msInterfaceActivity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        className = getIntent().getStringExtra("className");
        try {
            //得到Activity的Class
            Class<?> activityClass = getClassLoader().loadClass(className);
            //得到構(gòu)造方法
            Constructor<?> constructor = activityClass.getConstructor(new Class[]{});
            //得到這個(gè)類的對(duì)象
            Object object = constructor.newInstance();

            if (object instanceof MSInterfaceActivity){
                msInterfaceActivity= (MSInterfaceActivity) object;
            }

            if (msInterfaceActivity!=null){
                msInterfaceActivity.attach(this);
                Bundle bundle=new Bundle();
                msInterfaceActivity.onCreate(bundle);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");
        Intent newIntent=new Intent(this,ProxyActivity.class);
        newIntent.setClassName("className",className);
        super.startActivity(newIntent);
    }

    @Override
    public ClassLoader getClassLoader() {
        return PluginLoadManager.getInstance().getDexClassLoader();
    }

    @Override
    public Resources getResources() {
        return PluginLoadManager.getInstance().getResources();
    }

    @Override
    protected void onStart() {
        super.onStart();
        msInterfaceActivity.onStart();
    }

    @Override
    protected void onResume() {
        super.onResume();
        msInterfaceActivity.onResume();
    }


    @Override
    protected void onPause() {
        super.onPause();
        msInterfaceActivity.onPause();
    }

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


}

這個(gè)是專門用來加載插件頁面的,通過解析得到的需要跳轉(zhuǎn)的className通過反射得到這個(gè)頁面的對(duì)象,并注入context以及調(diào)用onCreate方法。

運(yùn)行效果完美實(shí)現(xiàn),需要源碼學(xué)習(xí)留下你的郵箱。

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

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

  • 超級(jí)干貨:國(guó)內(nèi)八大頂級(jí)游資的操盤筆記!寶貴的經(jīng)驗(yàn),謹(jǐn)記于心 炒gu手訓(xùn)練軟件 炒gu手訓(xùn)練軟件 一、Asking ...
    baaacf66a46d閱讀 262評(píng)論 0 0
  • 推薦了transformer的文章,不愧是機(jī)器學(xué)習(xí)中的最新結(jié)構(gòu),看起來太復(fù)雜了。 但是,通過信息流的模型,可以大大...
    Obj_Arr閱讀 340評(píng)論 0 1
  • 鄰居一臺(tái)聯(lián)想E41-55上網(wǎng)本故障,Win11進(jìn)桌面后就卡死不動(dòng),鼠標(biāo)都非??ǎ怀霈F(xiàn)任務(wù)欄,按任何按鍵無反應(yīng),多...
    硅谷少年閱讀 3,751評(píng)論 0 0
  • 高防服務(wù)器網(wǎng)站搭建專用死扛CC攻擊有效處理高并發(fā) 福州,福州,福州,常州,高防服務(wù)器,大帶寬資源 淺談Tomcat...
    qzlqzl閱讀 201評(píng)論 0 0
  • 大家好,本周給大家分享的是最近發(fā)表在PNAS上與代謝組學(xué)選擇增強(qiáng)水果風(fēng)味相關(guān)的一篇文章。 文章題目:Metabol...
    楊博士聊生信閱讀 1,916評(píng)論 0 10

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