Android 11 替换Launcher后导致近期任务无效

简介: Android 11 替换Launcher后导致近期任务无效

平台


    RK3566 + Android 11


概述


   替换默认主界面, 更换为指定第三方Launcher后, 点击导航栏的RECENT键无效. 究其原因在于, 在旧版本SDK上, 删除Launcher3并不会影响RECENT的功能 , 而在新的SDK上, RECENT功能集成于Launcher3目录下, 删除 Launcher3后, 导致SystemUI调用对应的RECENT界面启动的服务失败.


过程疏理


frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java


@Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mNavigationBarView = (NavigationBarView) view;
        final Display display = view.getDisplay();
        // It may not have display when running unit test.
        if (display != null) {
            mDisplayId = display.getDisplayId();
            mIsOnDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY;
        }
        mNavigationBarView.setComponents(mStatusBarLazy.get().getPanelController());
        mNavigationBarView.setDisabledFlags(mDisabledFlags1);
        mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
        mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
        if (savedInstanceState != null) {
            mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState);
        }
        mNavigationBarView.setNavigationIconHints(mNavigationIconHints);
        mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
        prepareNavigationBarView();
    }
    private void prepareNavigationBarView() {
        mNavigationBarView.reorient();
        ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
        recentsButton.setOnClickListener(this::onRecentsClick);
        recentsButton.setOnTouchListener(this::onRecentsTouch);
        recentsButton.setLongClickable(true);
        recentsButton.setOnLongClickListener(this::onLongPressBackRecents);
    }
    private void onRecentsClick(View v) {
        if (LatencyTracker.isEnabled(getContext())) {
            LatencyTracker.getInstance(getContext()).onActionStart(
                    LatencyTracker.ACTION_TOGGLE_RECENTS);
        }
        mStatusBarLazy.get().awakenDreams();
        mCommandQueue.toggleRecentApps();
    }


frameworks/base/packages/SystemUI/src/com/android/systemui/recents/Recents.java


@Override
    public void toggleRecentApps() {
        // Ensure the device has been provisioned before allowing the user to interact with
        // recents
        if (!isUserSetup()) {
            return;
        }
        mImpl.toggleRecentApps();
    }


frameworks/base/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java


@Override
    public void toggleRecentApps() {
        Log.d(TAG, "toggleRecentApps");
        // If connected to launcher service, let it handle the toggle logic
        IOverviewProxy overviewProxy = mOverviewProxyService.getProxy();
        if (overviewProxy != null) {
            final Runnable toggleRecents = () -> {
                try {
                    if (mOverviewProxyService.getProxy() != null) {
                        mOverviewProxyService.getProxy().onOverviewToggle();
                        mOverviewProxyService.notifyToggleRecentApps();
                    }
                } catch (RemoteException e) {
                    Log.e(TAG, "Cannot send toggle recents through proxy service.", e);
                }
            };
            // Preload only if device for current user is unlocked
            if (mStatusBarLazy != null && mStatusBarLazy.get().isKeyguardShowing()) {
                mStatusBarLazy.get().executeRunnableDismissingKeyguard(() -> {
                        // Flush trustmanager before checking device locked per user
                        mTrustManager.reportKeyguardShowingChanged();
                        mHandler.post(toggleRecents);
                    }, null,  true /* dismissShade */, false /* afterKeyguardGone */,
                    true /* deferred */);
            } else {
                toggleRecents.run();
            }
            return;
        } else {
            // Do nothing
        }
    }


重点看下overviewProxy


frameworks/base/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java


private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
    @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
    @Inject
    public OverviewProxyService(Context context, CommandQueue commandQueue,
            NavigationBarController navBarController, NavigationModeController navModeController,
            NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
            PipUI pipUI, Optional<Divider> dividerOptional,
            Optional<Lazy<StatusBar>> statusBarOptionalLazy,
            BroadcastDispatcher broadcastDispatcher) {
        super(broadcastDispatcher);
        mContext = context;
        mPipUI = pipUI;
        mStatusBarOptionalLazy = statusBarOptionalLazy;
        mHandler = new Handler();
        mNavBarController = navBarController;
        mStatusBarWinController = statusBarWinController;
        mConnectionBackoffAttempts = 0;
        mDividerOptional = dividerOptional;
//frameworks/base/core/res/res/values/config.xml
//    <string name="config_recentsComponentName" translatable="false">com.android.launcher3/com.android.quickstep.RecentsActivity</string>
        mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
                com.android.internal.R.string.config_recentsComponentName));
        mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
    }
    //绑定服务
    private void internalConnectToCurrentUser() {
        //..............
        Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP)
                .setPackage(mRecentsComponentName.getPackageName());
        try {
            mBound = mContext.bindServiceAsUser(launcherServiceIntent,
                    mOverviewServiceConnection,
                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                    UserHandle.of(getCurrentUserId()));
        } catch (SecurityException e) {
            Log.e(TAG_OPS, "Unable to bind because of security error", e);
        }
        //.....
    }
    //绑定成功
        private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //.........
            mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
        }
    //由上面OverviewProxyRecentsImpl 调用
    public IOverviewProxy getProxy() {
        return mOverviewProxy;
    }


近期任务UI的实现有别与旧版本的SDK, 有frameworks/base/packages/SystemUI改到了packages/apps/Launcher3/quickstep


packages/apps/Launcher3/quickstep/AndroidManifest.xml
            <service
            android:name="com.android.quickstep.TouchInteractionService"
            android:permission="android.permission.STATUS_BAR_SERVICE"
            android:directBootAware="true" >
            <intent-filter>
                <action android:name="android.intent.action.QUICKSTEP_SERVICE" />
            </intent-filter>
        </service>


为了使用默认第三方Launcher, 需要注释掉quickstep自带的Launcher


packages/apps/Launcher3/quickstep/AndroidManifest-launcher.xml


 

<activity
            android:name="com.android.launcher3.uioverrides.QuickstepLauncher"
            android:launchMode="singleTask"
            android:clearTaskOnLaunch="true"
            android:stateNotNeeded="true"
            android:windowSoftInputMode="adjustPan"
            android:screenOrientation="unspecified"
            android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
            android:resizeableActivity="true"
            android:resumeWhilePausing="true"
            android:taskAffinity=""
            android:enabled="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <!--category android:name="android.intent.category.HOME" /-->
                <category android:name="android.intent.category.DEFAULT" />
                <!--category android:name="android.intent.category.MONKEY"/-->
                <category android:name="android.intent.category.LAUNCHER_APP" />
            </intent-filter>
            <meta-data
                android:name="com.android.launcher3.grid.control"
                android:value="${packageName}.grid_control" />
        </activity>


开机奔溃


2022-07-10 04:01:50.179 1472-1472/com.android.launcher3 E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.android.launcher3, PID: 1472
    java.lang.RuntimeException: Unable to create service com.android.quickstep.TouchInteractionService: java.lang.NullPointerException: Attempt to read from field 'android.content.pm.ActivityInfo android.content.pm.ResolveInfo.activityInfo' on a null object reference
        at android.app.ActivityThread.handleCreateService(ActivityThread.java:4198)
        at android.app.ActivityThread.access$1500(ActivityThread.java:237)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1932)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7664)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: java.lang.NullPointerException: Attempt to read from field 'android.content.pm.ActivityInfo android.content.pm.ResolveInfo.activityInfo' on a null object reference
        at com.android.quickstep.OverviewComponentObserver.<init>(OverviewComponentObserver.java:82)
        at com.android.quickstep.TouchInteractionService.onUserUnlocked(TouchInteractionService.java:345)
        at com.android.quickstep.-$$Lambda$6M6xH0rMyGjroOocJ2F5KYabvuw.run(Unknown Source:2)
        at com.android.quickstep.RecentsAnimationDeviceState.runOnUserUnlocked(RecentsAnimationDeviceState.java:272)
        at com.android.quickstep.TouchInteractionService.onCreate(TouchInteractionService.java:301)
        at android.app.ActivityThread.handleCreateService(ActivityThread.java:4186)
        at android.app.ActivityThread.access$1500(ActivityThread.java:237) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1932) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.app.ActivityThread.main(ActivityThread.java:7664) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)


packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java


public OverviewComponentObserver(Context context, RecentsAnimationDeviceState deviceState) {
        mContext = context;
        mDeviceState = deviceState;
        mCurrentHomeIntent = new Intent(Intent.ACTION_MAIN)
                .addCategory(Intent.CATEGORY_HOME)
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        //异常位置: 由于前面注释掉了自带Launcher导致, 将mContext.getPackageName 替换成 三方Launcher的包名可解决.
        mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(/*mContext.getPackageName()*/"com.android.launcherX");
        ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0);
        ComponentName myHomeComponent =
                new ComponentName(/*mContext.getPackageName()*/"com.android.launcherX", info.activityInfo.name);
        mMyHomeIntent.setComponent(myHomeComponent);
        mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges);
        ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class);
        mFallbackIntent = new Intent(Intent.ACTION_MAIN)
                .addCategory(Intent.CATEGORY_DEFAULT)
                .setComponent(fallbackComponent)
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        try {
            ActivityInfo fallbackInfo = context.getPackageManager().getActivityInfo(
                    mFallbackIntent.getComponent(), 0 /* flags */);
            mConfigChangesMap.append(fallbackComponent.hashCode(), fallbackInfo.configChanges);
        } catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }
        mContext.registerReceiver(mUserPreferenceChangeReceiver,
                new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED));
        updateOverviewTargets();
    }


解决奔溃问题后, 点击recent键返回了HOME, 而不是显示RECENT界面:


packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java



/**
     * Update overview intent and {@link BaseActivityInterface} based off the current launcher home
     * component.
     */
    private void updateOverviewTargets() {
        ComponentName defaultHome = PackageManagerWrapper.getInstance()
                .getHomeActivities(new ArrayList<>());
        mIsHomeDisabled = mDeviceState.isHomeDisabled();
        mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome);
        // Set assistant visibility to 0 from launcher's perspective, ensures any elements that
        // launcher made invisible become visible again before the new activity control helper
        // becomes active.
        if (mActivityInterface != null) {
            mActivityInterface.onAssistantVisibilityChanged(0.f);
        }
        if (SEPARATE_RECENTS_ACTIVITY.get()) {
            mIsDefaultHome = false;
            if (defaultHome == null) {
                defaultHome = mMyHomeIntent.getComponent();
            }
        }
        //不走if分支, 
        if (false && !mDeviceState.isHomeDisabled() && (defaultHome == null || mIsDefaultHome)) {
            // User default home is same as out home app. Use Overview integrated in Launcher.
            mActivityInterface = LauncherActivityInterface.INSTANCE;
            mIsHomeAndOverviewSame = true;
            mOverviewIntent = mMyHomeIntent;
            mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
            // Remove any update listener as we don't care about other packages.
            unregisterOtherHomeAppUpdateReceiver();
        } else {
            // The default home app is a different launcher. Use the fallback Overview instead.
            mActivityInterface = FallbackActivityInterface.INSTANCE;
            mIsHomeAndOverviewSame = false;
            mOverviewIntent = mFallbackIntent;
            mCurrentHomeIntent.setComponent(defaultHome);
            // User's default home app can change as a result of package updates of this app (such
            // as uninstalling the app or removing the "Launcher" feature in an update).
            // Listen for package updates of this app (and remove any previously attached
            // package listener).
            if (defaultHome == null) {
                unregisterOtherHomeAppUpdateReceiver();
            } else if (!defaultHome.getPackageName().equals(mUpdateRegisteredPackage)) {
                unregisterOtherHomeAppUpdateReceiver();
                mUpdateRegisteredPackage = defaultHome.getPackageName();
                mContext.registerReceiver(mOtherHomeAppUpdateReceiver, getPackageFilter(
                        mUpdateRegisteredPackage, ACTION_PACKAGE_ADDED, ACTION_PACKAGE_CHANGED,
                        ACTION_PACKAGE_REMOVED));
            }
        }
        mOverviewChangeListener.accept(mIsHomeAndOverviewSame);
    }


关键在于使mOverviewIntent = mFallbackIntent;


由SystemUI调用onOverviewToggle:


packages/apps/Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java


@BinderThread
        @Override
        public void onOverviewToggle() {
            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
            mOverviewCommandHelper.onOverviewToggle();
        }


packages/apps/Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java


@BinderThread
    public void onOverviewToggle() {
        // If currently screen pinning, do not enter overview
        if (mDeviceState.isScreenPinningActive()) {
            return;
        }
        ActivityManagerWrapper.getInstance()
                .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
        MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
    }


MAIN_EXECUTOR 和 LooperExecutor

image.png

RecentsActivityCommand


private class RecentsActivityCommand<T extends StatefulActivity<?>> implements Runnable {
        protected final BaseActivityInterface<?, T> mActivityInterface;
        //.....
        private ActivityInitListener mListener;
        public RecentsActivityCommand() {
            mActivityInterface = mOverviewComponentObserver.getActivityInterface();
    //....
        }
        @Override
        public void run() {
    //...
            // Otherwise, start overview.
            mListener = mActivityInterface.createActivityInitListener(this::onActivityReady);
            mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
                    new RemoteAnimationProvider() {
                        @Override
                        public AnimatorSet createWindowAnimation(
                                RemoteAnimationTargetCompat[] appTargets,
                                RemoteAnimationTargetCompat[] wallpaperTargets) {
                            return RecentsActivityCommand.this.createWindowAnimation(appTargets,
                                    wallpaperTargets);
                        }
                    }, mContext, MAIN_EXECUTOR.getHandler(),
                    mAnimationProvider.getRecentsLaunchDuration());
        }


packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java


/**
     * Get the current activity control helper for managing interactions to the overview activity.
     *
     * @return the current activity control helper
     */
    public BaseActivityInterface getActivityInterface() {
        return mActivityInterface;
    }


OverviewComponentObserver.mActivityInterface = FallbackActivityInterface.INSTANCE;


packages/apps/Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java


public final class FallbackActivityInterface extends
        BaseActivityInterface<RecentsState, RecentsActivity> {
    public static final FallbackActivityInterface INSTANCE = new FallbackActivityInterface();
    @Override
    public ActivityInitListener createActivityInitListener(
            Predicate<Boolean> onInitListener) {
        return new ActivityInitListener<>((activity, alreadyOnHome) ->
                onInitListener.test(alreadyOnHome), RecentsActivity.ACTIVITY_TRACKER);
    }


packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/ActivityInitListener.java


/**
     * Starts the given intent with the provided animation. Unlike {@link #register(Intent)}, this
     * method will not call {@link #init} if the activity already exists, it will only call it when
     * we get handleIntent() for the provided intent that we're starting.
     */
    public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
            Context context, Handler handler, long duration) {
        mIsRegistered = true;
        Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
        context.startActivity(addToIntent(new Intent(intent)), options);
    }


最终context.startActivity 其中的Intent是:mOverviewComponentObserver.getOverviewIntent()


packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java


/**
     * Get the current intent for going to the overview activity.
     *
     * @return the overview intent
     */
    public Intent getOverviewIntent() {
      /*参见前面的代码:
      ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class);
        mFallbackIntent = new Intent(Intent.ACTION_MAIN)
                .addCategory(Intent.CATEGORY_DEFAULT)
                .setComponent(fallbackComponent)
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         */
        return mOverviewIntent;
    }

image.png

至此RecentsActivity启动完成.


模块编译:


mmm packages/apps/Launcher3/:Launcher3QuickStep -j4


需要注意的是


1.原生的Launcher3需要注释掉, 或让它不参与编译.

2.近期任务UI的存放目录是在Lauuncher3目录下, 而需要参与编译的模块只有Launcher3QuickStep

3.第三方Launcher不能使用com.android.launcher3这个包名, 与上面的模块冲突


参考


SystemUI之QuickStep探索(常规启动篇)

Android11 最近任务Recents功能分析


相关文章
|
13天前
|
Java 数据库 Android开发
【专栏】Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理
【4月更文挑战第27天】本文探讨了Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理。通过案例分析展示了网络请求、图像处理和数据库操作的优化实践。同时,文章指出并发编程的挑战,如性能评估、调试及兼容性问题,并强调了多线程优化对提升应用性能的重要性。开发者应持续学习和探索新的优化策略,以适应移动应用市场的竞争需求。
|
5月前
|
XML Java Android开发
Android Studio App开发之捕获屏幕的变更事件实战(包括竖屏与横屏切换,回到桌面与切换到任务列表)
Android Studio App开发之捕获屏幕的变更事件实战(包括竖屏与横屏切换,回到桌面与切换到任务列表)
45 0
|
8月前
|
Shell Android开发 容器
你真了解Android任务栈 Task 与启动模式吗?
你真了解Android任务栈 Task 与启动模式吗?
64 0
|
18天前
|
Java Android开发
Android系统 修改无源码普通应用为默认Launcher和隐藏Settings中应用信息图标
Android系统 修改无源码普通应用为默认Launcher和隐藏Settings中应用信息图标
43 0
|
18天前
|
存储 Java Android开发
Android系统 设置第三方应用为默认Launcher实现和原理分析
Android系统 设置第三方应用为默认Launcher实现和原理分析
38 0
|
1天前
|
Android开发
Android Launcher研究(二)-----------Launcher为何物,究竟是干什么
Android Launcher研究(二)-----------Launcher为何物,究竟是干什么
|
1天前
|
Android开发
Android 如何将定制的Launcher成为系统中唯一的Launcher
Android 如何将定制的Launcher成为系统中唯一的Launcher
11 2
|
1天前
|
Linux 开发工具 Android开发
Android Launcher研究(一)-----------图文详解手把手教你在Windows环
Android Launcher研究(一)-----------图文详解手把手教你在Windows环
|
1天前
|
存储 Android开发
android launcher总体分析
android launcher总体分析
|
1天前
|
Java Android开发
Android桌面快捷方式图标生成与删除 使用Intent与launcher交互
Android桌面快捷方式图标生成与删除 使用Intent与launcher交互