------2018-07-26更新-----
一些说明:
其实对于下文的拿Activity引用的方式我个人已经不推荐了。这里仅作为一种思路,同时解答一些类似Activity创建后,到底被哪个对象持有等类似的问题。
不关心废话,直接需要结果的,可以参考这里: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<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
......
}
ActivityThread里持有了一个Map,这个Map的Value是ActivityClientRecord,熟悉的朋友一定知道ActivityClientRecord是持有一个Activity对象的,然后相当于是一个Activity被一个Map持有。那mActivities的value很可能就是整个Activity列表。
先不急着反射,先debug看下。
太长了,拉到右边
DebugAllActivityActivity和ActivityThreadActivity刚好是两个Activity。反射那实例前还有个问题,当前是从Activity对象里的mMainThread对象的mActivities。实际中我们肯定是希望这个方法应该可以从Application对象里就可以调用,否则局限性太明显了。
继续debug看下Application都有什么属性。
很容易就发现了mLoadedApk持有ActivityThread@4446,而且两个都是@4446这表示是同一个实例,那剩下就简单了,一路反射过去拿到Map拿到ValueList。
private static List<Activity> getActivitiesByApplication(Application application) {
List<Activity> list = new ArrayList<>();
try {
Class<Application> 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<Object, Object> arrayMap = (Map<Object, Object>) mActivities;
for (Map.Entry<Object, Object> 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
网友评论