Android中的动态加载(简单实现)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/u013174702/article/details/52312561

先说明一点,这篇文章说的动态加载,只能加载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());
    }
}

这里就大致实现了动态加载的功能,不过博主水平有限,这种方式对插件编写的要求很多,还不支持资源加载。并且耦合度高,一旦修改或添加某些功能就要全都修改,并且现在网上有很多动态加载的框架,功能要强大很多,有兴趣的可以自行了解。

猜你喜欢

转载自blog.csdn.net/u013174702/article/details/52312561
今日推荐