Android5.1 -Recents分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013656135/article/details/49686425
    Android使用RecentsActivity来展示最近使用过的app。至于此界面,不同品牌的Android智能手机,可能界面都会有所不一样。以Android 5.1的RecentsActivity界面为例,原始风格也跟以往的版本有一些不一样(如图),似乎比以往的界面都要炫酷一些。不过从Android 5.0开始,Recents就有了诸多变化,比如:Android4.4 在SystemUI中并没有Recents文件结构。
                     

    要触发Recents界面,从目前的手机使用来看,大致可以分为两种:
    1)长按HOME键触发Recents界面;
    2)通过点击NavigationBar上的图标(也相当于一个键)来触发Recents界面。
             
    以长按HOME键触发Recents界面为例,来学习和分析Android5.1源码中Recents显示流程,同时也作为记录,方面以后参考。
    Recents界面是个Activity,并且属于SystemUI,既然是属于SystemUI,那就是说Recents界面正常情况下都是在手机开机进入系统时就开始初始化。事实上也确实如此,在手机开机进入锁屏或者Launcher界面以后,触发Recents界面并不会执行onCreate()方法,只是执行onStart()方法。可见通常情况下RecentsActivity被隐藏起来了,这点,我刚开始时候也有点颠覆了我对activity的认知,不过经过后面看代码也就释然了,RecentsActivity是一个比较特殊的activity,framework层会对它做特殊处理:RecentsActivity在Manifest文件中是这么声明的:
<activity android:name=".recent.RecentsActivity"
                android:label="@string/accessibility_desc_recent_apps"
                android:theme="@style/RecentsStyle"
                android:excludeFromRecents="true"
                android:launchMode="singleInstance"
                android:resumeWhilePausing="true"
                android:exported="true">
          <intent-filter>
            <action android:name="com.android.systemui.TOGGLE_RECENTS" />
          </intent-filter>
 </activity>
 真是不同于常用的activity。
 excludeFromRecents 属性表示:
    Indicates that an Activity should be excluded from the list of recently launched activities。(大致含义:表示此activity不会被列在Recents列表内)
 resumeWhilePausing 属性表示:
    Indicate that it is okay for this activity be resumed while the previous activity is in the process of pausing, without waiting for the previous pause to complete. Use this with caution: your activity can not acquire any exclusive resources (such as opening the camera or recording audio) when it launches, or it may conflict with the previous activity and fail.(大致含义:表示此activity能被Resume,尽管前一个activity仍在pause的过程中,不用等它pause完成,使用了此属性,在此activity启动的时候,不能得到任何独立资源(如照相机和录音等)...)。
    RecentsActivity是如何被隐藏的?隐藏的时候会调用PhoneStatusBar.java中的onVisibilityChanged()方法:    
  @Override
    public void onVisibilityChanged(boolean visible) {
        // Update the recents visibility flag
        if (visible) {
            mSystemUiVisibility |= View.RECENT_APPS_VISIBLE;
        } else {
            mSystemUiVisibility &= ~View.RECENT_APPS_VISIBLE;
        }
        notifyUiVisibilityChanged(mSystemUiVisibility);
    }
    通过View中的一个FLAG来控制它的visibility。不光如此,ActivityManagerService也会对它做特殊处理,管理Recents,大部分工作都是ActivityManagerService管理activity的TASK。ActivityRecord会对它做判断,与excludeFromRecents 属性相对应。
 boolean isRecentsActivity() {
        return mActivityType == RECENTS_ACTIVITY_TYPE;
    }
    关于RecentsActivity先就了解这些,下面跟一下触发Recents的流程,流程用一张简约的流程图来描述可能更加直观一些。既然是长按HOME键来触发Recents,那么肯定是从framework层按键消息分发开始-PhoneWindowManager.java->interceptKeyBeforeDispatching();    
 
    上面的流程图可能显得比较粗糙,但是大致可以看出触发Recents时先PreLoadRecents,完成以还要执行toggleRecentApps(),这个的流程跟上面图中基本一样。
都是通过AIDL远程通信,在SystemUI这边通过接口,实现对应的回调来完成。toggleRecentApps()最终会走到AlternateRecentsComponent.java中的toggleRecentsActivity()->startRecentsActivity()->startAlternateRecentsActivity()方法。
      ...
    final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
    public final static String sRecentsPackage = "com.android.systemui";
    public final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
        ...
      /** Starts the recents activity */
    void startAlternateRecentsActivity(ActivityManager.RunningTaskInfo topTask,
            ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
            TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
        // Update the configuration based on the launch options
        mConfig.launchedFromHome = fromSearchHome || fromHome;
        mConfig.launchedFromSearchHome = fromSearchHome;
        mConfig.launchedFromAppWithThumbnail = fromThumbnail;
        mConfig.launchedToTaskId = (topTask != null) ? topTask.id : -1;
        mConfig.launchedWithAltTab = mTriggeredFromAltTab;
        mConfig.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
        mConfig.launchedNumVisibleTasks = vr.numVisibleTasks;
        mConfig.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
        mConfig.launchedHasConfigurationChanged = false;
        Intent intent = new Intent(sToggleRecentsAction);
        intent.setClassName(sRecentsPackage, sRecentsActivity);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
        if (opts != null) {
            mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
        } else {
            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
        }
        mCanReuseTaskStackViews = true;
    }
    启动RecentsActivity,执行的是RecentsActivity的onStart()方法,其中比较重点是update Recents task:  
 /** Updates the set of recent tasks */
    void updateRecentsTasks(Intent launchIntent) {
        // If AlternateRecentsComponent has preloaded a load plan, then use that to prevent
        // reconstructing the task stack
        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
        RecentsTaskLoadPlan plan = AlternateRecentsComponent.consumeInstanceLoadPlan();
        if (plan == null) {
            plan = loader.createLoadPlan(this);
        }
        // Start loading tasks according to the load plan
        if (plan.getTaskStack() == null) {
            loader.preloadTasks(plan, mConfig.launchedFromHome);
        }
        RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
        loadOpts.runningTaskId = mConfig.launchedToTaskId;
        loadOpts.numVisibleTasks = mConfig.launchedNumVisibleTasks;
        loadOpts.numVisibleTaskThumbnails = mConfig.launchedNumVisibleThumbnails;
        loader.loadTasks(this, plan, loadOpts);
        SpaceNode root = plan.getSpaceNode();
        ArrayList<TaskStack> stacks = root.getStacks();
        boolean hasTasks = root.hasTasks();
        if (hasTasks) {
            mRecentsView.setTaskStacks(stacks);
        }
        mConfig.launchedWithNoRecentTasks = !hasTasks;
        // Create the home intent runnable
        Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
        homeIntent.addCategory(Intent.CATEGORY_HOME);
        homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
        mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent,
            ActivityOptions.makeCustomAnimation(this,
                mConfig.launchedFromSearchHome ? R.anim.recents_to_search_launcher_enter :
                        R.anim.recents_to_launcher_enter,
                    mConfig.launchedFromSearchHome ? R.anim.recents_to_search_launcher_exit :
                        R.anim.recents_to_launcher_exit));
        // Mark the task that is the launch target
        int taskStackCount = stacks.size();
        if (mConfig.launchedToTaskId != -1) {
            for (int i = 0; i < taskStackCount; i++) {
                TaskStack stack = stacks.get(i);
                ArrayList<Task> tasks = stack.getTasks();
                int taskCount = tasks.size();
                for (int j = 0; j < taskCount; j++) {
                    Task t = tasks.get(j);
                    if (t.key.id == mConfig.launchedToTaskId) {
                        t.isLaunchTarget = true;
                        break;
                    }
                }
            }
        }
        // Update the top level view's visibilities
        if (mConfig.launchedWithNoRecentTasks) {
            if (mEmptyView == null) {
                mEmptyView = mEmptyViewStub.inflate();
            }
            mEmptyView.setVisibility(View.VISIBLE);
            mRecentsView.setSearchBarVisibility(View.GONE);
        } else {
            if (mEmptyView != null) {
                mEmptyView.setVisibility(View.GONE);
            }
            if (mRecentsView.hasSearchBar()) {
                mRecentsView.setSearchBarVisibility(View.VISIBLE);
            } else {
                addSearchBarAppWidgetView();
            }
        }
        // Animate the SystemUI scrims into view
        mScrimViews.prepareEnterRecentsAnimation();
    }
    那么Recent应用展示界面存放在什么视图当中呢,这个要回到SystemUI 在开机初始化显示的时候执行RecentsActivity的onCreate方法,其布局文件为recents.xml,用来存放Recent 应用展示界面的View是RecentsView:   
 ...
     <!-- Recents View -->
    <com.android.systemui.recents.views.RecentsView
        android:id="@+id/recents_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:focusable="true" />

    <!-- Empty View -->
    <ViewStub android:id="@+id/empty_view_stub"
           android:layout="@layout/recents_empty"
           android:layout_width="match_parent"
           android:layout_height="match_parent" />
    ...
    RecentsView里面又包含哪些东西:   
    /**
     * This is called with the full size of the window since we are handling our own insets.
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        // Get the search bar bounds so that we lay it out
        if (mSearchBar != null) {
            Rect searchBarSpaceBounds = new Rect();
            mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(),
                    mConfig.systemInsets.top, searchBarSpaceBounds);
            mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top,
                    searchBarSpaceBounds.right, searchBarSpaceBounds.bottom);
        }
        // Layout each TaskStackView with the full width and height of the window since the
        // transition view is a child of that stack view
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child != mSearchBar && child.getVisibility() != GONE) {
                child.layout(left, top, left + child.getMeasuredWidth(),
                        top + child.getMeasuredHeight());
            }
        }
    }
    RecentsView包含TaskStackView,TaskStackView里面又包含什么:
    ...
      @Override
    public TaskView createView(Context context) {
        return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
    }
      ...
       /**
     * This is called with the size of the space not including the top or right insets, or the
     * search bar height in portrait (but including the search bar width in landscape, since we want
     * to draw under it.
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        // Layout each of the children
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            TaskView tv = (TaskView) getChildAt(i);
            if (tv.getBackground() != null) {
                tv.getBackground().getPadding(mTmpRect);
            } else {
                mTmpRect.setEmpty();
            }
            tv.layout(mLayoutAlgorithm.mTaskRect.left - mTmpRect.left,
                    mLayoutAlgorithm.mTaskRect.top - mTmpRect.top,
                    mLayoutAlgorithm.mTaskRect.right + mTmpRect.right,
                    mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom);
        }
        if (mAwaitingFirstLayout) {
            mAwaitingFirstLayout = false;
            onFirstLayout();
        }
    }
    TaskStackView里面包含TaskView,也就是说,在Recents界面有几个Recent 应用,就会有几个TaskView:   
     
    每一个TaskView包含的View(recents_task_view.xml): 
 <com.android.systemui.recents.views.TaskView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:focusable="true">
    <FrameLayout
        android:id="@+id/task_view_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <com.android.systemui.recents.views.TaskViewThumbnail
           android:id="@+id/task_view_thumbnail"
           android:layout_width="match_parent"
           android:layout_height="match_parent" />
        <include layout="@layout/recents_task_view_header" />
        <FrameLayout
            android:id="@+id/lock_to_app_fab"
            android:layout_width="@dimen/recents_lock_to_app_size"
            android:layout_height="@dimen/recents_lock_to_app_size"
            android:layout_gravity="bottom|right"
            android:layout_marginRight="15dp"
            android:layout_marginBottom="15dp"
            android:translationZ="2dp"
            android:contentDescription="@string/recents_lock_to_app_button_label"
            android:background="@drawable/recents_lock_to_task_button_bg">
            <ImageView
                android:layout_width="@dimen/recents_lock_to_app_icon_size"
                android:layout_height="@dimen/recents_lock_to_app_icon_size"
                android:layout_gravity="center"
                android:src="@drawable/recents_lock_to_app_pin" />
        </FrameLayout>
    </FrameLayout>
</com.android.systemui.recents.views.TaskView>
    在这其中比较重要的是TaskViewThumbnail,因为它就是用来承载每个Recents app缩略图的View,而对于recents_lock_to_app_pin这个新东西是从Android L开始加进来的一个新功能--屏幕固定,在设置-安全中可以看到。视图有了,剩下就是把数据填充进去,而这部分逻辑要关注RecentsActivity被Resume之前的PreLoad和RecentsActivity Resume时候的updateRecentsTasks()。这个就不赘述,只要晓得了整个流程的主要走向,就可以通过查看代码和debug查看数据来分析。

展开阅读全文

没有更多推荐了,返回首页