使用“插件”模式定制皮肤(一)

首先我们不谈什么是“插件”模式,先了解下DexClassLoader类,该类是android用来加载dex文件使用的,可以动态加载class类文件,我们称之为“类装载器”。

假设我们有两个APK,一个是Host,一个是Plugin,其中Plugin中有个PluginClass类,该类有个方法function1(),代码如下:

package com.ldw.plugin;

import android.util.Log;

public class PluginClass {

	public PluginClass() {
		Log.i("Plugin", "PluginClass client initialized");
	}

	public int function1(int a, int b) {
		return a + b;
	}

}

其中Host中MainActivity代码如下:

package com.ldw.host;

import java.lang.reflect.Method;
import java.util.List;

import dalvik.system.DexClassLoader;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        useDexClassLoader();
    }

    @SuppressLint("NewApi") 
    public void useDexClassLoader() {
    	Intent intent = new Intent();
    	intent.setAction(Intent.ACTION_MAIN);
    	intent.addCategory(Intent.CATEGORY_LAUNCHER);
    	intent.setPackage("com.ldw.plugin");
    	PackageManager pm = getPackageManager();
    	List<ResolveInfo> plugins =  pm.queryIntentActivities(intent, 0);
    	ResolveInfo rinfo = plugins.get(0);
    	ActivityInfo ainfo = rinfo.activityInfo;

    	String packageName = ainfo.packageName;
    	//目标类所在apk或jar包文件的路径
    	String dexPath = ainfo.applicationInfo.sourceDir;
    	//由于dex文件存在apk中,因此在装载目标类之前需要先从apk中解压出dex文件,这个参数就是解压后存放的路径
    	String dexOutputDir = getApplicationInfo().dataDir;
    	//指目标类中所使用的C/C++库存放的路径
    	String libPath = ainfo.applicationInfo.nativeLibraryDir;

    	DexClassLoader cl = new DexClassLoader(dexPath, dexOutputDir, libPath, this.getClass().getClassLoader());
    	try {
			Class<?> clazz = cl.loadClass(packageName + ".PluginClass");
			//获取真正的PluginClass对象
			Object obj = clazz.newInstance();
			Class[] params = new Class[2];
			params[0] = Integer.TYPE;
			params[1] = Integer.TYPE;
			Method action = clazz.getMethod("function1", params);
			Integer ret = (Integer) action.invoke(obj, 12, 12);
			Log.i("Host", "result is " + ret);
		} catch (Exception e) {
			Log.i("Host", "error", e);
		}
    }
}

上面useDexClassLoader()方法主要介绍了如何使用DexClassLoader来动态加载外部apk中的方法。

有人可能感觉使用动态加载来加载外部类中的方法的过程有些繁琐,构造Method,构造参数,然后才能调用,有没有一种方法直接newInstance()出来类对象之后就可以调用类里面的方法呢,答案是肯定的。我们可以首先定义一个interface接口,该接口中有方法function1(),注意该接口是在Host工程中定义的,然后我们导出一个jar包,该jar包很简单,只包含我们定义的那个接口类,然后我们将该jar包导入到我们的Plugin工程中,然后类PluginClass实现该接口,注意导入到Plugin的方式,如图所示:

图中错误的导入方式的原因是因为如果以外部jar的形式导入的话会作为程序的一部分被打包到最终的程序文件中,从而使得Plugin和Host项目中存在包名相同但验证码不同的类文件,这最终会导致运行时出现错误信息“Class ref in pre-verified class resolved to unexpected implementation”。

这里我就不再演示代码了,下一节将介绍使用“插件”模式定制皮肤的功能。

《使用“插件”模式定制皮肤(一)》上有1条评论

  1. 找了好多关于这个问题的文章 终于找到原因了 原来就这么简单 蛋都碎了一地啊。。。

评论已关闭。