先说明一点,这篇文章说的动态加载,只能加载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());
}
}
这里就大致实现了动态加载的功能,不过博主水平有限,这种方式对插件编写的要求很多,还不支持资源加载。并且耦合度高,一旦修改或添加某些功能就要全都修改,并且现在网上有很多动态加载的框架,功能要强大很多,有兴趣的可以自行了解。