1. 程式人生 > >Android中的動態載入(簡單實現)

Android中的動態載入(簡單實現)

先說明一點,這篇文章說的動態載入,只能載入dex檔案中的功能,涉及到資源的就不可以了。

動態載入步驟

    1、在宿主程式中寫外掛介面


2、在外掛中實現宿主程式的介面


這裡要注意,外掛介面的包名要和宿主程式中的一樣。

3、將外掛打包成dex檔案,注意不能講宿主介面打包,否則在呼叫時會出錯。

打包這裡先將實現類打成jar包,不會在Android studio上打jar包的看這篇文章:http://blog.csdn.net/u013174702/article/details/52251540

我在gradle中是這麼寫的

//打包任務
task makeJar(type:org.gradle.api.tasks.bundling.Jar) {
    //指定生成的jar名
    baseName 'dynamic'
    //從哪裡打包class檔案
    from('build/intermediates/classes/debug/test/halewang/com/dynamicloadtest')
    //打包到jar後的目錄結構
    into('test/halewang/com/dynamicloadtest/')
    //去掉不需要打包的目錄和檔案
    exclude('dynamic/', 'BuildConfig.class', 'R.class',"MainActivity.class")
    //去掉R$開頭的檔案
    exclude{ it.name.startsWith('R$');}
}


然後需要把生成的jar包轉化成含dex檔案的jar包


最後生成的test.jar就在F:\SDK\build-tools\23.0.2的目錄下

4、寫配置檔案xxx.prop. 然後將配置檔案放在含dex檔案的jar包內,與dex檔案在同一目錄下

這是配置檔案

然後將test.jar改成test.zip,然後直接將寫好的res.prop檔案拖進去,再把test.zip改成test.jar


然後將test.jar推送到目標機的sdcard/dex/目錄下, 寫配置檔案的目的是獲取入口類的全名稱和一些外掛的設定。

5、將打包好的jar包推送到目標機上的指定路徑,然後在宿主程式中載入。

在宿主程式的activity中

/**
     *列出外掛列表
     */
    private void listMyPlugin(){

        ViewGroup root = mList;
        root.removeAllViews();

        File dexFiles =  new File(Environment.getExternalStorageDirectory().toString() + File.separator + "dex");
        File[] dexes = dexFiles.listFiles();
        if(dexes != null) {
            for (File file : dexes) {
                String dexPath = Environment.getExternalStorageDirectory().toString()
                        + File.separator + "dex" + File.separator + file.getName();
                PropBean propBean = null;
                if(pluginManager != null){
                    propBean = pluginManager.getProp(getApplicationContext(),dexPath);
                }
                if(propBean != null){
                    switch (propBean.getPluginType()){
                        case 0:
                            //代表外掛型別為一個按鈕
                            createButton(root, propBean.getDesc(),dexPath);

                            break;
                        case 1:
                            //代表外掛型別為滑動按鈕
                            createLayout(root,dexPath,propBean);
                            break;
                        default:
                            break;
                    }
                }
            }
        }else{
            Toast.makeText(PluginLoadActivity.this,"沒有任何外掛",Toast.LENGTH_SHORT).show();
        }
    }

    //建立一個按鈕
    private void createButton(ViewGroup root, String desc, final String dexPath){
        Button button = new Button(this);
        button.setPadding(10, 25, 10, 25);
        LinearLayout.LayoutParams layoutParam = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        layoutParam.topMargin = 25;
        layoutParam.bottomMargin = 25;
        layoutParam.gravity = Gravity.CENTER;
        root.addView(button, layoutParam);
        button.setText(desc);
        button.setBackgroundResource(R.drawable.btn_send_selector);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                pluginManager.loadDex(PluginLoadActivity.this, dexPath);
            }
        });
    }

Pluginmanager.java
package com.tencent.wecarbugreport.plugin;

import android.content.Context;
import android.util.Log;

import com.tencent.wecarbugreport.myinterface.IPlugin;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import dalvik.system.DexClassLoader;

/**
 * Created by halewang on 2016/8/22.
 */
public class PluginManager {

    /**
     * 配置檔名稱
     */
    private static final String RES_NAME = "res.prop";
    /**
     * 入口類全名稱
     */
    private static final String ENTER_CLASS = "enterClass";
    /**
     * 外掛型別
     * 0代表普通按鈕
     * 1代表滑動按鈕
     */
    private static final String PLUGIN_TYPE = "pluginType";
    /**
     * 外掛描述
     */
    private static final String DESC = "desc";
    /**
     * 滑動開關狀態
     */
    private static final String SLIDE_STATUS = "slideStatus";


    private static final PluginManager mPluginManager =
            new PluginManager();

    /**
     * 儲存外掛,在以後呼叫時,如果存在直接接呼叫方法而不重新建立
     */
    private static Map<String,IPlugin> mPlugins = new HashMap<String,IPlugin>();
    /**
     * 儲存外掛配置
     */
    private static Map<String,PropBean> mProps = new HashMap<String,PropBean>();

    /**
     * 外掛檔案全路徑名
     */
    private static List<String> mPaths = new ArrayList<String>();

    private PluginManager(){

    }

    public static PluginManager getInstance(){
        return mPluginManager;
    }

    public void addPlugin(String pluginName, IPlugin mPlugin){
        mPlugins.put(pluginName, mPlugin);
    }

    public void removePlugin(String pluginName){
        mPlugins.get(pluginName).onPluginDestroy();
        mPlugins.remove(pluginName);
    }

    public IPlugin getPlugin(String pluginName){
        IPlugin plugin;
        plugin = mPlugins.get(pluginName);
        return plugin;
    }

    //載入外掛功能
    public void loadDex(Context context, String dexPath){
        if(getPlugin(dexPath) != null){
            PropBean propBean = mProps.get(dexPath);
            switch (propBean.getPluginType()) {
                case 0:
                    getPlugin(dexPath).callMethod(context);
                    break;
                case 1:
                    if(propBean.getSlideStatus().equals("0")){
                        getPlugin(dexPath).callMethod(context,true);
                        propBean.setSlideStatus("1");
                    }else{
                        getPlugin(dexPath).callMethod(context,false);
                        propBean.setSlideStatus("0");
                    }
            }
        }else {

            File dexOutputDir = context.getDir("dex1", 0);

            DexClassLoader loader = new DexClassLoader(dexPath,
                    dexOutputDir.getAbsolutePath(),
                    null, context.getClassLoader());
            try {

                Class clz = loader.loadClass(getClassName(loader));
                IPlugin impl = (IPlugin) clz.newInstance();
                PropBean propBean = mProps.get(dexPath);
                switch (propBean.getPluginType()) {
                    case 0:
                        impl.callMethod(context);
                        break;
                    case 1:
                        if(propBean.getSlideStatus().equals("0")){
                            impl.callMethod(context,true);
                            propBean.setSlideStatus("1");
                        }else{
                            impl.callMethod(context,false);
                            propBean.setSlideStatus("0");
                        }
                }
                addPlugin(dexPath, impl);
            } catch (Exception e) {
                Log.e("PluginManager", "error happened", e);
            }
        }
    }

    /**
     * 獲取類全名
     * @param loader
     * @return
     */
    public String getClassName(DexClassLoader loader){
        String className = null;
        InputStream is = loader.getResourceAsStream(RES_NAME);
        Properties pp = new Properties();
        try {
            pp.load(is);
            if(!pp.isEmpty()) {
                className = pp.getProperty(ENTER_CLASS);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                is.close();
                pp.clear();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return className;
    }

    /**
     * 獲取配置實體
     * @param dexPath
     * @return
     */
    public PropBean getProp(Context context, String dexPath){

        if(mProps.get(dexPath) != null) {
            return mProps.get(dexPath);
        }else {
            //dex檔案解壓目錄
            File dexOutputDir = context.getDir("dex1", 0);
            //得到類載入器
            DexClassLoader loader = new DexClassLoader(dexPath,
                    dexOutputDir.getAbsolutePath(),
                    null, context.getClassLoader());
            PropBean propBean = new PropBean();
            InputStream is = loader.getResourceAsStream(RES_NAME);
            Properties pp = new Properties();
            try {
                pp.load(is);
                if (!pp.isEmpty()) {
                    propBean.setEnterClass(pp.getProperty(ENTER_CLASS));

                    propBean.setPluginType(Integer.parseInt(pp.getProperty(PLUGIN_TYPE)));
                    propBean.setDesc(pp.getProperty(DESC));
                    propBean.setSlideStatus(pp.getProperty(SLIDE_STATUS));
                    Log.d("propStatus","狀態是--------------"+propBean.getSlideStatus());
                    Log.d("propStatus","描述是--------------"+propBean.getDesc());

                    mProps.put(dexPath, propBean);
                    mPaths.add(dexPath);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    is.close();
                    pp.clear();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return propBean;
        }
    }

    public void removeAllPlugin(){
        for(String path : mPaths){
            IPlugin plugin = mPlugins.get(path);
            if(plugin != null) {
                plugin.onPluginDestroy();
                mPlugins.remove(path);
            }
        }
        mProps.clear();
        Log.d("propStatus","配置檔案個數"+mProps.size());
    }
}

這裡就大致實現了動態載入的功能,不過博主水平有限,這種方式對外掛編寫的要求很多,還不支援資源載入。並且耦合度高,一旦修改或新增某些功能就要全都修改,並且現在網上有很多動態載入的框架,功能要強大很多,有興趣的可以自行了解。

推薦一個公眾號,不吐槽,不毒舌,偶爾發發文章,偶爾推薦好物,歡迎關注或者有女票的程式狗們推薦給女票