ActivityTaskView: 直观的 Activity 任务栈和 LaunchMode 分析工具

2,035 阅读5分钟
原文链接: www.jianshu.com

前言

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任务栈,像下图这样:


singletask

原理

其实很简单,只要知道 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{

    private static final int[] COLORS = {
            Color.GREEN,//onCreate
            Color.YELLOW,//onStart
            Color.RED,//onResume

            Color.WHITE,//onPause
            Color.GRAY,//onStop
            Color.BLACK//onDestroy
    };

    public ObserverTextView(Context context) {
        super(context);
    }

    public ObserverTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ObserverTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void update(Observable o, Object arg){
        Pair<Integer, Integer> pair = (Pair<Integer, Integer>) arg;
        if(pair.second == (int)getTag()) {
            setTextColor(COLORS[pair.first]);
        }
    }

}

ActivityLifecycleObservable是被观察者也就是事件源,在ActivityLifecycleCallbacks回调中调用lifecycleChange方法,通知ObserverTextView并把数据传递过去

class ActivityLifecycleObservable extends Observable {

        /**
         * when activity lifecycle changed, notify the observer
         * @param pair lifecycle:0-5/activityId:hashCode
         */
        void lifecycleChange(Pair<Integer, Integer> pair){
            setChanged();
            notifyObservers(pair);
        }
    }

使用Observable的 addObserver() 方法注册监听。

LaunchMode分析

有了这个工具,分析Activity的LaunchMode就很直观了,一图胜千言。
红色代表Activity的onResume状态,白色是onPause状态,灰色是onStop状态。

standard mode

标准模式,启动直接加到栈顶,销毁后移除。


standard

singletop mode

栈顶唯一,如果栈顶存在就不会重复启动,保证栈顶不会有两个相同的Activtiy


singletop

singletask mode

栈内唯一,如果栈内存在,再次启动时会自动把它上面的其他Activity全部清除(调用onDestroy)


singletask

singleinstance mode

独占一栈,启动时会建立新栈切换过去,如果启动了普通Activity又会切换回原来的共享栈(新栈仍然存在,会在栈内唯一的Activity结束时关闭)


singleinstance

dialog style activity

对话框样式的Activity,启动后上一个Activity仍可见(处于onPause状态)


dialog

用来分析你的项目

demo项目展示了不同的LaunchMode,可以代表开发中的大部分情况了。尽管如此,当你的项目中Activity很多,有时你都不知道当前是什么页面(这完全有可能),或者你手速太快一下点出好多页面,使用这个工具就可以直观地展示所有页面了。

1) 在模块的build.gradle中添加依赖

compile 'cc.rome753:activitytaskview:0.8.5'

2) 在主模块的AndroidManifest.xml中添加权限

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

3) 在你的Application的继承类的onCreate()方法中添加初始化方法

@Override
public void onCreate() {
    super.onCreate();
    ActivityTask.init(this, BuildConfig.DEBUG);
}

一句代码即可使用,传入BuildConfig.DEBUG是为了Release版本中不显示,只测试的话传 true 就可以。

Github地址

工具和demo的源码都在这里

github.com/rome753/Act…