AndroidのAPKの強化 - メモリ負荷のdex
施工方法の分析DexClassLoader
ソースコードを表示するには、ウェブサイトをAndroidXrefすることができます
見つかったコードを見て、DexClassLoaderは、親クラスのコンストラクタを呼び出しますBaseDexClassLoader
親源を観察し続けて、親クラスの名前をクリックします
コア機能DexPathListで構成発見、閲覧し続けます
観察された、DexPathList構造は、makeDexElements、要素のセットを作成するようなメソッドがあります
とともに
メソッドが呼び出された場合LoadDexFileサフィックスは、DEXであるかどうかを判断するための機能
これはその名のサフィックスの別の定義であります
LoadDexFileを観察し続けます
OptimizedPathForメソッドのパラメータは、DEXファイルをロードするパス法にoptimizedDirectory発見されていき、loadDexメソッドDexFileクラスです
loadDexはDexFileがDexFileオブジェクトパラメータは、ソースファイルと出力ファイルです戻りました
構造を表示し続けるDexFile
我々はコンストラクタDexFileクラスはメソッドデックス関連するopenDexFileとロードファイルを持っていました。表示され続けます
openDexFIleがネイティブメソッドに戻ったが見つかりました。どの部分がC ++で実装されています。
を続行
要約すると:
1.DexClassLoaderは、親クラスのコンストラクタは、コア機能DexPathListを持っていBaseDexClassLoader親クラスのコンストラクタを呼び出します
2.コア機能DexPathList構造、makeDexElements、要素のセットを作成するようなメソッドは、あります
3.makeDexElementsこの機能は、メソッドが呼び出された場合LoadDexFileサフィックスは、DEXであるかどうかを決定します
シングルパス方式に方法optimizedDirectory LoadDexFile optimizedPathForパラメータ4.、及びファイルをロードする方法はloadDex DEX方法DexFileクラスであり、
loadDex 5.DexFileはDexFileオブジェクトを返し、オブジェクトパラメータは、ソースファイルと出力ファイルがあり、
6.跟入发现DexFile类的构造方法中又有个方法openDexFile和加载Dex文件有关联。
7.发现openDexFIle返回了个native方法。其实现部分是在C++中。
简述:
makDexELements判断了4种文件类型,dex/jar/zip/apk,所以android中能够动态加载的构造方法中,就这四种。
DexClassLoader跟到最后发现最核心功能是openDexFile,native层的,传递文件字节码,返回值是一个虚拟机的cookie值(java层)(C++层是pDexOrJar的指针)
分析DexClassLoader的loadClass方法
由于我们使用的是DexClassLoader,继承自BaseDexClassLoader,而查看findClass方法在ClassLoader的源码是必须要实现的,所以应该看BaseDexClassLoader的重写方法。
发现其中有查找类的方法。调用findClass的pathList对象是在BaseDexClassLoader构造中创建的。
继续分析DexPathList的findClass方法
发现返回类的对象代码,是DexFile中的loadClassBinaryName
loadClassBinaryName方法中调用了defineClass,其中参数是名称,类加载器,cookie值,cookie值是DexFile中的openDexFile方法的返回值。
defineClass方法是个native方法
流程:
1.ClassLoader.loadClass 方法
2.BaseDexClassLoader.findClass方法
3.DexPathList.findClass方法
4.DexFile.loadClassBinaryName方法
5.DexFile.defineClass,Native方法
与DexClassLoad的构造结合起来,可以看到,DexFile这个类是加载类的关键,在DexClassLoader的构造方法中,最后调用的openDexFIle方法,返回dalvik虚拟机中的一个cookie值,这个值正是loadClasss方法最后调用的defineClass的参数。
编写自己的DexClassLoader
思路:
0.创建一个DexClassLoader的子类
1.创建构造方法,加入参数byte[]
2.使用反射调用openDexFile,获取mCookie
3.重写loadClass,使用反射调用defineClass
1.因为想要一个带有传入参数byte[]的DexClassLoader,所以新创建一个DexClassLoader子类MyDexClassLoader,创建构造方法加入参数byte[]。
public MyDexClassLoader(byte bytes[],
String dexPath,
String optimizedDirectory,
String librarySearchPath,
ClassLoader parent) {
super(dexPath, optimizedDirectory, librarySearchPath, parent);
createDexClassLoader(bytes,parent);
}
2.使用反射调用openDexFile,获取mCookie。
为了方便,封装了一个方法,实现调用openDexFile的逻辑,这个逻辑就是创建自己的DexClassLoader的逻辑。另外定义了两个变量,,存放cookie和parentclassLoadr。
因为openDexFile方法是在DexFile类里面,所以代码的逻辑应该是先获取DexFile类,再获取openDexFile,然后调用。
private ClassLoader mClassLoader;
private int mCookie;
private void createDexClassLoader(byte[] bytes, ClassLoader parent) {
// android 4.1 DexFile.openDexFile(byte[])
mClassLoader = parent;
try {
// 1. 获取 DexFile 类类型
Class clz = Class.forName("dalvik.system.DexFile");
// 2. 获取 openDexFile 方法对象
Method method = clz.getDeclaredMethod("openDexFile",byte[].class);
// 3. 调用方法,返回 cookie
method.setAccessible(true);
mCookie = (int) method.invoke(null,new Object[]{bytes});
} catch (Exception e) {
e.printStackTrace();
}
}
3.重写loadClass
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// android 4.1 DexFile.defineClass(String name, ClassLoader loader, int cookie)
Class c = null;
try {
// 获取加载的类信息
Class dexFile = Class.forName("dalvik.system.DexFile");
// 获取静态方法
Method method = dexFile.getDeclaredMethod("defineClass", String.class, ClassLoader.class, int.class);
method.setAccessible(true);
// 调用
c = (Class)method.invoke(null,name, mClassLoader, mCookie);
return c;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return super.loadClass(name);
}
完整的MyDexClassLoader.java
package com.bluelesson.mydexclassloader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
import dalvik.system.DexFile;
public class MyDexClassLoader extends DexClassLoader {
public MyDexClassLoader(byte bytes[],
String dexPath,
String optimizedDirectory,
String librarySearchPath,
ClassLoader parent) {
super(dexPath, optimizedDirectory, librarySearchPath, parent);
createDexClassLoader(bytes,parent);
}
private ClassLoader mClassLoader;
private int mCookie;
private void createDexClassLoader(byte[] bytes, ClassLoader parent) {
// android 4.1 DexFile.openDexFile(byte[])
mClassLoader = parent;
try {
// 1. 获取 DexFile 类类型
Class clz = Class.forName("dalvik.system.DexFile");
// 2. 获取 openDexFile 方法对象
Method method = clz.getDeclaredMethod("openDexFile",byte[].class);
// 3. 调用方法,返回 cookie
method.setAccessible(true);
mCookie = (int) method.invoke(null,new Object[]{bytes});
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// android 4.1 DexFile.defineClass(String name, ClassLoader loader, int cookie)
Class c = null;
try {
// 获取加载的类信息
Class dexFile = Class.forName("dalvik.system.DexFile");
// 获取静态方法
Method method = dexFile.getDeclaredMethod("defineClass", String.class, ClassLoader.class, int.class);
method.setAccessible(true);
// 调用
c = (Class)method.invoke(null,name, mClassLoader, mCookie);
return c;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return super.loadClass(name);
}
}
使用MyDexClassLoader
为了测试方便,回顾之前的例子,动态加载Activity是比较简单,我们可以把原先动态加载activity例子中,创建ClassLoader改为创建自己的MyDexClassLoader,加载Activity时,调用自己的loadClass。
首先新建一个简单的activity,然后编译代码,将Main2Activity生成的calss文件转为dex文件。然后将文件复制到项目中的assets目录中,名为m2a.dex
先整理下思路,因为是内存中加载dex,所以把assets目录中的dex文件读取到byte数组中即可,然后创建自己的MyDexClassLoader,在获取类型。步骤如下:
1.读取文件,返回数组地址
2.创建dex文件的类加载器,返回DexClassLoader对象
3.使用loadClass获取加载的类信息
4.创建Intent,启动Activity
根据思路开始写代码。
封装asset目录读取文件的方法
byte[] getdexFromAssets(String dexName){
// 获取assets目录管理器
AssetManager as = getAssets();
// 合成路径
String path = getFilesDir() + File.separator + dexName;
Log.i(TAG, path);
try {
// 创建文件流
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 打开文件
InputStream is = as.open(dexName);
// 循环读取文件,拷贝到对应路径
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return out.toByteArray();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return null;
}
然后就可以用我们的MyDexLoader载入读取到的数据,并运行它。
1.读取文件到内存
2.用自己的MyDexLoader加载
3.加载载入的m2a .dex里面的类
4.替换ClassLoader
5.启动
public void btnClick(View view) {
// 1. 获取dex字节数组
byte bytes[] = getdexFromAssets("m2a.dex");
// 2. 加载dex,返回dexClassLoader对象
MyDexClassLoader dex = new MyDexClassLoader(bytes,getPackageCodePath(),
getCacheDir().toString(),null,getClassLoader()
);
// 3. 加载类
Class clz = null;
try {
clz = dex.loadClass("com.bluelesson.mydexclassloader.Main2Activity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 4. 替换ClassLoader
replaceClassLoader1(dex);
// 5. 启动activity
startActivity(new Intent(this,clz));
}
完整的MainActivity.java
package com.bluelesson.mydexclassloader;
import android.content.Intent;
import android.content.res.AssetManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "15pb-log";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void btnClick(View view) {
// 1. 获取dex字节数组
byte bytes[] = getdexFromAssets("m2a.dex");
// 2. 加载dex,返回dexClassLoader对象
MyDexClassLoader dex = new MyDexClassLoader(bytes,getPackageCodePath(),
getCacheDir().toString(),null,getClassLoader()
);
// 3. 加载类
Class clz = null;
try {
clz = dex.loadClass("com.bluelesson.mydexclassloader.Main2Activity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 4. 替换ClassLoader
replaceClassLoader1(dex);
// 5. 启动activity
startActivity(new Intent(this,clz));
}
byte[] getdexFromAssets(String dexName){
// 获取assets目录管理器
AssetManager as = getAssets();
// 合成路径
String path = getFilesDir() + File.separator + dexName;
Log.i(TAG, path);
try {
// 创建文件流
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 打开文件
InputStream is = as.open(dexName);
// 循环读取文件,拷贝到对应路径
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return out.toByteArray();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return null;
}
public void replaceClassLoader1(DexClassLoader dexClassLoader){
try {
// 1. 获取ActivityThead类对象
// android.app.ActivityThread
// 1.1 获取类类型
Class clzActivityThead = Class.forName("android.app.ActivityThread");
// 1.2 获取类方法
Method currentActivityThread = clzActivityThead.getMethod("currentActivityThread",new Class[]{});
// 1.3 调用方法
currentActivityThread.setAccessible(true);
Object objActivityThread = currentActivityThread.invoke(null);
// 2. 通过类对象获取成员变量mBoundApplication
//clzActivityThead.getDeclaredField()
Field field = clzActivityThead.getDeclaredField("mBoundApplication");
// AppBindData
field.setAccessible(true);
Object data = field.get(objActivityThread);
// 3. 获取mBoundApplication对象中的成员变量info
// 3.1 获取 AppBindData 类类型
Class clzAppBindData = Class.forName("android.app.ActivityThread$AppBindData");
// 3.2 获取成员变量info
Field field1 = clzAppBindData.getDeclaredField("info");
// 3.3 获取对应的值
//LoadedApk
field1.setAccessible(true);
Object info = field1.get(data);
// 4. 获取info对象中的mClassLoader
// 4.1 获取 LoadedApk 类型
Class clzLoadedApk = Class.forName("android.app.LoadedApk");
// 4.2 获取成员变量 mClassLoader
Field field2 = clzLoadedApk.getDeclaredField("mClassLoader");
field2.setAccessible(TRUE); // 5替换クラスローダ field2.set(情報、dexClassLoader); }キャッチ(例外e){ e.printStackTrace(); } } }
エラーのバージョン4.4を実行した後:そのような方法は存在しない、テストは4.1で置き換えることができます。
概要
表面上のこの実験はまだ資産にm2a.dexファイルフォルダが存在するように、しかし、この全体のプロセスは、メモリにロードされた最初のm2a.dexにある(ロードM2A。デックスのDEXデータは専用メモリを作るために存在します)その後、読み出しMyDexClassLoader使用しています。つまり、限り、他の方法を用いて、DEXデータメモリは、直接MyDexClassLoaderデータを読み取るために使用することができるように、DEXファイルなしで使用することができる資産フォルダです。メモリから動的にロードされたDEXのAPK補強の目的を達成するために。