背景
為了保存一個全局可用的ApplicationContext對象,通過反射ActivityThread.currentActivityThread()來實現。近期在分析線上錯誤日志時,偶有發現這里會小概率死鎖,分析堆棧后發現問題出在“切換至主線程反射調用currentActivityThread()”時加的同步鎖這里,雖然最直接的方向是如何避免死鎖場景的出現,也就是不要用容易產生死鎖的調用方式,但可惜在我們的應用場景下這種調用方式是無法避免的,所以只能從別的方向入手,那為什么這里一定要切換至主線程調用,如果沒有這步操作,就不會有死鎖問題了,所以就從這里著手調查。
理論講解
https://zhuanlan.zhihu.com/p/26285030
根據上文中的分析,在低版本系統中,由于ActivityThread對象被保存在ThreadLocal變量中,所以必須在主線程調用 currentActivityThread()
方法才能獲取到,子線程下context為null。
通過閱讀 android.app.ActivityThread
的源碼,確認了從 API 18(Android 4.3)開始,ActivityThread對象由ThreadLocal變量改為普通的全局變量保存。經過測試,確實在API 18以下的機器上,子線程無法獲取到context,必須切換主線程,而從API 18開始,子線程也能正常獲取context,因此最優方案是在API 18以下的系統才做子線程到主線程的切換,其他情況下不用在意是否是主線程,而現在市面上用4.3以下系統的設備很少,這樣可以最大限度避免死鎖的隱患。
API 17的ActivityThread:
public static ActivityThread currentActivityThread() {
return sThreadLocal.get();
}
API 18的ActivityThread:
public static ActivityThread currentActivityThread() {
return sCurrentActivityThread;
}
示例代碼
private Context context;
public static Context getContext() {
if (context == null) {
try {
Object actThread = currentActivityThread();
if (actThread != null) {
Context app = ReflectHelper.invokeInstanceMethod(actThread, "getApplication");
if (app != null) {
context = app;
}
}
} catch (Throwable t) {
t.printStacktrace();
}
}
return context;
}
/** 獲取當前進程的ActivityThread對象 */
public static Object currentActivityThread() {
Object activityThread;
final ReflectHelper.ReflectRunnable<Void, Object> mainThreadAct = new ReflectHelper.ReflectRunnable<Void, Object>() {
public Object run(Void arg) {
try {
String clzName = ReflectHelper.importClass("android.app.ActivityThread");
return ReflectHelper.invokeStaticMethod(clzName, "currentActivityThread");
} catch (Throwable t) {
t.printStacktrace();
}
return null;
}
};
// 當前在主線程,或者系統版本>=18(Android 4.3)的子線程上,就直接在當前線程中獲取ActivityThread對象
if (Thread.currentThread().getId() == Looper.getMainLooper().getThread().getId() || Build.VERSION.SDK_INT >= 18) {
activityThread = mainThreadAct.run(null);
if (activityThread != null) {
return activityThread;
}
}
// 如果是在系統版本<18的子線程上,必須切換到主線程獲取ActivityThread對象
final Object lock = new Object();
final Object[] output = new Object[1];
synchronized (lock) {
UIHandler.sendEmptyMessage(0, new Handler.Callback() {
public boolean handleMessage(Message msg) {
synchronized (lock) {
try {
output[0] = mainThreadAct.run(null);
} catch (Throwable t) {
t.printStacktrace();
} finally {
try {
lock.notify();
} catch (Throwable t) {
t.printStacktrace();
}
}
}
return false;
}
});
try {
lock.wait();
} catch (Throwable t) {
t.printStacktrace();
}
}
return output[0];
}