BruceFan's Blog

Stay hungry, stay foolish

0%

Android动态加载之插件开发

动态加载可以用来进行插件开发,这些插件大概都是为了在一个主程序中实现比较通用的功能,使主程序具有可扩展性。实现原理是实现一套插件接口,把插件实现编成apk或dex,在运行时用DexClassLoader动态加载进来。
预备知识:Android中的动态加载机制

插件演示

这里用了三个项目:

  • PInterface:插件接口项目(只是接口的定义)
  • PImplement:插件项目(实现插件接口,定义具体的功能)
  • HostProject:宿主项目(需要引用插件接口项目,然后动态加载插件项目)

项目介绍

下面看一下项目源代码:
1.PInterface项目

  1. IBean.java

    1
    2
    3
    4
    5
    6
    package com.pluginsdk.interfaces;

    public abstract interface IBean{
    public abstract String getName();
    public abstract void setName(String paramString);
    }
  2. IDynamic.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.pluginsdk.interfaces;

    import android.content.Context;

    public abstract interface IDynamic{
    public abstract void methodWithCallBack(YKCallBack paramYKCallBack);
    public abstract void showPluginWindow(Context paramContext);
    public abstract void startPluginActivity(Context context,Class<?> cls);
    public abstract String getStringForResId(Context context);
    }

    没有太多可说的,后面的代码可以下载项目之后自己看。

2.PImplement项目

  1. Dynamic.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    package com.pluginsdk.pimplement;

    import android.app.AlertDialog;
    import android.app.AlertDialog.Builder;
    import android.app.Dialog;
    import android.content.Context;
    import android.content.DialogInterface;
    import android.content.Intent;

    import com.pluginsdk.R;
    import com.pluginsdk.bean.Bean;
    import com.pluginsdk.interfaces.IDynamic;
    import com.pluginsdk.interfaces.YKCallBack;

    public class Dynamic implements IDynamic{

    public void methodWithCallBack(YKCallBack callback) {
    Bean bean = new Bean();
    bean.setName("PLUGIN_SDK_USER");
    callback.callback(bean);
    }

    public void showPluginWindow(Context context) {
    AlertDialog.Builder builder = new Builder(context);
    builder.setMessage("对话框");
    builder.setTitle(R.string.hello_world);
    builder.setNegativeButton("取消", new Dialog.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
    dialog.dismiss();
    }
    });
    Dialog dialog = builder.create();//.show();
    dialog.show();
    }

    public void startPluginActivity(Context context, Class<?> cls){
    /*
    * 这里要注意几点:
    * 1、如果单纯的写一个MainActivity的话,在主项目中也有一个MainActivity,开启的Activity还是主项目中的MainActivity
    * 2、如果这里将MainActivity写成全名的话,还是有问题,会报找不到这个Activity的错误
    */
    Intent intent = new Intent(context, cls);
    context.startActivity(intent);
    }

    public String getStringForResId(Context context){
    return context.getResources().getString(R.string.hello_world);
    }

    }
  2. Bean.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.pluginsdk.bean;


    public class Bean implements com.pluginsdk.interfaces.IBean{
    private String name = "这是来自于插件项目中设置的初始化的名字";

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    }

3.宿主项目HostProject

  1. MainActivity.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    package com.plugindemo;
    import java.io.File;
    import java.lang.reflect.Method;

    import android.annotation.SuppressLint;
    import android.app.Activity;
    import android.content.Context;
    import android.content.res.AssetManager;
    import android.content.res.Resources;
    import android.content.res.Resources.Theme;
    import android.os.Bundle;
    import android.os.Environment;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.ListView;
    import android.widget.Toast;

    import com.pluginsdk.interfaces.IBean;
    import com.pluginsdk.interfaces.IDynamic;
    import com.pluginsdk.interfaces.YKCallBack;

    import dalvik.system.DexClassLoader;

    public class MainActivity extends Activity {
    private AssetManager mAssetManager;//资源管理器
    private Resources mResources;//资源
    private Theme mTheme;//主题
    private String apkFileName = "Pimplement.apk";
    private String dexpath = null;//apk文件地址
    private File fileRelease = null; //释放目录
    private DexClassLoader classLoader = null;
    @SuppressLint("NewApi")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    dexpath = getApplicationContext().getFilesDir().getAbsolutePath() + File.separator + apkFileName;
    fileRelease = getDir("dex", 0);

    /*初始化classloader
    * dexpath dex文件地址
    * fileRelease 文件释放地址
    * 父classLoader
    */
    classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(), null, getClassLoader());

    Button btn_1 = (Button)findViewById(R.id.btn_1);
    Button btn_2 = (Button)findViewById(R.id.btn_2);
    Button btn_3 = (Button)findViewById(R.id.btn_3);
    Button btn_4 = (Button)findViewById(R.id.btn_4);
    Button btn_5 = (Button)findViewById(R.id.btn_5);
    Button btn_6 = (Button)findViewById(R.id.btn_6);

    btn_1.setOnClickListener(new View.OnClickListener() {//普通调用 反射的方式
    @Override
    public void onClick(View v) {
    Class mLoadClassBean;
    try {
    mLoadClassBean = classLoader.loadClass("com.pluginsdk.bean.Bean");
    Object beanObject = mLoadClassBean.newInstance();
    Log.d("DEMO", "ClassLoader:" + mLoadClassBean.getClassLoader());
    Log.d("DEMO", "ClassLoader:" + mLoadClassBean.getClassLoader().getParent());
    Method getNameMethod = mLoadClassBean.getMethod("getName");
    getNameMethod.setAccessible(true);
    String name = (String) getNameMethod.invoke(beanObject);
    Toast.makeText(MainActivity.this, name, Toast.LENGTH_SHORT).show();
    } catch (Exception e) {
    Log.e("DEMO", "msg:"+e.getMessage());
    }
    }
    });
    btn_2.setOnClickListener(new View.OnClickListener() {//带参数调用
    @Override
    public void onClick(View arg0) {
    Class mLoadClassBean;
    try {
    mLoadClassBean = classLoader.loadClass("com.pluginsdk.bean.Bean");
    Object beanObject = mLoadClassBean.newInstance();
    //接口形式调用
    Log.d("DEMO", beanObject.getClass().getClassLoader()+"");
    Log.d("DEMO",IBean.class.getClassLoader()+"");
    Log.d("DEMO",ClassLoader.getSystemClassLoader()+"");
    IBean bean = (IBean)beanObject;
    bean.setName("宿主程序设置的新名字");
    Toast.makeText(MainActivity.this, bean.getName(), Toast.LENGTH_SHORT).show();
    }catch (Exception e) {
    Log.e("DEMO", "msg:"+e.getMessage());
    }

    }
    });
    btn_3.setOnClickListener(new View.OnClickListener() {//带回调函数的调用
    @Override
    public void onClick(View arg0) {
    Class mLoadClassDynamic;
    try {
    mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.pimplement.Dynamic");
    Object dynamicObject = mLoadClassDynamic.newInstance();
    //接口形式调用
    IDynamic dynamic = (IDynamic)dynamicObject;
    //回调函数调用
    YKCallBack callback = new YKCallBack() {//回调接口的定义
    public void callback(IBean arg0) {
    Toast.makeText(MainActivity.this, arg0.getName(), Toast.LENGTH_SHORT).show();
    };
    };
    dynamic.methodWithCallBack(callback);
    } catch (Exception e) {
    Log.e("DEMO", "msg:"+e.getMessage());
    }

    }
    });
    btn_4.setOnClickListener(new View.OnClickListener() {//带资源文件的调用
    @Override
    public void onClick(View arg0) {
    loadResources();
    Class mLoadClassDynamic;
    try {
    mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.pimplement.Dynamic");
    Object dynamicObject = mLoadClassDynamic.newInstance();
    //接口形式调用
    IDynamic dynamic = (IDynamic)dynamicObject;
    dynamic.showPluginWindow(MainActivity.this);
    } catch (Exception e) {
    Log.e("DEMO", "msg:"+e.getMessage());
    }
    }
    });
    btn_5.setOnClickListener(new View.OnClickListener() {//带资源文件的调用
    @Override
    public void onClick(View arg0) {
    loadResources();
    Class mLoadClassDynamic;
    try {
    mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.pimplement.Dynamic");
    Object dynamicObject = mLoadClassDynamic.newInstance();
    //接口形式调用
    IDynamic dynamic = (IDynamic)dynamicObject;
    dynamic.startPluginActivity(MainActivity.this,
    classLoader.loadClass("com.plugindemo.MainActivity"));
    } catch (Exception e) {
    Log.e("DEMO", "msg:"+e.getMessage());
    }
    }
    });
    btn_6.setOnClickListener(new View.OnClickListener() {//带资源文件的调用
    @Override
    public void onClick(View arg0) {
    loadResources();
    Class mLoadClassDynamic;
    try {
    mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.pimplement.Dynamic");
    Object dynamicObject = mLoadClassDynamic.newInstance();
    //接口形式调用
    IDynamic dynamic = (IDynamic)dynamicObject;
    String content = dynamic.getStringForResId(MainActivity.this);
    Toast.makeText(getApplicationContext(), content+"", Toast.LENGTH_LONG).show();
    } catch (Exception e) {
    Log.e("DEMO", "msg:"+e.getMessage());
    }
    }
    });

    }

    protected void loadResources() {
    try {
    AssetManager assetManager = AssetManager.class.newInstance();
    Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
    addAssetPath.invoke(assetManager, dexpath);
    mAssetManager = assetManager;
    } catch (Exception e) {
    e.printStackTrace();
    }
    Resources superRes = super.getResources();
    superRes.getDisplayMetrics();
    superRes.getConfiguration();
    mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());
    mTheme = mResources.newTheme();
    mTheme.setTo(super.getTheme());
    }

    @Override
    public AssetManager getAssets() {
    return mAssetManager == null ? super.getAssets() : mAssetManager;
    }

    @Override
    public Resources getResources() {
    return mResources == null ? super.getResources() : mResources;
    }

    @Override
    public Theme getTheme() {
    return mTheme == null ? super.getTheme() : mTheme;
    }
    }

项目引用关系

1.将接口项目PInterface设置成一个Library,

并导出项目为一个jar。

2.插件项目PImplement引用接口项目的jar
注意是lib文件夹,而不是libs,在Android中的动态加载机制
中说过这样做的原因:插件项目打包不能集成接口jar,宿主项目打包一定要集成接口jar

3.HostProject项目引用PInterface这个Library

项目引用完成后,我们编译PImplement项目,生成PImplement.apk放到/data/data/com.plugindemo/files,因为代码中是从这个目录进行加载的,这个目录是可以修改的。运行HostProject

运行成功,这个对话框其实是在插件中定义的。
项目下载
reference
http://blog.csdn.net/jiangwei0910410003/article/details/41384667