预备知识:
<1> : adb push的使用;
<2> : eclipse 导出jar包的过程
<3> : dx命令行的使用;
首先介绍ClassLoader类关系,它本身也是继承Object,然而实际开发中用的比较多的是他的子类,继承关系如下:
ClassLoader
extends Object
↳ | java.lang.ClassLoader |
Known Direct Subclasses |
Known Indirect Subclasses |
[以上信息来自: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;
}
}
}
暂时不用理会MainActivity.java和布局文件等.
<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 :
<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]);
}
}