利用反射拿到Android的整个Activity栈。

转自https://www.jianshu.com/p/ac0b237bac03

利用反射拿到Android的整个Activity栈。

利用反射拿到Android的整个Activity栈。_第1张图片 
Jerry2015  595a1b60 08f6 4beb 998f 2bf55e230555  关注
2016.11.21 21:25*  字数 763  阅读 4306 评论 6 赞赏 1

不关心废话,直接需要结果的,可以参考这里:https://github.com/aesean/ActivityStack
由于各种各样的原因,我们项目里部分地方需要用到获取当前App的所有Activity列表。我们之前的做法是比较简单粗暴的,就是Application里维护一个Activity的弱引用List,每次创建Activity的时候把Activity的实例Add到List里。直觉告诉我这样写很不好,但是项目里用这种方式代码也一直跑的好好的没出什么问题,泄漏的地方该处理的也都处理了。直觉告诉我,Framework里肯定某个地方持有了所有的Activity。类似android.app.FragmentManager并没有像supportFragmentManager那样提供getFragments方法,但是我们知道其实只是android.app.FragmentManager没有把mActive public而已(原因就只有Google自己知道了),自己反射是很容易拿到mActive引用的。

最近在看Framewok层源码的时候看到这样一个东西。

   public final class ActivityThread {
       ......
       final ArrayMap mActivities = new ArrayMap<>();
       ......
   }

ActivityThread里持有了一个Map,这个Map的Value是ActivityClientRecord,熟悉的朋友一定知道ActivityClientRecord是持有一个Activity对象的,然后相当于是一个Activity被一个Map持有。那mActivities的value很可能就是整个Activity列表。
先不急着反射,先debug看下。

利用反射拿到Android的整个Activity栈。_第2张图片
Debug

太长了,拉到右边

利用反射拿到Android的整个Activity栈。_第3张图片
Debug

AllActivityActivity和ActivityThreadActivity刚好是两个Activity。反射那实例前还有个问题,当前是从Activity对象里的mMainThread对象的mActivities。实际中我们肯定是希望这个方法应该可以从Application对象里就可以调用,否则局限性太明显了。
继续debug看下Application都有什么属性。

利用反射拿到Android的整个Activity栈。_第4张图片
Debug

很容易就发现了mLoadedApk持有ActivityThread@4446,而且两个都是@4446这表示是同一个实例,那剩下就简单了,一路反射过去拿到Map拿到ValueList。

    private static List getActivitiesByApplication(Application application) {
        List list = new ArrayList<>();
        try {
            Class applicationClass = Application.class;
            Field mLoadedApkField = applicationClass.getDeclaredField("mLoadedApk");
            mLoadedApkField.setAccessible(true);
            Object mLoadedApk = mLoadedApkField.get(application);
            Class mLoadedApkClass = mLoadedApk.getClass();
            Field mActivityThreadField = mLoadedApkClass.getDeclaredField("mActivityThread");
            mActivityThreadField.setAccessible(true);
            Object mActivityThread = mActivityThreadField.get(mLoadedApk);
            Class mActivityThreadClass = mActivityThread.getClass();
            Field mActivitiesField = mActivityThreadClass.getDeclaredField("mActivities");
            mActivitiesField.setAccessible(true);
            Object mActivities = mActivitiesField.get(mActivityThread);
            // 注意这里一定写成Map,低版本这里用的是HashMap,高版本用的是ArrayMap
            if (mActivities instanceof Map) {
                @SuppressWarnings("unchecked")
                Map arrayMap = (Map) mActivities;
                for (Map.Entry entry : arrayMap.entrySet()) {
                    Object value = entry.getValue();
                    Class activityClientRecordClass = value.getClass();
                    Field activityField = activityClientRecordClass.getDeclaredField("activity");
                    activityField.setAccessible(true);
                    Object o = activityField.get(value);
                    list.add((Activity) o);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            list = null;
        }
        return list;
    }

方法设置为了private,是否需要处理持有Activity导致的回收问题,是否要包装成弱引用List,那就看你自己的需要了。
当然最后有个问题,我只是在5.1和7.0的手机上测试了下没问题,因为是反射,并不是PublicApi,低版本是否有不一样的实现,请自行测试。另外还有不同的启动模式是否会有影响,也请自行测试。
上面反射的过程比较复杂,代码非常杂乱,所以写个工具类,简化下反射过长。具体参考这里。
https://github.com/aesean/ActivityStack/blob/master/app/src/main/java/com/aesean/activitystack/utils/ReflectUtils.java
简化后可以直接使用这个类进行反射获取。
https://github.com/aesean/ActivityStack/blob/master/app/src/main/java/com/aesean/activitystack/utils/ApplicationUtils.java

另外更加详细的参考这里:
http://blog.csdn.net/qinjuning/article/details/7262769
和这里:
http://blog.csdn.net/xieqibao/article/details/6570080

你可能感兴趣的:(利用反射拿到Android的整个Activity栈。)