android 动态加载记录

     由于项目需要一个jar包,但是这个jar包比较大,有几百kb(项目的优点之一就是安装包体积小),综合考虑后决定采用网络下载后动态加载jar包。于是我用周六日两天研究了一下这个技术,记录下来,以备以后查阅。

     关于动态加载,理论上可以用到的有DexClassLoader、PathClassLoader和URLClassLoader。
     DexClassLoader :可以加载文件系统上的jar、dex、apk。jar和apk能被加载也是因为里面包含dex的格式。
     PathClassLoader(Android应用中的默认加载器):只能加载/data/app中的apk,也就是已经安装到手机中的apk。
     URLClassLoader :可以加载java中的jar,但是由于android不能直接识别jar,所以此方法在android中无法使用。

       通过Eclipse生成jar包步骤是,选中工程,右键 ->Export->Java目录下的JAR file -> 选择要打包进去的java文件,选择jar文件的存储路径。-> Finish.

      原始的jar是不行的,需要转换成android能识别的字节码dex。使用dx工具,可以在andorid的SKD目录下面搜索一下这个工具,以前的说在platform-tools这个目录下面但是我没有找到,我是在build-tools的子目录里面找到了不同版本的dx工具,我将最新版本的dx文件和当前目录下得lib目录一起拷贝到platform-tools。
转换命令 :

dx --dex --output=dest.jar src.jar

先简单介绍一下DexClassLoader 和 PathClassLoader

	public DexClassLoader(String dexPath, String dexOutputDir, String libPath, ClassLoader parent) 
	public PathClassLoader(String path, String libPath, ClassLoader parent)

dexPath:是加载apk/dex/jar的路径,必须是全路径,比如/data/app/com.ijinshan.browser_fast.apk.如果要包含多个路径(多个jar包),路径之间必须使用特定的分隔符,这个特定分隔符可使用 File.pathSeparator获得
dexOutDir:是dex的输出路径(因为加载apk/jar的时候会解压除dex文件,这个路径就是保存dex文件的),一般写在本程序的数据路径中,比如getApplicationContext().getDir("dex", 0)
libPath:是加载的时候需要用到的c/c++库存放的路径,这个一般不用。
parent:给DexClassLoader指定父加载器,一般为当前执行类的加载器。

可以看出来PathClassLoader的构造函数比DexClassLoader的少了一个参数dexOutDir,

这个原因很简单啦,因为PathClassLoader加载的是安装后的也就是/data/app中得apk,

而这部分的apk都会解压释放dex到指定的目录/data/dalvik-cache,这个释放解压操作

是系统做得,所以不需要dexOutDir参数。

    下面就是jar包中的代码,有对象函数也有静态函数和枚举。

package com.dynamic;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.widget.Toast;

public class DynamicTest {
    public enum arithmetic
    {
        plus, minus, multiply, divide;
    };

    private Context mContext;

    public DynamicTest(Context context) {
        mContext = context;
    }

    public static String helloWorld() {
        return "helloWorld";
    }

    public static int arithmetic(arithmetic sign, int a, int b) {
        int s = 0;
        switch (sign) {
            case plus:
                s = a + b;
                break;
            case minus:
                s = a - b;
                break;
            case multiply:
                s = a * b;
                break;
            case divide:
                if (b != 0) {
                    s = a / b;
                }
                break;
            default:
                break;
        }
        return s;
    }

    public void toastShow(String showString) {
        Toast.makeText(mContext, showString, Toast.LENGTH_SHORT).show();
    }

    public void showPluginWindow(String[] message) {
        AlertDialog.Builder builder = new Builder(mContext);
        builder.setMessage(message[1]);
        builder.setTitle(message[0]);
        builder.setNegativeButton(message[2], new Dialog.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        });
        Dialog dialog = builder.create();
        dialog.show();
    }
}

右键工程选择Export



选择要导出的文件保存成jar包,然后在终端执行 cd 到platform-tools目录下执行 

dx --dex --output=test.jar dynamic.jar

将转码后的test.jar push到手机的sd卡上

adb push test.jar /sdcard/text.jar

然后就是通过反射执行这些函数,下面是对4个函数的调用

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        final File optimizedDexOutputPath = new File(Environment.getExternalStorageDirectory()
                .toString() + File.separator + apkFileName);
        optimizedDexOutputPath.setExecutable(true);
        File dexOutputDirs = getApplicationContext().getDir("dex", 0);
        final DexClassLoader cl = new DexClassLoader(optimizedDexOutputPath.getAbsolutePath(),
                dexOutputDirs.getAbsolutePath(), null, getClass().getClassLoader());

        mToastButton = (Button) findViewById(R.id.helloWorld);
        mArithmetic = (Button) findViewById(R.id.arithmetic);
        mToastShow = (Button) findViewById(R.id.toastShow);
        mShowPluginWindow = (Button) findViewById(R.id.showPluginWindow);


        mShowPluginWindow.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Class<?> libProviderClazz = null;
                try {
                    libProviderClazz = cl.loadClass("com.dynamic.DynamicTest");
                    String[] strMessage = {
                            "title", "message", "ok"
                    };
                    Method method = libProviderClazz.getDeclaredMethod("showPluginWindow",
                            new Class[] {
                                String[].class
                            });
                    Constructor<?> con = libProviderClazz.getConstructor(Context.class);// 返回该类的构造方法
                    method.invoke(con.newInstance(MainActivity.this), (Object) strMessage);
                } catch (Exception exception) {
                    exception.printStackTrace();
                }
            }
        });

        mToastShow.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Class<?> libProviderClazz = null;
                try {
                    libProviderClazz = cl.loadClass("com.dynamic.DynamicTest");

                    Method method = libProviderClazz.getDeclaredMethod("toastShow", new Class[] {
                        String.class
                    });
                    Constructor<?> con = libProviderClazz.getConstructor(Context.class);// 返回该类的构造方法
                    method.invoke(con.newInstance(MainActivity.this), "好吗!");
                } catch (Exception exception) {
                    exception.printStackTrace();
                }
            }
        });
        mArithmetic.setOnClickListener(new View.OnClickListener() {

            @TargetApi(Build.VERSION_CODES.CUPCAKE)
            @SuppressLint("NewApi")
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub

                Class<?> libProviderClazz = null;
                try {
                    libProviderClazz = cl.loadClass("com.dynamic.DynamicTest");
                    Class<?> libProviderEnumClazz = cl
                            .loadClass("com.dynamic.DynamicTest$arithmetic");
                    Method[] methods = libProviderEnumClazz.getMethods();
                    Field[] fields = libProviderEnumClazz.getFields();
                    for (Method m : methods) { // 打印所有方法
                        Log.d("abcd", m.toString());
                    }
                    for (Field f : fields) {// 打印所有对象
                        Log.d("abcde", f.toString());
                    }
                    Field f = libProviderEnumClazz.getField("multiply");
                    Method method = libProviderClazz.getDeclaredMethod("arithmetic", new Class[] {
                            libProviderEnumClazz, int.class, int.class
                    });
                    int s = (Integer) method.invoke(null, f.get(libProviderEnumClazz), 5, 8);
                    Toast.makeText(MainActivity.this, String.valueOf(s), Toast.LENGTH_SHORT).show();
                } catch (Exception exception) {
                    exception.printStackTrace();
                }

            }
        });
        mToastButton.setOnClickListener(new View.OnClickListener() {
            @SuppressLint("NewApi")
            @TargetApi(Build.VERSION_CODES.CUPCAKE)
            public void onClick(View view) {

                Class<?> libProviderClazz = null;

                try {
                    libProviderClazz = cl.loadClass("com.dynamic.DynamicTest");
                    Method[] methods = libProviderClazz.getMethods();
                    for (Method m : methods) {
                        Log.d("abcd", m.toString());
                    }
                    Method method = libProviderClazz
                            .getDeclaredMethod("helloWorld", new Class[] {});
                    // 如果传递给Method对象的invoke()方法的第一个参数为null,说明该Method对象对应的是一个静态方法,不通过对象调用
                    Toast.makeText(MainActivity.this, (String) method.invoke(null),
                            Toast.LENGTH_SHORT).show();
                } catch (Exception exception) {
                    exception.printStackTrace();
                }
            }
        });
    }

通过ClassLoader装载类,调用其内部函数的过程有点烦琐,包括先构造出Method对象,并构造出Method对象所使用的参数对象,然后才能调用。

有一种方法比较简单,那就是定义interface接口,interface仅仅定义函数的输入输出,却不定义函数的具体实现,然后在插件工程导出jar包的时候不要勾选interface文件,因为interface是要包含在主工程里的,如果导出jar包也被包含在里面,程序运行的时候是会出错的(程序运行时包含了两份interface文件)。因为不像反射这么烦琐,就不记录了。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值