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

0.856字数 1519阅读 1688

前言

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.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

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


standard.gif

singletop mode

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


singleTop.gif

singletask mode

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


singleTask.gif

singleinstance mode

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


singleInstance.gif

dialog style activity

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


dialogStyle.gif

用来分析你的项目

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

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

compile 'cc.rome753:activitytaskview:1.0.0'

  1. 在主模块的AndroidManifest.xml中添加权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
  1. 在你的Application的继承类的onCreate()方法中添加初始化方法
@Override
public void onCreate() {
    super.onCreate();
    ActivityTask.init(this, BuildConfig.DEBUG);
}

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

Github地址

源码在这里,欢迎Fork和Star

https://github.com/rome753/ActivityTaskView

推荐阅读更多精彩内容