編輯:關於android開發
上一篇博客中,我們通過介紹dex分包原理引出了Android的熱補丁技術,而現在我們將解決兩個問題。
1. 怎麼將修復後的Bug類打包成dex
2. 怎麼將外部的dex插入到ClassLoader中
activity_main.xml
MainActivity.class
package com.aitsuki.bugfix;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;
import com.aitsuki.bugfix.animal.Cat;
public class MainActivity extends AppCompatActivity {
private Cat mCat;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCat = new Cat();
}
public void click(View view) {
Toast.makeText(this, mCat.say(),Toast.LENGTH_SHORT).show();
}
}
Cat.class
package com.aitsuki.bugfix.animal;
/**
* Created by AItsuki on 2016/3/14.
*/
public class Cat {
public String say() {
return "汪汪汪!";
}
}
假設這是我們公司的開發項目,剛剛上線就發現了嚴重bug,貓會狗叫。
想修復bug,讓用戶再立刻更新一次顯然很不友好,此時熱補丁修復技術就有用了。
在加載dex的代碼之前,我們先來制作補丁。
1. 首先我們將Cat類修復,汪汪汪改成喵喵喵,然後重新編譯項目。(Rebuild一下就行了)
2. 去保存項目的地方,將Cat.class文件拷貝出來,在這裡
3. 新建文件夾,要和該Cat.class文件的包名一致,然後將Cat.class復制到這裡,如圖
vc281tC1xHRlc3TEv8K8o6zUy9DQ0rvPwsP8we6jrLTysPyyubahoaPI5828o7o8YnIgLz4NCjxpbWcgYWx0PQ=="這裡寫圖片描述" src="http://www.bkjia.com/uploads/allimg/160408/0414554559-4.png" title="\" />
然後test目錄是這樣的
patch_dex.jar就是我們打包好的補丁了,我們將它放到sdCard中,待會從這裡加載補丁。
關於什麼用這麼復雜的方法打包補丁的說明:
你也可以直接將java文件拷出來,通過javac -d帶包編譯再轉成jar。
但我這麼麻煩是有原因的,因為用這種方法你可能會遇到ParseException,原因是jar包版本和dx工具版本不一致。
而從項目中直接將編譯好的class直接轉成jar就沒問題,因為java會向下兼容,打出來的jar包和class版本是一致的。
總而言之,dx版本要和class編譯版本對應。
通過上一篇博文,我們知道dex保存在這個位置
BaseDexClassLoader–>pathList–>dexElements
加載外部dex,我們可以在Application中操作。
首先新建一個HotPatchApplication,然後在清單文件中配置,順便加上讀取sdcard的權限,因為補丁就保存在那裡。
HotPatchApplication代碼如下:
package com.aitsuki.hotpatchdemo;
import android.app.Application;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import dalvik.system.DexClassLoader;
/**
* Created by hp on 2016/4/6.
*/
public class HotPatchApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 獲取補丁,如果存在就執行注入操作
String dexPath = Environment.getExternalStorageDirectory().getAbsolutePath().concat("/patch_dex.jar");
File file = new File(dexPath);
if (file.exists()) {
inject(dexPath);
} else {
Log.e("BugFixApplication", dexPath + "不存在");
}
}
/**
* 要注入的dex的路徑
*
* @param path
*/
private void inject(String path) {
try {
// 獲取classes的dexElements
Class cl = Class.forName("dalvik.system.BaseDexClassLoader");
Object pathList = getField(cl, "pathList", getClassLoader());
Object baseElements = getField(pathList.getClass(), "dexElements", pathList);
// 獲取patch_dex的dexElements(需要先加載dex)
String dexopt = getDir("dexopt", 0).getAbsolutePath();
DexClassLoader dexClassLoader = new DexClassLoader(path, dexopt, dexopt, getClassLoader());
Object obj = getField(cl, "pathList", dexClassLoader);
Object dexElements = getField(obj.getClass(), "dexElements", obj);
// 合並兩個Elements
Object combineElements = combineArray(dexElements, baseElements);
// 將合並後的Element數組重新賦值給app的classLoader
setField(pathList.getClass(), "dexElements", pathList, combineElements);
//======== 以下是測試是否成功注入 =================
Object object = getField(pathList.getClass(), "dexElements", pathList);
int length = Array.getLength(object);
Log.e("BugFixApplication", "length = " + length);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
/**
* 通過反射獲取對象的屬性值
*/
private Object getField(Class cl, String fieldName, Object object) throws NoSuchFieldException, IllegalAccessException {
Field field = cl.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(object);
}
/**
* 通過反射設置對象的屬性值
*/
private void setField(Class cl, String fieldName, Object object, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = cl.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}
/**
* 通過反射合並兩個數組
*/
private Object combineArray(Object firstArr, Object secondArr) {
int firstLength = Array.getLength(firstArr);
int secondLength = Array.getLength(secondArr);
int length = firstLength + secondLength;
Class componentType = firstArr.getClass().getComponentType();
Object newArr = Array.newInstance(componentType, length);
for (int i = 0; i < length; i++) {
if (i < firstLength) {
Array.set(newArr, i, Array.get(firstArr, i));
} else {
Array.set(newArr, i, Array.get(secondArr, i - firstLength));
}
}
return newArr;
}
}
運行一下Demo,報以下錯誤。(AndroidStudio 2.0可能不會報錯,需要打包的時候才會出現錯誤,這是Instant run導致的)
dexElements的length = 2,看來我們的patch_dex已經成功添加進去了。
但是從黃色框框和黃色框上面那一段log提示中可以看出,MainActivity引用了Cat,但是發現他們在不同的Dex中。
看到這裡可能就會問:
為什麼之前那麼多項目都采用分包方案,但是卻不會出現這個錯誤呢?
我在這裡總結了一個過程,想知道詳細分析過程的請看QQ空間開發團隊的原文。
根據上面的第六條,我們只要讓所有類都引用其他dex中的某個類就可以了。
下面是QQ控件給出的解決方案
System.out.println(AntilazyLoad.class);
System.out.println(AntilazyLoad.class);
這行代碼,因為此時hack.dex還沒有加載進來,AntilazyLoad並不存在。 之所以選擇構造函數是因為他不增加方法數,一個類即使沒有顯式的構造函數,也會有一個隱式的默認構造函數。
其實整個熱補丁技術最難的地方不是原理,不是注入dex,而是字節碼的注入。
這需要我們隊Gradle構建腳本,Groovy語言有一定的了解。
Android App監聽軟鍵盤按鍵的三種方式與改變軟鍵盤右下角確定鍵樣式,androidappactionNone : 回車鍵,按下後光標到下一行actionGo :
完整項目:木子記事本(原創,轉載請注明出處)。,轉載出處 一個較為完整的記事本App,記錄,存儲到數據庫,編輯,保存。 運行效果圖: Activity類:
Linux內核系列—操作系統開發之保護模式的優勢,linux內核在上一篇中我們雖然成功進入了保護模式,但是並沒有體驗到保護模式帶給我們的便利。其實在保護模式下尋址空間可以
Android界面架構(Activity,PhoneWiondow,DecorView)簡介,activitydecorview 在一個Android