通过ActivityThread获取Context

背景

为了保存一个全局可用的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];
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • [TOC] 1 JAVA: String为什么这么设计 在源码中string是用final 进行修饰,它是不可更改...
    寄浮生阅读 871评论 0 0
  • 描述清点击 Android Studio 的 build 按钮后发生了什么 build[https://jueji...
    CHSmile阅读 631评论 0 1
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 126,160评论 2 7
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,120评论 0 4