Android动态加载那些事儿-开篇

前些天基于AndFix给正在做的项目弄了套HotFix的方案,初步使用效果不错,今天又看了下腾讯空间基于dex加载顺序制定的热修补方案,感觉它们方法都用得非常精巧,什么时候自己也能想出这些点子就好了。才入门Android不久,只是能够紧凑的按照需求进行开发,感觉知识点并不是那么的系统和深入,所以打算从dex动态加载开始,逐步研究学习Android方面的知识,后续会写几篇文章,和大家分享我的学习心得。

做为开篇,这次简单开个项目,后续持续补充,算是给自己一个目标去完成它。GitHub

Android的加载器

查了下资料,Android有两种加载器:

  • DexClassLoader : 可以加载文件系统上的dex、jar和apk
  • PathClassLoader : 只可以加载已经安装好了的apk文件,在/data/app/<packagename>/目录下

DexClassLoader只有一个构造函数,其参数如下:

DexClassLoader需要一个私有可写的目录去存储优化后的dex文件(optimizedDirectory),可以通过Contexct.getDir(String, int)来创建。

原始的class打包的jar文件是不能直接使用的,需要使用官方提供的dx工具,这个工具在/androidSDK/build-tools/<versioncode>/下,使用方法为:

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

示例

我们先写一个简单的类,然后通过dex打包成androd系统可用的jar文件,通过DexClassLoader去加载它,然后通过反射去调用里面的方法。

DexLoaderPlugin项目

我们把这个项目打包成jar

TestClass.java:

public class TestClass implements PluginInterface{  
    @Override
    public String get() {
        return "这是个测试"+this.getClass().getName();
    }
}

PluginInterface.java:
目前这个接口并没什么实际意义,只是为了方便后面在主项目做动态加载,下意识写的个接口

public interface PluginInterface {  
    String get();
}

在AndroidStudio中,直接把项目定义成com.android.library,编译后,会在/build/outputs/aar/中生成aar文件,aar文件是集合了编译好的class、so、lib和资源文件的包,所用的class被打包成了一个jar,我们只需要把它的后缀改成zip,解压就可以取到里面的classes.jar文件了。 然后使用dx --dex --output=dest.jar src.jar命令,将jar包转换成可以使用的包就可以了。最后我们再通过ADB命令把文件传到sd卡根目录

DexLoaderMain主项目

MainActivity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.to_load).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                File file = new File(Environment.getExternalStorageDirectory()
                        .toString() + File.separator + "classes.jar");
                final File optimizedDexOutputPath = getDir("tmp", Context.MODE_PRIVATE);
                DexClassLoader classLoader = new DexClassLoader(file.getAbsolutePath(),
                        optimizedDexOutputPath.getAbsolutePath(), null,
                        getClassLoader());
                Class<?> clazz = null;
                try {
                    clazz = classLoader.loadClass("com.breeedust.dexloaderplugin.TestClass");
                    Method method = clazz.getMethod("get");
                    String data = (String) method.invoke(clazz.newInstance());
                    Log.e("-->", data);
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        });

    }
}

主项目主要是加载sd卡根目录的classes.jar文件,然后通过反射取到里面的get方法,并执行它。

最后

代码都很简单,所以没写注释,目前可以看出,需要通过指明类名和方法名才可以去调用,在实际项目应用中,不可能手动的把类和方法名写在代码里,所以需要动态的知道有哪些类,有哪些方法,然后去调用他们。今天先简单写到这里,后续继续深入。

BreezeDust

继续阅读我的更多文章