android动态加载class文件(DexClassLoader可从SD卡加载)

出自:http://www.tuicool.com/articles/qeqM7vN

一、思路

        1、这段时间因为要做一个自定义的3Dwidget,这个widget是一个时钟,想实现的功能是可以给该widget更换皮肤(View)和交互,皮肤都是又图片和字符串组成的,但是又要考虑可能用户会选择不同的皮肤(皮肤中的某些元素不需要,又或者多添加一些元素),开始想吧,可以将所有的对象都先实例化,然后再在SD卡的配置文件中读取哪些是需要的元素,但是这样太有局限性了,一个是内存会耗费,另一个是根本就不灵活。偶然在Q群看到胡哥给的一个用.class文件创建对象的文章,那么我想可不可以在android里用动态加载的方式来切换皮肤呢?我的每个类所需要做的并不多,而且每个不同皮肤都是在另一个类(类似一个ViewGroup)中被调用,那个类几乎就不需要更改。那如果我将这些类的所必须的方法抽出来做一个接口,那么 ViewGroup中只要调用这个接口的方法即可,具体的实现由其子类实现。

        2、整理了一下思路,决定用上一个代理模式。

public interface ITest {
    public void log(String content);
}
public class Test implements ITest {

  ITest test;

  public Test(Context context) {
    getTestInstanceDynamicFromSDCard();
  }

  private void getTestInstanceDynamicFromSDCard() {
    // get test instance by dynamic
  }

  @Override
  public void log(String content) {
    test.log(content);
  }

}

          如此一来 ViewGroup中只要实例化Test类,调用相应的方法即可,而其具体做什么将有其内部 getTestInstanceDynamicFromSDCard所得到ITest对象实现。那么具体的问题就是如何动态加载一个类并创建其对象!

二、ClassLoader类来加载

          1、看了胡哥推荐的用.class文件创建对象,于是也跟着用类似的方法来实现: 

( http://wlh0706-163-com.iteye.com/blog/2098619 )

          2、但是总会失败,并且报错“can't load this type of class file”查了资料后才知道是虚拟机的问题!

在使用标准 Java 虚拟机时,我们经常自定义继承自 ClassLoader 的类加载器。然后通过 defineClass 方法来从一个二进制流中加载 Class。(为什么要继承 ClassLoader?如果不继承他就无法使用 defineClass方法 ),但是在android的 Dalvik虚拟机里走不通,但是android有提供两个类以供使用 PathClassLoader、 DexClassLoader

三、 DexClassLoader加载(从SD卡加载)

          1、 PathClassLoader因为路径设计到system/app,故不作考虑。

          2、 DexClassLoader可以动态加载 .apk 、 .jar 和 .dex 文件而且路径不受限制,而且根据我的需求,显然是将自己分离的类打包成jar包会很灵活,因为这样我可以指定主程序对哪个路径的jar包进行加载(便于切换资源)

          3、将所有程序都编译好后,包括需要动态加载的类也放在同一主程序运行以保证程序正常,将需要动态加载的隔离开,如实现一个DemoTest,放到com.example.out包下

package com.example.out;

import android.util.Log;

import com.example.biz.ITest;

public class DemoTest implements ITest {

    @Override
    public void log(String content) {
        Log.d("example", content);
    }

}

          4、导出成jar包(eclipse直接右击该类export->java->jar file->next->存储目录+jar报名,不如就叫 Dynamic.jar->finish ),此时可以将原项目中的插件部分删除咯! 

          5、将Dynamic。jar拷贝到 android-sdk-windows\platform-tools下将其转换成DexClassLoader所能识别的二进制jar包:cd 到platform-tools目录下,执行命令 dx --dex --output=DynamicTest.jar Dynamic.jar  其中 DynamicTest.jar为新的jar包,是我们所需要的二进制jar包, Dynamic.jar 为eclipse直接导出的jar包

          6、将 DynamicTest.jar拷贝到SD卡,例如:.. /sbclock/DynamicTest.jar

          7、程序实现动态加载:

package com.example.dynamic;

import android.app.Activity;
import android.os.Bundle;

import com.example.biz.ITest;
import com.example.biz.Test;

public class MainActivity extends Activity {

  private ITest mTest;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mTest = new Test(this);

  }

  @Override
  protected void onDestroy() {
    mTest.log("it is just a test");
    super.onDestroy();
  }

}

   Test类动态加载:

package com.example.biz;

import java.io.File;
import java.lang.reflect.Constructor;

import dalvik.system.DexClassLoader;
import android.annotation.SuppressLint;
import android.content.Context;

public class Test implements ITest {

  private ITest mTest;
  private Context mContext;
  private String mJarPath;
  private String mClassName;

  public Test(Context context) {
    mContext = context;
    getTestInstanceDynamicFromSDCard();
  }

  @SuppressLint("NewApi")
  private void getTestInstanceDynamicFromSDCard() {
    // get test instance by dynamic
    mJarPath = android.os.Environment.getExternalStorageDirectory()
        .getAbsolutePath() + "/sbclock/DynamicTest.jar";// 前半部分为获得SD卡的目录
    mClassName = "com.example.out.DemoTest";//和导出之前的包名和类名保持一致
    File dexOutputDir = mContext.getDir("dex", 0);//
    File file = new File(mJarPath);
    DexClassLoader classLoader = new DexClassLoader(file.getAbsolutePath(),
        dexOutputDir.getAbsolutePath(), null, mContext.getClassLoader());
    try {
      Class<?> clazz = classLoader.loadClass(mClassName);

      Constructor<?> constructor = clazz.getConstructor(new Class[] {});//得到构造器
      mTest = (ITest) constructor.newInstance();//实例化
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  @Override
  public void log(String content) {
    mTest.log(content);
  }

}

          8、结果,当退出该程序时确实有打印:

 D/example (28710): it is just a test

          9、更换其他的实现方式,还是只需要使用这个框架,在Test类中注册一个广播,外来程序可以发送广播通过Intent将其路径名和包名传过来即可创建新的子类!

          10.1、在切换成其他子类时需要注意的是子类的名称不要相同,在实践中本来想使用统一的包名和类名,结果因为其对象在程序内存中已经有了,所以不会再次创建!

          10.2、导出的jar包其实就是主程序中的一部分,所以不论其类中需要导入其他的类(而该jar包不含有)也没关系,因为动态加载之后这个对象就是主程序中的一部分。

         10.3.导出的文件资源的加载可以从主程序中加载,也可以从SD卡中加载(最好是SD卡,这样就不同的jar包可以对应不同的资源了)

          10.4、这应该算是一个粗略的插件框架了,如果想实现功能多的插件,那还是得将接口考虑周到!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值