Android性能优化汇总
热修复
阿里系:DeXposed。andfix
从底层C的二进制来入手的。
腾讯系:tinker
Java类加载机制来入手的。
一般的bug修复,都是等下一个版本解决,然后发布新的apk。
热修复: 可以直接在客户已经安装的程序当中修复bug。bug一般会出现在某个类的某个方法地方。我们需要动态地将客户手机里面的apk里面的某个类给替换成我们已经修复好的类。
public class PathClassLoader extends BaseDexClassLoader {
用来加载应用程序的dex
public class DexClassLoader extends BaseDexClassLoader {
可以加载指定的某个dex文件。(限制:必须要在应用程序的目录下面)
系统通过BaseDexClassLoader类 中的DexPathList,再通过DexPathList类中的Element[] dexElements,找到对应的第一个java类,从而进行类的加载;所以,我们只需把我们修改后的dex文件元素利用反射技术,插入到dexElements数组的中去,并在数组的前面。这样,程序在运行时,会先拿到我们修复的类,运行无异常,从而实现了修复
安卓java文件最终打包生产dex文件,我们修改bug后的java文件要编译成dex文件,然后去“替换”已经存在的dex文件中对应的那个类
通过tools文件夹下工具可以将java文件打包生产dex文件
public class FixDexUtils {
private static HashSet loadedDex = new HashSet();
static{
loadedDex.clear();
}
public static void loadFixedDex(Context context){
if(context == null){
return ;
}
//遍历所有的修复的dex
File fileDir = context.getDir(MyConstants.DEX_DIR,Context.MODE_PRIVATE);
File[] listFiles = fileDir.listFiles();
for(File file:listFiles){
if(file.getName().startsWith("classes")&&file.getName().endsWith(".dex")){
loadedDex.add(file);//存入集合
}
}
//dex合并之前的dex
doDexInject(context,fileDir,loadedDex);
}
private static void setField(Object obj,Class> cl, String field, Object value) throws Exception {
Field localField = cl.getDeclaredField(field);
localField.setAccessible(true);
localField.set(obj,value);
}
private static void doDexInject(final Context appContext, File filesDir,HashSet loadedDex) {
String optimizeDir = filesDir.getAbsolutePath()+File.separator+"opt_dex";
File fopt = new File(optimizeDir);
if(!fopt.exists()){
fopt.mkdirs();
}
//1.加载应用程序的dex
try {
PathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader();
for (File dex : loadedDex) {
//2.加载指定的修复的dex文件。
DexClassLoader classLoader = new DexClassLoader(
dex.getAbsolutePath(),//String dexPath,
fopt.getAbsolutePath(),//String optimizedDirectory,
null,//String libraryPath,
pathLoader//ClassLoader parent
);
//3.合并
Object dexObj = getPathList(classLoader);
Object pathObj = getPathList(pathLoader);
Object mDexElementsList = getDexElements(dexObj);
Object pathDexElementsList = getDexElements(pathObj);
//合并完成
Object dexElements = combineArray(mDexElementsList,pathDexElementsList);
//重写给PathList里面的lement[] dexElements;赋值
Object pathList = getPathList(pathLoader);
setField(pathList,pathList.getClass(),"dexElements",dexElements);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static Object getField(Object obj, Class> cl, String field)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field localField = cl.getDeclaredField(field);
localField.setAccessible(true);
return localField.get(obj);
}
private static Object getPathList(Object baseDexClassLoader) throws Exception {
return getField(baseDexClassLoader,Class.forName("dalvik.system.BaseDexClassLoader"),"pathList");
}
private static Object getDexElements(Object obj) throws Exception {
return getField(obj,obj.getClass(),"dexElements");
}
/**
* 两个数组合并
* @param arrayLhs
* @param arrayRhs
* @return
*/
private static Object combineArray(Object arrayLhs, Object arrayRhs) {
Class> localClass = arrayLhs.getClass().getComponentType();
int i = Array.getLength(arrayLhs);
int j = i + Array.getLength(arrayRhs);
Object result = Array.newInstance(localClass, j);
for (int k = 0; k < j; ++k) {
if (k < i) {
Array.set(result, k, Array.get(arrayLhs, k));
} else {
Array.set(result, k, Array.get(arrayRhs, k - i));
}
}
return result;
}
// [12345] [9876]
// [9876 12345]
}
MyTestClass 报错了,a= 0,a不能作为分母
public class MyTestClass {
public void testFix(Context context){
int i = 10;
int a = 0;
Toast.makeText(context, "shit:"+i/a, Toast.LENGTH_SHORT).show();
}
}
Activity中使用了MyTestClass
public class Lsn16Activity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.lsn16_acitivy);
}
public void test(View v) {
MyTestClass myTestClass = new MyTestClass();
myTestClass.testFix(this);
}
}
public void fix(View v) {
//目录:/data/data/packageName/odex
File fileDir = getDir(MyConstants.DEX_DIR, Context.MODE_PRIVATE);
//往该目录下面放置我们修复好的dex文件。
String name = "classes2.dex";
String filePath = fileDir.getAbsolutePath() + File.separator + name;
File file = new File(filePath);
if (file.exists()) {
file.delete();
}
//搬家:把下载好的在SD卡里面的修复了的classes2.dex搬到应用目录filePath
InputStream is = null;
FileOutputStream os = null;
try {
is = new FileInputStream(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + name);
os = new FileOutputStream(filePath);
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1){
os.write(buffer,0,len);
}
File f = new File(filePath);
if(f.exists()){
Toast.makeText(this ,"dex 重写成功", Toast.LENGTH_SHORT).show();
}
//热修复
FixDexUtils.loadFixedDex(this);
} catch (Exception e) {
e.printStackTrace();
}
}
public class MyTestClass {
public void testFix(Context context){
int i = 10;
int a = 1;
Toast.makeText(context, "shit:"+i/a, Toast.LENGTH_SHORT).show();
}
}
实际运用中通过网络请求,从服务器中获取dex文件,这里用点击事件fix()方法代替
Lsn16Activity