动态

详情 返回 返回

Launcher 桌面源碼筆記一 - 动态 详情

3D車模通過TaskView顯示在Launcher,首先需要知道,為什麼要用TaskView,而不是Activity,然後在説加載流程

1、surface比activity等效率更高,特別是針對車模跟地圖等重量級場景

2、切換桌面等場景時,可以更精確的控制暫停恢復

3、進程隔離,更精細的生命週期管理跟控制

4、taskView中SurfaceControl的跨進程綁定機制(reparent操作)效率比binder通訊效率更高

android.app.ActivityView(標記為棄用‌)

跟TaskView一樣都是Android系統中用於管理多窗口和任務嵌入的組件

public class ActivityView extends ViewGroup {
    private static final String DISPLAY_NAME = "ActivityViewVirtualDisplay";
    private static final String TAG = "ActivityView";
    private final SurfaceView mSurfaceView;
    private Surface mSurface;
    private final SurfaceCallback mSurfaceCallback;
    private StateCallback mActivityViewCallback;
    private IActivityManager mActivityManager;
    private TaskStackListener mTaskStackListener;

    public ActivityView(Context context) {
        this(context, (AttributeSet)null);
    }

    public ActivityView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ActivityView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.mActivityManager = ActivityManager.getService();
        this.mSurfaceView = new SurfaceView(context);
        this.mSurfaceCallback = new SurfaceCallback();
        this.mSurfaceView.getHolder().addCallback(this.mSurfaceCallback);
        this.addView(this.mSurfaceView);
    }
View Code

從源碼中看到,繼承自ViewGroup,基於SurfaceView實現,在初始化時將 SurfaceView addView到ViewGroup中

ActivityView‌ 直接依賴傳統的窗口管理機制,通過 WindowManager 控制嵌入的 Activity 生命週期。

com.android.wm.shell.TaskView

public class TaskView extends SurfaceView implements SurfaceHolder.Callback, ShellTaskOrganizer.TaskListener, ViewTreeObserver.OnComputeInternalInsetsListener {
    private final ShellTaskOrganizer mTaskOrganizer;
    private ActivityManager.RunningTaskInfo mTaskInfo;
    private WindowContainerToken mTaskToken;
    private SurfaceControl mTaskLeash;
    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();

    public TaskView(Context context, ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue) {
        super(context, (AttributeSet)null, 0, 0, true);
        this.getHolder().addCallback(this);
    }
View Code
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
        this.mTaskInfo = taskInfo;
        this.mTaskToken = taskInfo.token;
        this.mTaskLeash = leash;
        if (this.mSurfaceCreated) {
            this.mTransaction.reparent(this.mTaskLeash, this.getSurfaceControl()).show(this.mTaskLeash).apply();
        } else {
            this.updateTaskVisibility();
        }

        this.mTaskOrganizer.setInterceptBackPressedOnTaskRoot(this.mTaskToken, true);
        this.onLocationChanged();
        if (taskInfo.taskDescription != null) {
            int backgroundColor = taskInfo.taskDescription.getBackgroundColor();
            this.mSyncQueue.runInSync((t) -> this.setResizeBackgroundColor(t, backgroundColor));
        }

        if (this.mListener != null) {
            int taskId = taskInfo.taskId;
            ComponentName baseActivity = taskInfo.baseActivity;
            this.mListenerExecutor.execute(() -> this.mListener.onTaskCreated(taskId, baseActivity));
        }

    }
View Code
Android 12 引入的替代方案,專為 Android Automotive OS (AAOS) 優化,並擴展了 ShellTaskOrganizer.TaskListener 接口。

直接繼承自SurfaceView,並且在onTaskAppeared回調中使用了reparent操作,支持動態調整嵌入任務的位置和大小‌。

車模加載流程

車模屬於unity交互,在android中提供一個容器用於顯示,這裏直接使用Activity

<androidx.fragment.app.FragmentContainerView
        android:id="@+id/unity_player_fragment"
        android:name="com.test.UnityPlayerFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
View Code

新建一個CarLauncherActivity用於顯示這個UnityPlayerFragment

3DActivity有了,需要將它顯示到launcher桌面(MainActivity),自定義TaskView,用於3D桌面

<com.test.TaskViewCompat
        android:id="@+id/launcher3d"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:visibility="invisible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
View Code

在MainActivity啓動時通過 surfaceCreated 回調,對桌面進行初始化操作

public void surfaceCreated(SurfaceHolder holder) {
        this.mSurfaceCreated = true;
        if (this.mListener != null && !this.mIsInitialized) {
            this.mIsInitialized = true;
            this.mListenerExecutor.execute(() -> this.mListener.onInitialized());
        }

        this.mShellExecutor.execute(() -> {
            if (this.mTaskToken != null) {
                this.mTransaction.reparent(this.mTaskLeash, this.getSurfaceControl()).show(this.mTaskLeash).apply();
                this.updateTaskVisibility();
            }
        });
    }
View Code

接下來需要實現嵌入操作,可以看到在首次進入時,會調用 TaskView. Listener的 onInitialized 方法,在回調中需要執行taskview的startActivity進行內嵌

public void startActivity(PendingIntent pendingIntent, Intent fillInIntent, ActivityOptions options, Rect launchBounds) {
        this.prepareActivityOptions(options, launchBounds);

        try {
            pendingIntent.send(this.mContext, 0, fillInIntent, (PendingIntent.OnFinished)null, (Handler)null, (String)null, options.toBundle());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
View Code

startActivity後會觸發狀態變更,系統層處理後,在Systemui層通過TaskOrganizer監聽狀態

android.window.TaskOrganizer

當需要嵌入顯示另一個應用的 Activity 時,TaskOrganizer 會提供該任務窗口的 SurfaceControl(稱為 "leash"),但是TaskOrganizer位與SystemUI模塊,所以需要通過AIDL進行跨進程通訊

首先在launcher模塊定義好接口

package com.android.wm.shell;

import android.view.SurfaceControl;
import android.graphics.Rect;
import android.window.WindowContainerToken;
import com.android.wm.shell.RunningTaskInfo;

/**
 * Interface for ActivityTaskManager/WindowManager to delegate control of tasks.
 * {@hide}
 */
oneway interface ITaskView {
    /**
     * Called when a Task is starting and the system would like to show a UI to indicate that an
     * application is starting. The client is responsible to add/remove the starting window if it
     * has create a starting window for the Task.
     *
     * @param info The information about the Task that's available
     * @param appToken Token of the application being started.
     */

    /**
     * Called when the Task want to remove the starting window.
     * @param removalInfo The information used to remove the starting window.
     */

    /**
     * Called when the Task want to copy the splash screen.
     */
    void copySplashScreenView(int taskId);

    /**
     * Called when the Task removed the splash screen.
     */
    void onAppSplashScreenViewRemoved(int taskId);

    /**
     * A callback when the Task is available for the registered organizer. The client is responsible
     * for releasing the SurfaceControl in the callback. For non-root tasks, the leash may initially
     * be hidden so it is up to the organizer to show this task.
     *
     * @param taskInfo The information about the Task that's available
     * @param leash A persistent leash for this Task.
     */
    void onTaskAppeared(in RunningTaskInfo taskInfo, in SurfaceControl leash);
    void onTaskVanished(in RunningTaskInfo taskInfo);

    /**
     * Will fire when core attributes of a Task's info change. Relevant properties include the
     * {@link WindowConfiguration.ActivityType} and whether it is resizable.
     *
     * This is used, for example, during split-screen. The flow for starting is: Something sends an
     * Intent with windowingmode. Then WM finds a matching root task and launches the new task into
     * it. This causes the root task's info to change because now it has a task when it didn't
     * before. The default Divider implementation interprets this as a request to enter
     * split-screen mode and will move all other Tasks into the secondary root task. When WM
     * applies this change, it triggers an info change in the secondary root task because it now
     * has children. The Divider impl looks at the info and can see that the secondary root task
     * has adopted an ActivityType of HOME and proceeds to show the minimized dock UX.
     */
    void onTaskInfoChanged(in RunningTaskInfo taskInfo);

    /**
     * Called when the task organizer has requested
     * {@link ITaskOrganizerController.setInterceptBackPressedOnTaskRoot} to get notified when the
     * user has pressed back on the root activity of a task controlled by the task organizer.
     */
    void onBackPressedOnTaskRoot(in RunningTaskInfo taskInfo);

    /**
     * Called when the IME has drawn on the organized task.
     */
    void onImeDrawnOnTask(int taskId);
}
View Code

並且提供Service

<application android:name="com.testCarLauncherApp">
        <service
            android:name="com.test.SharedTaskViewService"
            android:enabled="true"
            android:exported="true" />
    </application>

package com.test.taskview;

import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.view.SurfaceControl;

import com.android.wm.shell.ITaskView;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.RunningTaskInfo;
import com.android.wm.shell.common.HandlerExecutor;

import java.util.concurrent.ConcurrentHashMap;

public class SharedTaskViewService extends Service {
    private static final String TAG = "SharedTaskViewService";
    private ConcurrentHashMap<Integer, Integer> taskIdDisplayMap = new ConcurrentHashMap<>();
    private final HandlerExecutor mExecutor = new HandlerExecutor(new Handler(Looper.getMainLooper()));


    private int getRealDisplayId(int taskId) {
        if (taskIdDisplayMap.containsKey(taskId)) {
            Integer val = taskIdDisplayMap.get(taskId);
            if (val == null) {
                return -1;
            }
            return val.intValue();
        }
        return -2;
    }

    ITaskView taskView = new ITaskView.Stub() {
        public void copySplashScreenView(int taskId) {

        }

        public void onAppSplashScreenViewRemoved(int taskId) {

        }

        public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
            mExecutor.execute(() -> SharedTaskViewService.this.onTaskAppeared(taskInfo, leash));
        }

        public void onTaskVanished(RunningTaskInfo taskInfo) {
            mExecutor.execute(() -> SharedTaskViewService.this.onTaskVanished(taskInfo));
        }


        public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
            mExecutor.execute(() -> SharedTaskViewService.this.onTaskInfoChanged(taskInfo));
        }


        public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {

        }

        /**
         * Called when the IME has drawn on the organized task.
         */
        public void onImeDrawnOnTask(int taskId) {

        }
    };


    public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
        if (SharedTaskManager.getInstance().getListeners() != null) {
            if (taskInfo != null && taskInfo.info != null && taskInfo.info.baseIntent != null && taskInfo.info.baseIntent.getComponent() != null) {
                String pkgName = taskInfo.info.baseIntent.getComponent().getPackageName();
                String clsName = taskInfo.info.baseIntent.getComponent().getClassName();
                Log.w(TAG, "onTaskAppeared clsName:" + clsName);
                boolean hasKey = SharedTaskManager.getInstance().getListeners().containsKey(clsName);
                boolean hasTaskLeash = SharedTaskManager.getInstance().hasTaskLeash(clsName);
                if (hasTaskLeash) {
                    SharedTaskManager.getInstance().addOrUpdateTaskLeash(clsName, new SharedTaskManager.SharedTaskInfo(leash, taskInfo.info.token));
                }

                if (hasKey) {
                    ShellTaskOrganizer.TaskListener taskListener = SharedTaskManager.getInstance().getListeners().get(clsName);
                    if (taskListener != null) {
                        Logger.w(TAG, "onTaskAppeared:" + pkgName + "displayId:" + taskInfo.info.displayId);
                        if (taskInfo.info.displayId == 0 && taskInfo.info.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW && taskInfo.info.numActivities > 0) {
                            taskIdDisplayMap.put(taskInfo.info.taskId, 0);
                            taskListener.onTaskAppeared(taskInfo.info, leash);
                        }
                    }
                } else {
                    Log.w(TAG, "onTaskAppeared,no suiteable key in TaskManager.getInstance().getListeners:" + pkgName);
                }

            }
        }
    }

    public void onTaskVanished(RunningTaskInfo taskInfo) {
        if (SharedTaskManager.getInstance().getListeners() != null) {
            if (taskInfo != null && taskInfo.info != null && taskInfo.info.realActivity != null) {
                String pkgName = taskInfo.info.baseIntent.getComponent().getPackageName();
                String clsName = taskInfo.info.baseIntent.getComponent().getClassName();
                boolean hasKey = SharedTaskManager.getInstance().getListeners().containsKey(clsName);
                if (hasKey) {
                    ShellTaskOrganizer.TaskListener taskListener = SharedTaskManager.getInstance().getListeners().get(clsName);
                    if (taskListener != null) {
                        int realDisplayId = getRealDisplayId(taskInfo.info.taskId);
                        Logger.w(TAG, "onTaskVanished:" + pkgName + ",displayId:" + taskInfo.info.displayId + ",realDisplayId:" + realDisplayId);
                        if (realDisplayId == 0) {
                            taskListener.onTaskVanished(taskInfo.info);
                        }
                    }
                } else {
                    Log.w(TAG, "onTaskVanished,no suiteable key in TaskManager.getInstance().getListeners:" + clsName);
                }
            }
        }
    }


    public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
        if (SharedTaskManager.getInstance().getListeners() != null) {
            if (taskInfo != null && taskInfo.info != null && taskInfo.info.realActivity != null) {
                String pkgName = taskInfo.info.baseIntent.getComponent().getPackageName();
                String clsName = taskInfo.info.baseIntent.getComponent().getClassName();
                boolean hasKey = SharedTaskManager.getInstance().getListeners().containsKey(clsName);
                if (hasKey) {
                    ShellTaskOrganizer.TaskListener taskListener = SharedTaskManager.getInstance().getListeners().get(clsName);
                    if (taskListener != null) {
                        Logger.w(TAG, "onTaskInfoChanged:" + taskInfo.info);
                        taskListener.onTaskInfoChanged(taskInfo.info);
                    }
                } else {
                    Log.w(TAG, "onTaskInfoChanged,no suiteable key in TaskManager.getInstance().getListeners:" + clsName);
                }
            }
        }
    }


    @Override
    public IBinder onBind(Intent intent) {
        Logger.w(TAG, "onBind: ");
        return (IBinder) taskaskView;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Logger.w(TAG, "onCreate: ");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Logger.w(TAG, "onDestroy: ");
    }
}
View Code

 

SystemUI模塊需要繼承TaskOrganizer,在TaskOrganizer的onTaskAppeared中去同步到Launcher模塊

private void bind(){
        Intent intent = new Intent();
        intent.setComponent(new ComponentName(PACKAGE_LAUNCHER, SERVICE_LAUNCHER_TASK_VIEW));
        mContext.bindService(intent,connection,Context.BIND_AUTO_CREATE);
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "bind SharedTaskViewService");
    }

private final ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            synchronized (mLock) {
                StringBuilder tempInfoString = new StringBuilder();
                for (TaskAppearedInfo taskAppearedInfo : tempInfoList) {
                    tempInfoString.append(getPackageName(taskAppearedInfo.getTaskInfo())).append(",");
                }
                Log.d(TAG, "onServiceConnected name = " + name + ", tempInfoList = " + tempInfoString);
                ProtoLog.v(WM_SHELL_TASK_ORG, "onServiceConnected name = " + name);
                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                iTaskView = ITaskView.Stub.asInterface(service);
                isConnecting = false;
                if(tempInfoList.size()>0){
                    try {
                        Log.d(TAG, "tempInfo is not null, notify launcher");
                        for (TaskAppearedInfo taskAppearedInfo : tempInfoList) {
                            Log.d(TAG, "tempInfo = " + getPackageName(taskAppearedInfo.getTaskInfo()));
                            RunningTaskInfo runningTaskInfo = taskAppearedInfo.getTaskInfo();
                            if (runningTaskInfo == null || runningTaskInfo.numActivities == 0) {
                                Log.d(TAG, "ignore empty taskInfo " + runningTaskInfo);
                            } else {
                                iTaskView.onTaskAppeared(new RunningTaskInfo(taskAppearedInfo.getTaskInfo()), taskAppearedInfo.getLeash());
                            }
                        }
                        tempInfoList.clear();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG,"onServiceDisconnected componentName="+name);
            iTaskView = null;
            bind();
        }

        @Override
        public void onBindingDied(ComponentName name) {
            Log.d(TAG,"onBindingDied componentName="+name);
            iTaskView = null;
            bind();
        }
    };

@Override
    public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
        Log.d(TAG, "task appeared " + getPackageName(taskInfo));
        synchronized (mLock) {
            iTaskView.onTaskAppeared(new RunningTaskInfo(info.getTaskInfo()), info.getLeash());
        }
    }
View Code

初始化時通過bind()方法綁定服務,在onTaskAppeared回調中調用服務方法

Launcher在收到AIDL方法onTaskAppeared時會執行 mTransaction.reparent(mTaskLeash, getSurfaceControl()) 顯示當前車模桌面

 

 
user avatar notobarth 头像 hubert-style 头像
点赞 2 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.