Android8.1 Car Model No Launcher 实现原理

我们知道Car Model 属于 No Launcher 模式,但真的是没有 Launcher应用吗?我们一步一步分析:

Car Module Navigation Bar

packages/services/Car/car_product/overlay/frameworks/base/packages/SystemUI/res/values/arrays_car.xml

<resources>
    <!-- There needs to be correspondence per index between these arrays, which means that if there
         isn't a longpress action associated with a shortcut item, put in an empty item to make
         sure everything lines up.
    -->
    <array name="car_facet_icons">
        <item>@drawable/car_ic_navigation</item>
        <item>@drawable/car_ic_phone</item>
        <item>@drawable/car_ic_overview</item>
        <item>@drawable/car_ic_music</item>
        <item>@drawable/car_ic_car</item>
    </array>
    <array name="car_facet_intent_uris">
        <!-- Launch the lenspicker for all the facets. The lens picker will trampoline into the last run app or display a list of valid apps -->
        <item>intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x14000000;package=com.android.support.car.lenspicker;end</item>
        <item>intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x14000000;package=com.android.support.car.lenspicker;end</item>
        <item>intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x14000000;package=com.android.support.car.lenspicker;S.system_command=toggle_notifications;end</item>
        <item>intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x14000000;package=com.android.support.car.lenspicker;end</item>
        <item>intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x14000000;package=com.android.support.car.lenspicker;end</item>
    </array>
    <array name="car_facet_longpress_intent_uris">
        <item></item>
        <item></item>
        <item></item>
        <item></item>
        <!-- Long pressing the overflow triggers a bug report -->
        <item>intent:#Intent;component=com.google.android.car.bugreport/.BugReportActivity;end</item>
    </array>
    <array name="car_facet_category_filters">
        <item>android.intent.category.APP_MAPS</item>
        <item>android.intent.category.APP_MESSAGING</item>
        <item></item>
        <item>android.intent.category.APP_MUSIC</item>
        <item></item>
    </array>
    <array name="car_facet_package_filters">
        <item>com.android.car.mapsplaceholder</item>
        <item>com.android.car.dialer</item>
        <item>com.android.car.overview</item>
        <item></item>
        <item>com.android.car.hvac;com.android.settings;com.android.car.settings;com.android.vending;com.google.android.car.bugreport;com.google.android.car.kitchensink;com.android.car.systemupdater;org.chromium.webview_shell;com.android.contacts;org.codeaurora.bluetooth.bttestapp;com.google.android.projection.sink</item>
    </array>
</resources>

我们通过这个配置文件可以看出这个文件配置了Car Module 中NavigationBar 上的按钮个数,以及按钮点击之后所携带的intent参数,Uri,Category, Package Filters。
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java

    /**
     * Handles a click on a facet. A click will trigger the given Intent.
     *
     * @param index The index of the facet that was clicked.
     */
    private void onFacetClicked(Intent intent, int index) {
        String packageName = intent.getPackage();

        if (packageName == null) {
            return;
        }

        intent.putExtra(EXTRA_FACET_CATEGORIES, mFacetCategories.get(index));
        intent.putExtra(EXTRA_FACET_PACKAGES, mFacetPackages.get(index));
        // The facet is identified by the index in which it was added to the nav bar.
        // This value can be used to determine which facet was selected
        intent.putExtra(EXTRA_FACET_ID, Integer.toString(index));

        // If the current facet is clicked, we want to launch the picker by default
        // rather than the "preferred/last run" app.
        intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, index == mCurrentFacetIndex);

        int stackId = StackId.FULLSCREEN_WORKSPACE_STACK_ID;
        if (intent.getCategories().contains(Intent.CATEGORY_HOME)) {
            stackId = StackId.HOME_STACK_ID;
        }

        setCurrentFacet(index);
        mStatusBar.startActivityOnStack(intent, stackId);
    }

可以发现NavigationBar 启动Map, dialer, 等等其他应用,都是先去启动包名为com.android.support.car.lenspicker这个应用。
packages/apps/Car/LensPicker/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.android.support.car.lenspicker" >

    <uses-sdk
        android:minSdkVersion="22"
        android:targetSdkVersion='23'/>

    <!-- Permission to allow the LensPicker to set a default activity for an Intent. -->
    <uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS" />
    <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />

    <application
        android:label="@string/app_label" >

        <activity android:name=".LensPickerActivity"
            android:theme="@style/Theme.FloatingLensPicker"
            android:label="LensPickerActivity"
            android:resizeableActivity="true"
            android:excludeFromRecents="true"
            android:launchMode="singleTask"
            android:noHistory="true" />

        <activity android:name=".LensResolverActivity"
            android:theme="@style/Theme.FloatingLensPicker"
            android:label="LensResolverActivity"
            android:resizeableActivity="true"
            android:excludeFromRecents="true"
            android:launchMode="singleTask"
            android:noHistory="true" />

        <activity android:name=".LensPickerTrampolineActivity"
            android:theme="@style/Theme.FloatingLensPicker"
            android:label="LensPickerTrampolineActivity"
            android:resizeableActivity="true"
            android:excludeFromRecents="true"
            android:noHistory="true" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.LAUNCHER" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>
</manifest>

我们可以看到 <category android:name="android.intent.category.HOME" />它是一个Launcher应用
packages/apps/Car/LensPicker/src/com/android/support/car/lenspicker/LensPickerTrampolineActivity.java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        PackageManager packageManager = getPackageManager();
        mSharedPrefs = LensPickerUtils.getFacetSharedPrefs(this);

        Intent intent = getIntent();

        String systemCommand =
                intent.getStringExtra(LensPickerConstants.EXTRA_FACET_SYSTEM_COMMAND);
        if (systemCommand != null && executeSystemCommand(systemCommand)) {
            finish();
            return;
        }

        // Hide the shade if switching to a different facet.
        hideNotificationsShade();

        String facetId = intent.getStringExtra(LensPickerConstants.EXTRA_FACET_ID);

        // If no facetId was passed to this activity, then that means that we cannot retrieve the
        // application categories to determine an activity to launch. Thus, just launch the
        // default application.
        if (TextUtils.isEmpty(facetId)) {
            launchLastRunOrDefaultApplication();
            finish();
            return;
        }

        String facetKey = LensPickerUtils.getFacetKey(facetId);
        String savedPackageName = mSharedPrefs.getString(facetKey, null /* defaultValue */);
        String[] categories = intent.getStringArrayExtra(
                LensPickerConstants.EXTRA_FACET_CATEGORIES);
        String[] packages = intent.getStringArrayExtra(LensPickerConstants.EXTRA_FACET_PACKAGES);

        boolean alwaysLaunchPicker = intent.getBooleanExtra(
                LensPickerConstants.EXTRA_FACET_LAUNCH_PICKER, false);

        Intent launchIntent;
        if (!alwaysLaunchPicker && savedPackageName != null) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Launching saved package: " + savedPackageName);
            }

            launchIntent = packageManager.getLaunchIntentForPackage(savedPackageName);
        } else {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Delegating to LensPickerActivity to handle application launch.");
            }

            launchIntent = new Intent(this, LensPickerActivity.class);
            launchIntent.putExtra(LensPickerConstants.EXTRA_FACET_PACKAGES, packages);
            launchIntent.putExtra(LensPickerConstants.EXTRA_FACET_CATEGORIES, categories);
            launchIntent.putExtra(LensPickerConstants.EXTRA_FACET_ID, facetId);
        }

        startActivity(launchIntent);
        finish();
    }

从Intent中取出package,categrory,facet 信息,然后启动LensPickerActivity
packages/apps/Car/LensPicker/src/com/android/support/car/lenspicker/LensPickerActivity.java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mPackageManager = getPackageManager();
        mSharedPrefs = LensPickerUtils.getFacetSharedPrefs(this);

        setContentView(R.layout.lens_list);
        mPagedListView = (PagedListView) findViewById(R.id.list_view);
        // Set this to light mode, since the scroll bar buttons always appear
        // on top of a dark scrim.
        mPagedListView.setLightMode();

        findViewById(R.id.dismiss_area).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();

        Intent intent = getIntent();
        String[] categories = intent.getStringArrayExtra(
                LensPickerConstants.EXTRA_FACET_CATEGORIES);
        String[] packages = intent.getStringArrayExtra(LensPickerConstants.EXTRA_FACET_PACKAGES);
        String facetId = intent.getStringExtra(LensPickerConstants.EXTRA_FACET_ID);

        List<ResolveInfo> resolveInfos = getComponents(packages, categories);

        if (resolveInfos != null && resolveInfos.size() == 1) {
            // Directly launch the package rather than showing a list of 1.
            ResolveInfo rInfo = resolveInfos.get(0);
            String packageName = LensPickerUtils.getPackageName(rInfo);
            Intent launchIntent = LensPickerUtils.getLaunchIntent(packageName, rInfo,
                    mPackageManager);
            if (launchIntent != null) {
                launch(facetId, packageName, launchIntent);
            } else {
                Log.e(TAG, "Failed to get launch intent for package" + packageName);
            }
            finish();
            return;
        }

        mPagedListView.setAdapter(new LensPickerAdapter(this, resolveInfos, facetId,
                this /* LensPickerSelectionHandler */));
    }

    @Override
    protected void onPause() {
        super.onPause();

        if (mLastLaunchedFacetId == null || mLastLaunchedPackageName == null
                || mLastLaunchedIntent == null) {
            return;
        }

        LensPickerUtils.saveLastLaunchedAppInfo(mSharedPrefs, mLastLaunchedFacetId,
                mLastLaunchedPackageName, mLastLaunchedIntent);
    }

在onResume 方法中去启动Intent中保留的应用。
第一:以上我们可以得到 Car Model No Launcher 启动流程:

启动目标应用 --> 跳转LensPicker应用 --> 启动目标应用

第二:让我们无感它的切换过程,认定它是No Launcher 模式主要是 LensPicker AndroidManifest.xml 文件中配置的这两个属性:

android:excludeFromRecents="true"

该属性让我们在Recent 中无法查看到这个应用进程的信息

android:noHistory="true"

该属性清除了它自身的启动路径TaskRecord,
注意:让我们使用Back 按键时直接在相邻的两个应用中进行TaskRecord切换,避免再次经过LensPicker做启动,同时也减轻了LensPicker的工作,不需要保留所有的启动记录,而是让专业的人去做专业的事(AMS, ActivityStack, TaskRecord...)。
第三:而LensPicker只保留了当前最新的启动应用记录,当用户触发Home 按键,会回到LensPickerActivity页面中,而LensPickerActivity保留了最新的应用启动记录,会跳转到目标应用,这就实现了Car Model No Launcher模式。
下面我们贴一下栈信息,印证我们的想法:

ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
  Stack #1:
  mFullscreen=true
  isSleeping=false
  mBounds=null
    Task id #217
    mFullscreen=true
    mBounds=null
    mMinWidth=-1
    mMinHeight=-1
    mLastNonFullscreenBounds=null
    * TaskRecord{c4be22 #217 A=com.android.car.radio U=0 StackId=1 sz=1}
      userId=0 effectiveUid=u0a11 mCallingUid=u0a44 mUserSetupComplete=true mCallingPackage=com.android.support.car.lenspicker
      affinity=com.android.car.radio
      intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 pkg=com.android.car.radio cmp=com.android.car.radio/.CarRadioActivity}
      realActivity=com.android.car.radio/.CarRadioActivity
      autoRemoveRecents=false isPersistable=true numFullscreen=1 taskType=0 mTaskToReturnTo=1
      rootWasReset=false mNeverRelinquishIdentity=true mReuseTask=false mLockTaskAuth=LOCK_TASK_AUTH_PINNABLE
      Activities=[ActivityRecord{aa50792 u0 com.android.car.radio/.CarRadioActivity t217}]
      askedCompatMode=false inRecents=true isAvailable=true
      lastThumbnail=null lastThumbnailFile=/data/system_ce/0/recent_images/217_task_thumbnail.png
      stackId=1
      hasBeenVisible=true mResizeMode=RESIZE_MODE_RESIZEABLE mSupportsPictureInPicture=false isResizeable=true firstActiveTime=1564637925171 lastActiveTime=1564637925203 (inactive for 29s)
      * Hist #0: ActivityRecord{aa50792 u0 com.android.car.radio/.CarRadioActivity t217}
          packageName=com.android.car.radio processName=com.android.car.radio
          launchedFromUid=10044 launchedFromPackage=com.android.support.car.lenspicker userId=0
          app=ProcessRecord{cdd5c6d 2175:com.android.car.radio/u0a11}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 pkg=com.android.car.radio cmp=com.android.car.radio/.CarRadioActivity }
          frontOfTask=true task=TaskRecord{c4be22 #217 A=com.android.car.radio U=0 StackId=1 sz=1}
          taskAffinity=com.android.car.radio
          realActivity=com.android.car.radio/.CarRadioActivity
          baseDir=/system/priv-app/CarRadioApp/CarRadioApp.apk
          dataDir=/data/user/0/com.android.car.radio
          stateNotNeeded=false componentSpecified=true mActivityType=0
          compat={160dpi always-compat} labelRes=0x7f0d0028 icon=0x7f020076 theme=0x7f0e01a6
          mLastReportedConfigurations:
           mGlobalConfig={1.0 310mcc260mnc [en_US] ldltr sw990dp w1964dp h878dp 160dpi xlrg long land car finger -keyb/v/h tball/v appBounds=Rect(0, 0 - 1964, 934) s.7}
           mOverrideConfig={1.0 310mcc260mnc [en_US] ldltr sw990dp w1964dp h878dp 160dpi xlrg long land car finger -keyb/v/h tball/v appBounds=Rect(0, 0 - 1964, 934) s.7}
          CurrentConfiguration={1.0 310mcc260mnc [en_US] ldltr sw990dp w1964dp h878dp 160dpi xlrg long land car finger -keyb/v/h tball/v appBounds=Rect(0, 0 - 1964, 934) s.7}
          taskDescription: iconFilename=null label="null" primaryColor=fff5f5f5
            backgroundColor=fffafafa
            statusBarColor=ff283593
            navigationBarColor=ff000000
          launchFailed=false launchCount=1 lastLaunchTime=-29s467ms
          haveState=false icicle=null
          state=RESUMED stopped=false delayedResume=false finishing=false
          keysPaused=false inHistory=true visible=true sleeping=false idle=true mStartingWindowState=STARTING_WINDOW_SHOWN
          fullscreen=true noDisplay=false immersive=false launchMode=2
          frozenBeforeDestroy=false forceNewConfig=false
          mActivityType=APPLICATION_ACTIVITY_TYPE
          waitingVisible=false nowVisible=true lastVisibleTime=-26s401ms
          connections=[ConnectionRecord{9ddac5 u0 CR com.android.car.radio/.RadioService:@eecd3c}]
          resizeMode=RESIZE_MODE_RESIZEABLE
          mLastReportedMultiWindowMode=false mLastReportedPictureInPictureMode=false

    Running activities (most recent first):
      TaskRecord{c4be22 #217 A=com.android.car.radio U=0 StackId=1 sz=1}
        Run #0: ActivityRecord{aa50792 u0 com.android.car.radio/.CarRadioActivity t217}

    mResumedActivity: ActivityRecord{aa50792 u0 com.android.car.radio/.CarRadioActivity t217}

  Stack #0:
  mFullscreen=true
  isSleeping=false
  mBounds=null

    mLastPausedActivity: ActivityRecord{21dc322 u0 com.android.support.car.lenspicker/.LensPickerTrampolineActivity t-1 f}

    mLastNoHistoryActivity: ActivityRecord{21dc322 u0 com.android.support.car.lenspicker/.LensPickerTrampolineActivity t-1 f}

  ResumedActivity: ActivityRecord{aa50792 u0 com.android.car.radio/.CarRadioActivity t217}
  mFocusedStack=ActivityStack{5ff80b3 stackId=1, 1 tasks} mLastFocusedStack=ActivityStack{5ff80b3 stackId=1, 1 tasks}
  mCurTaskIdForUser={0=217}
  mUserStackInFront={}
  mStacks={0=ActivityStack{4728670 stackId=0, 0 tasks}, 1=ActivityStack{5ff80b3 stackId=1, 1 tasks}}
  mLockTaskModeState=NONE  mLockTaskPackages (userId:packages)=
    0:[]
 mLockTaskModeTasks[]
  KeyguardController:
    mKeyguardShowing=false
    mKeyguardGoingAway=false
    mOccluded=false
    mDismissingKeyguardActivity=null
    mDismissalRequested=false
    mVisibilityTransactionDepth=0
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271