ActivityTaskView: 直观的Activity任务栈
前言
Activity是安卓开发中最重要的元素,因为APP绝大部分使用都是操作它。某个应用的Activity都是放在一个或多个任务栈中,有两种方法可以查看任务栈和栈中的活动。
- ADB命令
adb shell dumpsys activity activities
该方法可以获得手机中所有活动的详细数据,然而要从中找到你想分析的活动有点麻烦,而且必须连着电脑。
- ActivityManager
ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> runningTaskInfoList = am.getRunningTasks(10);
for (RunningTaskInfo runningTaskInfo : runningTaskInfoList) {
log("id: " + runningTaskInfo.id);
log("description: " + runningTaskInfo.description);
log("number of activities: " + runningTaskInfo.numActivities);
log("topActivity: " + runningTaskInfo.topActivity);
log("baseActivity: " + runningTaskInfo.baseActivity.toString());
}
该方法只能获取到任务栈的栈顶和栈底的活动,操作起来也麻烦。
总之,目前还没有一种方法能直观地观察Activity任务栈,像下图这样:
![](https://img.haomeiwen.com/i1896166/94699929f7cee963.gif)
原理
其实很简单,只要知道 Android4.0 以后Application支持ActivityLifecycleCallbacks这样一个回调。
ActivityLifecycleCallbacks
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
在Application中注册这个回调,就能监听到所有Activity的生命周期了,再也不用往一个个Activity的生命周期方法里面加log了,在这个回调里统一搞定。
有了回调监听,就可以从APP启动开始,管理建立的每一个Activity,而Activity的getTaskId()方法可以获取到这个Activity属于哪个任务栈。
Activity和任务栈都有了,后面只是想个方法展示的问题。
悬浮窗
要在开发中直观、动态地展示任务栈,同时不能影响当前页面,使用悬浮窗是最好的方法。
WindowManager windowManager = (WindowManager)app.getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_PHONE;
params.format = PixelFormat.RGBA_8888;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.gravity = Gravity.START | Gravity.TOP;
params.x = 0;
params.y = app.getResources().getDisplayMetrics().heightPixels;
windowManager.addView(activityTaskView, params);
添加悬浮窗用这个方法就可以了,别忘了加上权限。
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
再加上悬浮窗触摸移动。
float mInnerX;
float mInnerY;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mInnerX = event.getX();
mInnerY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float x = event.getRawX();
float y = event.getRawY();
WindowManager.LayoutParams params = (WindowManager.LayoutParams) getLayoutParams();
params.x = (int) (x - mInnerX);
params.y = (int) (y - mInnerY);
((WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE)).updateViewLayout(this, params);
break;
}
return true;
}
ActivityTaskView的视图
这个视图要展示一个或多个任务栈,每个栈顶里面有若干个活动,栈和活动都要动态添加和删除。那么用一个横向的LinearLayout来放栈,一个竖向的LinearLayout来放栈中的活动,每个活动名用一个TextView来展示就可以了。
在onCreate回调方法中取得activity的任务栈id,没有就创建一个LinearLayout,有就创建一个TextView放到LinearLayout中;
在onDestroy回调方法中同样取得activity的任务栈id,找到对应的LinearLayout,从中把这个activity移除,如果LinearLayout没有子View了,就把它本身从ActivityTaskView中移除。
正常情况下,根据ActivityLifecycleCallbacks中的回调监听来动态添加和删除Activity,能准确地表示当前APP的活动栈。APP被异常杀死除外。
ObserverTextView
Activity是有不同生命周期的,当前永远只有一个onResume状态的活动,即栈顶活动,也就是你手机当前展示的页面。给表示Activity的TextView设置不同颜色就能代表不同生命周期了。
每个TextView的颜色状态可以放到集合里统一管理,也可以由每个TextView自己来管理。为了解耦提高内聚性这里选择后者。
使用Java自带的观察者模式,ObserverTextView继承TextView并实现Observer接口,另外一个ActivityLifecycleObservable类继承Observable。
这样ObserverTextView就成为观察者,观察Activity生命周期变化的事件,来改变自己的颜色
public class ObserverTextView extends TextView implements Observer{
public ObserverTextView(Context context) {
this(context, null);
}
public ObserverTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ObserverTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setTextSize(12);
setTextColor(Color.WHITE);
setMaxLines(1);
setBackgroundColor(Color.parseColor("#33AAAAAA"));
}
@Override
public void update(Observable o, Object arg){
ActivityTask.ColorInfo info = (ActivityTask.ColorInfo) arg;
if(info.getActivityId() == (int)getTag()) {
setTextColor(info.getColor());
}
}
}
ActivityLifecycleObservable是被观察者也就是事件源,在ActivityLifecycleCallbacks回调中调用lifecycleChange方法,通知ObserverTextView并把数据传递过去
static class ActivityLifecycleObservable extends Observable {
/**
* when activity lifecycle changed, notify the observer
*
* @param info ColorInfo
*/
void lifecycleChange(ColorInfo info) {
setChanged();
notifyObservers(info);
}
}
使用Observable的 addObserver() 方法注册监听。
延时消息队列(v1.0.0版本新增)
很多时候Activity的生命周期转换太快,比如从onStart到onPause,或从一个Activity的onPause到另一个Activity的onResume,如果实时把这些变化反映到ActivityTaskView上,就很难看清中间的变化过程。因此在新版本中添加了一个延时消息队列,思路如下:
生命周期产生时,先将对应的信息添加到一个Queue队列中,用一个Handler从队列中取消息,如果本次取消息距上一次取消息的间隔时间小于规定DELAY,那么就等待一段时间重新取。满足时间间隔才把生命周期反映到界面上。
static class QueueHandler extends Handler{
private Queue<Object> queue;
private long lastTime;
public QueueHandler(){
super(Looper.getMainLooper());
lastTime = 0;
queue = new LinkedList<>();
}
public void add(Object o){
queue.add(o);
sendEmptyMessage(0);
}
@Override
public void handleMessage(Message msg) {
if(System.currentTimeMillis() - lastTime < DELAY){
sendEmptyMessageDelayed(0, DELAY / 5);
}else {
Object obj = queue.poll();
if(obj instanceof TaskInfo){
TaskInfo taskInfo = (TaskInfo) obj;
if(taskInfo.isOnCreate()){
activityTaskView.add(taskInfo);
activityLifecycleObservable.lifecycleChange(new ColorInfo(COLORS[0], taskInfo.activityId));
}else{
activityTaskView.remove(taskInfo);
}
lastTime = System.currentTimeMillis();
}else if(obj instanceof ColorInfo){
activityLifecycleObservable.lifecycleChange((ColorInfo) obj);
lastTime = System.currentTimeMillis();
}
}
}
}
LaunchMode分析
有了这个工具,分析Activity的LaunchMode就很直观了,一图胜千言。
红色代表Activity的onResume状态,白色是onPause状态,灰色是onStop状态。
standard mode
标准模式,启动直接加到栈顶,销毁后移除。
![](https://img.haomeiwen.com/i1896166/210a9a551ffab54c.gif)
singletop mode
栈顶唯一,如果栈顶存在就不会重复启动,保证栈顶不会有两个相同的Activtiy
![](https://img.haomeiwen.com/i1896166/4d6150c0d9a947df.gif)
singletask mode
栈内唯一,如果栈内存在,再次启动时会自动把它上面的其他Activity全部清除(调用onDestroy)
![](https://img.haomeiwen.com/i1896166/49db88012bbc36eb.gif)
singleinstance mode
独占一栈,启动时会建立新栈切换过去,如果启动了普通Activity又会切换回原来的共享栈(新栈仍然存在,会在栈内唯一的Activity结束时关闭)
![](https://img.haomeiwen.com/i1896166/ecad63efe81f10d8.gif)
dialog style activity
对话框样式的Activity,启动后上一个Activity仍可见(处于onPause状态)
![](https://img.haomeiwen.com/i1896166/538d3d530f8cd0d6.gif)
用来分析你的项目
demo项目展示了不同的LaunchMode,可以代表开发中的大部分情况了。尽管如此,当你的项目中Activity很多,有时你都不知道当前是什么页面(这完全有可能),或者你手速太快一下点出好多页面,使用这个工具就可以直观地展示所有页面了。
- 在模块的build.gradle中添加依赖
compile 'cc.rome753:activitytaskview:1.0.0'
- 在主模块的AndroidManifest.xml中添加权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
- 在你的Application的继承类的onCreate()方法中添加初始化方法
@Override
public void onCreate() {
super.onCreate();
ActivityTask.init(this, BuildConfig.DEBUG);
}
一句代码即可使用,传入BuildConfig.DEBUG是为了Release版本中不显示,只测试的话传 true 就可以。
Github地址
源码在这里,欢迎Fork和Star