背景
为了保存一个全局可用的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];
}