【Android高级】DexClassloader和PathClassloader动态加载插件的实现

(一)DexClassloader

一、基本概念:


         在Android中可以跟java一样实现动态加载jar,但是Android使用德海Dalvik VM,不能直接加载java打包jar的byte code,需要通过dx工具来优化Dalvik byte code。
         Android在API中给出可动态加载的有:DexClassLoader 和 PathClassLoader。
         DexClassLoader:可加载jar、apk和dex,可以从SD卡中加载(本文使用这种方式)
         PathClassLoader:只能加载已经安装搭配Android系统中的apk文件

二、实施
        编写接口:Dynamic

package com.smilegames.dynamic.interfaces;

public interface Dynamic {
	public String helloWorld();
	
	public String smileGames();
	
	public String fyt();
}
        编写实现类:DynamicTest
package com.smilegames.dynamic.impl;

import com.smilegames.dynamic.interfaces.Dynamic;

public class DynamicTest implements Dynamic {

	@Override
	public String helloWorld() {
		return "Hello Word!";
	}

	@Override
	public String smileGames() {
		return "Smile Games";
	}

	@Override
	public String fyt() {
		return "fengyoutian";
	}

}
三、打包并编译成dex
       将接口打包成jar:dynamic.jar( 只打包这Dynamic.java这一个接口
       将实现类打包成jar:dynamic_test.jar( 只打包DynamicTest.java这一个实现类
       将打包后的实现类(dynamic_test.jar)编译成dex:dynamic_impl.jar
              1、将dynamic_test.jar拷贝到SDK安装目录android-sdk-windows\platform-tools下( ps:如果platform-tools没有dx.bat,可拷贝到build-tools目录下有dx.bat的子目录
              2、执行以下命令:
dx --dex --output=dynamic_impl.jar dynamic_test.jar
             3、将dynamic.jar引入测试实例
             4、将dynamic_impl.jar放到模拟器或真机的sdcard

四、修改onCreate例子
       
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

//        或许activity按钮
        helloWorld = (Button) findViewById(R.id.helloWorld);
        smileGames = (Button) findViewById(R.id.smileGames);
        fyt = (Button) findViewById(R.id.fyt);

        /*使用DexCkassLoader方式加载类*/
        // dex压缩文件的路径(可以是apk,jar,zip格式)
        String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "dynamic_impl.jar";

        // dex解压释放后的目录
        String dexOutputDirs = Environment.getExternalStorageDirectory().toString();

        // 定义DexClassLoader
        // 第一个参数:是dex压缩文件的路径
        // 第二个参数:是dex解压缩后存放的目录
        // 第三个参数:是C/C++依赖的本地库文件目录,可以为null
        // 第四个参数:是上一级的类加载器
        DexClassLoader dexClassLoader = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader());

        Class libProvierClazz = null;
        // 使用DexClassLoader加载类
        try {
            libProvierClazz = dexClassLoader.loadClass("com.smilegames.dynamic.impl.DynamicTest");
            // 创建dynamic实例
            dynamic = (Dynamic) libProvierClazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }

        helloWorld.setOnClickListener(new HelloWorldOnClickListener());
        smileGames.setOnClickListener(new SmileGamesOnClickListener());
        fyt.setOnClickListener(new FytOnClickListener());
    }

   private final class HelloWorldOnClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            if (null != dynamic) {
                Toast.makeText(getApplicationContext(), dynamic.helloWorld(), 1500).show();
            } else {
                Toast.makeText(getApplicationContext(), "类加载失败", 1500).show();
            }
        }
    }

    private final class SmileGamesOnClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            if (null != dynamic) {
                Toast.makeText(getApplicationContext(), dynamic.smileGames(), 1500).show();
            } else {
                Toast.makeText(getApplicationContext(), "类加载失败", 1500).show();
            }
        }
    }
       1、运行这段代码时4.0.1以上版本会报: java.lang.IllegalArgumentException: optimizedDirectory not readable/writable: /storage/sdcard0
      可以通过这个授权解决:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 
      2、授权之后又会报: java.lang.IllegalArgumentException: Optimized data directory /storage/sdcard0 is not owned by the current user. Shared storage cannot protect your application from code injection attacks.
      这个问题的原因是:在4.1系统由于This class loader requires an application-private, writable directory to cache optimized classes为了防止一下问题:
External storage does not provide access controls necessary to protect your application from code injection attacks.
所以加了一个判断Libcore.os.getuid() != Libcore.os.stat(parent).st_uid判断两个程序是不是同一个uid
 private DexFile(String sourceName, String outputName, int flags) throws IOException {
 if (outputName != null) {
 try {
 String parent = new File(outputName).getParent();
if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
 throw new IllegalArgumentException("Optimized data directory " + parent
 + " is not owned by the current user. Shared storage cannot protect"
 + " your application from code injection attacks.");
 }
 } catch (ErrnoException ignored) {
 // assume we'll fail with a more contextual error later
 }
 }
 
mCookie = openDexFile(sourceName, outputName, flags);
 mFileName = sourceName;
 guard.open("close");
 //System.out.println("DEX FILE cookie is " + mCookie);

 }
        解决方法是:指定dexoutputpath为APP自己的缓存目录
File dexOutputDir = context.getDir("dex", 0);
DexClassLoader dexClassLoader = new DexClassLoader(dexPath,dexOutputDir.getAbsolutePath(),null,getClassLoader());
  ps:第四步的问题最终是2导致的,所以只需用2的解决方案即可,不需要1的授权。

 执行结果自行尝试。。。


(二)PathClassloader


	public void dexLoad() {
		/** 使用DexClassLoader方式加载类 */
		// dex压缩文件的路径(可以是apk,jar,zip格式)
		String dexPath = Environment.getExternalStorageDirectory().toString()
				+ File.separator + "dynamic_temp.jar";
		// dex解压释放后的目录
		// String dexOutputDir = getApplicationInfo().dataDir;
		// String dexOutputDirs = Environment.getExternalStorageDirectory()
		// .toString();
		// 定义DexClassLoader
		// 第一个参数:是dex压缩文件的路径
		// 第二个参数:是dex解压缩后存放的目录
		// 第三个参数:是C/C++依赖的本地库文件目录,可以为null
		// 第四个参数:是上一级的类加载器
		File dexOutputDir = getApplicationContext().getDir("dex", 0);
		DexClassLoader cl = new DexClassLoader(dexPath,
				dexOutputDir.getAbsolutePath(), null, getClassLoader());

		Log.i("tag", dexOutputDir.getAbsolutePath());

		Class libProviderClazz;
		try {
			libProviderClazz = cl.loadClass("com.dynamic.impl.Dynamic");
			// dynamic = (IDynamic) libProviderClazz.newInstance();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	// MyInterface dynamic;
	Class dynamic;

	public void pathLoad() {
		/** 使用PathClassLoader方法加载类 */
		// 创建一个意图,用来找到指定的apk:这里的"com.dynamic.impl是指定apk中在AndroidMainfest.xml文件中定义的<action name="com.dynamic.impl"/>
		Intent intent = new Intent(
				"com.example.androidendyeartest.MainActivity", null);
		// 获得包管理器
		PackageManager pm = getPackageManager();
		List<ResolveInfo> resolveinfoes = pm.queryIntentActivities(intent, 0);
		// 获得指定的activity的信息
		ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;
		// 获得apk的目录或者jar的目录
		String apkPath = actInfo.applicationInfo.sourceDir;
		// native代码的目录
		String libPath = actInfo.applicationInfo.nativeLibraryDir;
		// 创建类加载器,把dex加载到虚拟机中
		// 第一个参数:是指定apk安装的路径,这个路径要注意只能是通过actInfo.applicationInfo.sourceDir来获取
		// 第二个参数:是C/C++依赖的本地库文件目录,可以为null
		// 第三个参数:是上一级的类加载器
		PathClassLoader pcl = new PathClassLoader(apkPath, libPath,
				this.getClassLoader());
		// 加载类
		try {
			dynamic = pcl.loadClass("com.test.bean.MyBean");
	

		} catch (Exception exception) {
			exception.printStackTrace();
		}
	}
应用:

	@OnClick(R.id.b1)
	public void t1(View v) {
		if (dynamic != null) {
			// dynamic.show1(getApplicationContext());

			Method method;
			try {

				method = dynamic.getMethod("show1",Context.class);
				//Context context=getApplicationContext();
				method.invoke(dynamic.newInstance(), getApplicationContext());
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		} else {
			Toast.makeText(getApplicationContext(), "类加载失败", 1500).show();
		}
	}



你可能感兴趣的:(【Android高级】DexClassloader和PathClassloader动态加载插件的实现)