一、基本概念:
在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
dx --dex --output=dynamic_impl.jar dynamic_test.jar3、将dynamic.jar引入测试实例
@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.
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的授权。
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(); } }