预备知识:
<1> : adb push的使用;
<2> : eclipse 导出jar包的过程
<3> : dx命令行的使用;
首先介绍ClassLoader类关系,它本身也是继承Object,然而实际开发中用的比较多的是他的子类,继承关系如下:
ClassLoader
extends Object
java.lang.Object |
|
↳ |
java.lang.ClassLoader |
Known Direct Subclasses BaseDexClassLoader, SecureClassLoader |
Known Indirect Subclasses DexClassLoader, PathClassLoader, URLClassLoader |
[以上信息来自:http://developer.android.com/reference/java/lang/ClassLoader.html]
DexClassLoader和PathClassLoader 的区别:
PathClassLoader是通过构造函数new DexFile(path)来产生DexFile对象的;而DexClassLoader则是通过其静态方法loadDex(path,outpath, 0)得到DexFile对象。这两者的区别在于DexClassLoader需要提供一个可写的outpath路径,用来释放.apk包或者.jar包中的dex文件。换个说法来说,就 是PathClassLoader不能主动从zip包中释放出dex,因此只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在 cache中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放 出dex文件。另外,PathClassLoader在加载类时调用的是DexFile的loadClassBinaryName,而DexClassLoader调用的是loadClass。因此,在使用PathClassLoader时类全名需要用”/”替换”.”.
因为博客中只是提供具体的应用,实际希望能够先去阅读ClassLoader源代码.
下面写一个APP体会一下,步骤如下:
<1> 新建OneplusDynamicloader工程,如下 :
<2> : 具体代码如下:
接口类IOneplusDynamic.java:
/** * @Description * */ package com.oneplus.interfaces; import android.app.Activity; import android.content.Context; /** * @author zhibao.liu * @Date 2015/11/14 * @company oneplus.Inc */ public interface IOneplusDynamic { public void OneplusInit(Context context); public void OneplusShowProjectManager(); public void OneplusShowArtistDesigner(); public void OneplusShowITProgrammer(); public void Destory(); } 具体实现上面接口类 OneplusDynamic.java: /** * @Description * */ package com.oneplus.impl; import android.app.Activity; import android.content.Context; import android.util.Log; import android.widget.Toast; import com.oneplus.interfaces.IOneplusDynamic; /** * @author zhibao.liu * @Date 2015/11/14 * @company oneplus.Inc */ public class OneplusDynamic implements IOneplusDynamic { private final static String TAG="OneplusDynamic"; private Context mContext; @Override public void OneplusInit(Context context) { // TODO Auto-generated method stub Log.i(TAG,"OneplusDynamic OneplusInit ..."); mContext=context; } @Override public void OneplusShowProjectManager() { // TODO Auto-generated method stub Log.i(TAG,"OneplusDynamic OneplusShowProjectManager ..."); } @Override public void OneplusShowArtistDesigner() { // TODO Auto-generated method stub Log.i(TAG,"OneplusDynamic OneplusShowArtistDesigner ..."); } @Override public void OneplusShowITProgrammer() { // TODO Auto-generated method stub Log.i(TAG,"OneplusDynamic OneplusShowITProgrammer ..."); if(mContext==null){ return ; } Toast.makeText(mContext, "IT programmer is running !", Toast.LENGTH_LONG).show(); } @Override public void Destory() { // TODO Auto-generated method stub Log.i(TAG,"Dynamic Destory ..."); if(mContext!=null){ mContext=null; } } }
<3> : 首先将<2>中OneplusDynamic.java打包成jar输出:
Eclipse步骤 :
<a>: File->Export->java->jar输出,如下图:
<b> : 下一步,选择需要输出jar的文件,并且制定输出路径,如下图:
点击Finish 按钮.
<c> : 在电脑C盘生成 oneplusdynamic.jar包,将这个jar拷贝开发androidapp的SDK包下面的build-tools/android-*.*目录下,比如我的目录:
<d> : 启动cmd终端,切换到<c>路径中,执行dx –dex –outputoneplus_dynamic.jar oneplusdynamic.jar
执行上面以后,在同名目录下产生一个新的oneplus_dynamic.jar包,如下:
上面执行主要做的工作是:首先将dynamic.jar编译成dynamic.dex文件(Android虚拟机认识的字节码文件),然后再将 dynamic.dex文件压缩成dynamic_temp.jar,当然你也可以压缩成.zip格式的,或者直接编译成.apk文件都可以的(看了ClassLoader源代码,会发现其实zip,apk,jar其实本质都是一样的---个人之见).
<e> : 同样将工程中interface包打成jar包,然后同样执行dx命令行,结果执行得到oneplus_dynamicinterface.jar(加入前面导出的jar包名为oneplusdynamicinterface.jar) .
后续还需要提前再修改这个工程几个地方,如下 :
删除 : [删除下面是为了这个工程安装到系统就没有必须显示给user,也不需要运行,相当于plugin]
<category android:name="android.intent.category.LAUNCHER" /> android:icon="@drawable/ic_launcher"
<action android:name="com.oneplus.impl.OneplusDynamic"/>
主配置文件如下 :
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.oneplus.oneplusdynamicloader" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" /> <application android:allowBackup="true" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.oneplus.oneplusdynamicloader.OneplusDynamicLoadClassActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <action android:name="com.oneplus.impl.OneplusDynamic"/> </intent-filter> </activity> </application> </manifest>
<4> : 再新建一个测试工程OneplusDynamicLoaderTest,工程树如下 :
注意 : 里面interface包和接口是完全将前面里面的抄过来的,复制一份到宿主机APP中.
主工程类文件OneplusDynamicLoadClassActivity.java
/** * @Description * */ package com.oneplus.oneplusdynamicloadertest; import java.io.File; import java.util.List; import com.oneplus.interfaces.IOneplusDynamic; import android.os.Bundle; import dalvik.system.DexClassLoader; import dalvik.system.PathClassLoader; import android.os.Environment; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.util.Log; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; /** * @author zhibao.liu * @Date 2015/11/14 * @company oneplus.Inc */ public class OneplusDynamicLoadClassActivity extends Activity implements OnClickListener { private final static String TAG = "OneplusDynamicLoadClassActivity"; private Button mButtonShowPm; private Button mButtonShowArt; private Button mButtonShowIT; private TextView mText; private Context mContext; private IOneplusDynamic mDexLib; private IOneplusDynamic mPathLib; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.oneplus_main); mContext = OneplusDynamicLoadClassActivity.this; mButtonShowPm = (Button) findViewById(R.id.showpm); mButtonShowPm.setOnClickListener(this); mButtonShowArt = (Button) findViewById(R.id.showart); mButtonShowArt.setOnClickListener(this); mButtonShowIT = (Button) findViewById(R.id.showit); mButtonShowIT.setOnClickListener(this); mText=(TextView)findViewById(R.id.showinfo); /*DexClassLoaderX();*/ PathClassLoaderX(); } private void DexClassLoaderX() { /** 使用DexClassLoader方式加载类 */ // dex压缩文件的路径(可以是apk,jar,zip格式) String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "OneplusDynamicLoader.apk"; // dex解压释放后的目录 // String dexOutputDir = getApplicationInfo().dataDir; String dexOutputDirs = Environment.getExternalStorageDirectory() .toString(); // 定义DexClassLoader // 第一个参数:是dex压缩文件的路径 // 第二个参数:是dex解压缩后存放的目录 // 第三个参数:是C/C++依赖的本地库文件目录,可以为null // 第四个参数:是上一级的类加载器 DexClassLoader cl = new DexClassLoader(dexPath, dexOutputDirs, null, getClassLoader()); // com.dynamic.impl.Dynamic是动态类名 // 使用DexClassLoader加载类 try { Class libProviderClazz = cl .loadClass("com.oneplus.impl.OneplusDynamic"); try { mDexLib = (IOneplusDynamic) libProviderClazz.newInstance(); mDexLib.OneplusInit(mContext); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void PathClassLoaderX() { /** 使用PathClassLoader方法加载类 */ // 创建一个意图,用来找到指定的apk:这里的"com.dynamic.impl是指定apk中在AndroidMainfest.xml文件中定义的<action name="com.dynamic.impl"/> Intent intent = new Intent("com.oneplus.impl.OneplusDynamic", 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()); // 加载类 Class libProviderClazz; try { libProviderClazz = pcl.loadClass("com.oneplus.impl.OneplusDynamic"); try { mPathLib = (IOneplusDynamic) libProviderClazz.newInstance(); if (mPathLib != null && mContext != null) { mPathLib.OneplusInit(mContext); } } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void onClick(View view) { // TODO Auto-generated method stub int id = view.getId(); switch (id) { case R.id.showpm: if (mPathLib != null) { mPathLib.OneplusShowProjectManager(); } break; case R.id.showart: if (mPathLib != null) { mPathLib.OneplusShowArtistDesigner(); } break; case R.id.showit: if (mPathLib != null) { mPathLib.OneplusShowITProgrammer(); } break; } } }
对应布局文件oneplus_main.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/showinfo" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/showpm" android:text="@string/oneplus_showpm"/> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/showart" android:text="@string/oneplus_showart"/> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/showit" android:text="@string/oneplus_showit"/> </LinearLayout>
Values 文件string.xml 下新增下面:
<string name="oneplus_showpm">Show PM</string> <string name="oneplus_showart">Show ART</string> <string name="oneplus_showit">Show IT</string>
运行程序,点击app按钮 :
结果就出来了,当点击”Show IT”按钮将会显示Toast气泡.
不过读者会发现上面我们只是用PathClassLoader类,但是DexClassLoader类没使用呀.
由于DexClassLoaderX()方式是使用路径加载的方式,程序中给出的路径是:
String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "OneplusDynamicLoader.apk";
那么这个不需要安装,将被加载的对象编译生成的APK放到手机SD卡中,push到手机sdcard目录下,现在来分析一下下面DexClassLoaderX方法的程序:
private void DexClassLoaderX() { /** 使用DexClassLoader方式加载类 */ // dex压缩文件的路径(可以是apk,jar,zip格式) //由于oneplus_dynamic.jar被执行dx过,其实就是dex文件,所以正如上面所说,这时oneplus_dynamic.jar和OneplusDynamicLoader.apk对DexClassLoader来说是没有区别的. String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "oneplus_dynamic.jar";//"OneplusDynamicLoader.apk"; File file=new File(dexPath); if(!file.exists()){ return ; } // dex解压释放后的目录 // 这个路径在4.2版本以后是存在问题的,因为android系统会认为这个dexOutputDirs这个路径是一个random direction,将不会允许app访问,会报异常错误的 String dexOutputDirs = Environment.getExternalStorageDirectory() .toString(); // this is not access permission to read/write random file //所以实际上需要下面的方式获取,这一点非常重要!!! final File optimizedDexOutputPath = getDir("outdex", 0); // 定义DexClassLoader // 第一个参数:是dex压缩文件的路径 // 第二个参数:是dex解压缩后存放的目录 // 第三个参数:是C/C++依赖的本地库文件目录,可以为null // 第四个参数:是上一级的类加载器 DexClassLoader cl = new DexClassLoader(dexPath, optimizedDexOutputPath.getAbsolutePath(), null, getClassLoader()); // com.dynamic.impl.Dynamic是动态类名 // 使用DexClassLoader加载类 try { Class libProviderClazz = cl .loadClass("com.oneplus.impl.OneplusDynamic"); try { mDexLib = (IOneplusDynamic) libProviderClazz.newInstance(); mDexLib.OneplusInit(mContext); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
所以将上面的程序替换前面的程序,程序即可以work了,效果时一样的.
下面在附录 :<1> : ClassLoader 源码片段供读者参考:
/** * Creates a {@code DexClassLoader} that finds interpreted and native * code. Interpreted classes are found in a set of DEX files contained * in Jar or APK files. * * The path lists are separated using the character specified by * the "path.separator" system property, which defaults to ":". * * @param dexPath * the list of jar/apk files containing classes and resources * @param dexOutputDir * directory where optimized DEX files should be written * @param libPath * the list of directories containing native libraries; may be null * @param parent * the parent class loader */ public DexClassLoader(String dexPath, String dexOutputDir, String libPath, ClassLoader parent) { super(parent); if (dexPath == null || dexOutputDir == null) throw new NullPointerException(); mRawDexPath = dexPath; mDexOutputPath = dexOutputDir; mRawLibPath = libPath; String[] dexPathList = mRawDexPath.split(":"); int length = dexPathList.length; //System.out.println("DexClassLoader: " + dexPathList); mFiles = new File[length]; mZips = new ZipFile[length]; mDexs = new DexFile[length]; /* open all Zip and DEX files up front */ for (int i = 0; i < length; i++) { //System.out.println("My path is: " + dexPathList[i]); File pathFile = new File(dexPathList[i]); mFiles[i] = pathFile; if (pathFile.isFile()) { try { mZips[i] = new ZipFile(pathFile); } catch (IOException ioex) { // expecting IOException and ZipException System.out.println("Failed opening '" + pathFile + "': " + ioex); //ioex.printStackTrace(); } /* we need both DEX and Zip, because dex has no resources */ try { String outputName = generateOutputName(dexPathList[i], mDexOutputPath); mDexs[i] = DexFile.loadDex(dexPathList[i], outputName, 0); } catch (IOException ioex) { // might be a resource-only zip System.out.println("Failed loadDex '" + pathFile + "': " + ioex); } } else { if (VERBOSE_DEBUG) System.out.println("Not found: " + pathFile.getPath()); } } /* * Prep for native library loading. */ String pathList = System.getProperty("java.library.path", "."); String pathSep = System.getProperty("path.separator", ":"); String fileSep = System.getProperty("file.separator", "/"); if (mRawLibPath != null) { if (pathList.length() > 0) { pathList += pathSep + mRawLibPath; } else { pathList = mRawLibPath; } } mLibPaths = pathList.split(pathSep); length = mLibPaths.length; // Add a '/' to the end so we don't have to do the property lookup // and concatenation later. for (int i = 0; i < length; i++) { if (!mLibPaths[i].endsWith(fileSep)) mLibPaths[i] += fileSep; if (VERBOSE_DEBUG) System.out.println("Native lib path " +i+ ": " + mLibPaths[i]); } }