Android 7.1 FreeForm 多窗口模式

您所在的位置:网站首页 小窗口模式是什么意思 Android 7.1 FreeForm 多窗口模式

Android 7.1 FreeForm 多窗口模式

2024-07-11 13:07:16| 来源: 网络整理| 查看: 265

平台

RK3288 + Android 7.1

关于Freeform

Android N上的多窗口功能有三种模式:(扩展-4)

分屏模式 这种模式可以在手机上使用。该模式将屏幕一分为二,同时显示两个应用的界面。画中画模式 这种模式主要在TV上使用,在该模式下视频播放的窗口可以一直在最顶层显示。Freeform模式 这种模式类似于我们常见的桌面操作系统,应用界面的窗口可以自由拖动和修改大小。 效果图

在这里插入图片描述

切入

在这里插入图片描述 平台默认并没有打开这个模式的支持, 需要增加一个文件以打开Feeeform特性 增加 /system/etc/permissions/android.software.freeform_window_management.xml

打开后: 在这里插入图片描述 看任务右上角 X 旁边的图标 然而, 当尝试点击此按键后, 预想的画面并没有出现, 费解!

排查

跟踪下源码中此界面的布局:

frameworks/base/packages/SystemUI/res/layout/recents_task_view_header.xml

对应的自定义VIEW控件

frameworks/base/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java /* The task bar view */ public class TaskViewHeader extends FrameLayout implements View.OnClickListener, View.OnLongClickListener { @Override protected void onFinishInflate() { SystemServicesProxy ssp = Recents.getSystemServices(); // Initialize the icon and description views mIconView = (ImageView) findViewById(R.id.icon); mIconView.setOnLongClickListener(this); mTitleView = (TextView) findViewById(R.id.title); mDismissButton = (ImageView) findViewById(R.id.dismiss_task); if (ssp.hasFreeformWorkspaceSupport()) { mMoveTaskButton = (ImageView) findViewById(R.id.move_task); } onConfigurationChanged(); } @Override public void onClick(View v) { if (v == mIconView) { //... } else if (v == mMoveTaskButton) { TaskView tv = Utilities.findParent(this, TaskView.class); EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null, mMoveTaskTargetStackId, false)); } else if (v == mAppInfoView) { //... } } }

重点关注点击的实现的事件 关于LaunchTaskEvent

frameworks/base/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java public class LaunchTaskEvent extends EventBus.Event { public final TaskView taskView; public final Task task; public final Rect targetTaskBounds; public final int targetTaskStack; public final boolean screenPinningRequested; public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds, int targetTaskStack, boolean screenPinningRequested) { this.taskView = taskView; this.task = task; this.targetTaskBounds = targetTaskBounds; this.targetTaskStack = targetTaskStack; this.screenPinningRequested = screenPinningRequested; } }

检测是否支持自由窗口模式

frameworks/base/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java /** Private constructor */ private SystemServicesProxy(Context context) { mAccm = AccessibilityManager.getInstance(context); mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); mIam = ActivityManagerNative.getDefault(); mPm = context.getPackageManager(); mIpm = AppGlobals.getPackageManager(); mAssistUtils = new AssistUtils(context); mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mIwm = WindowManagerGlobal.getWindowManagerService(); mUm = UserManager.get(context); mDisplay = mWm.getDefaultDisplay(); mRecentsPackage = context.getPackageName(); mHasFreeformWorkspaceSupport = mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT) || Settings.Global.getInt(context.getContentResolver(), DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0; } /** * Returns whether this device has freeform workspaces. */ public boolean hasFreeformWorkspaceSupport() { return mHasFreeformWorkspaceSupport; }

点击后, 加入事件队列

frameworks/base/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java /** * Sends an event to the subscribers of the given event type immediately. This can only be * called from the same thread as the EventBus's looper thread (for the default EventBus, this * is the main application thread). */ public void send(Event event) { // Fail immediately if we are being called from the non-main thread //... queueEvent(event); } /** * Processes and dispatches the given event to the given event handler, on the thread of whoever * calls this method. */ private void processEvent(final EventHandler eventHandler, final Event event) { //...反射调用. eventHandler.method.invoke(sub, event); //... }

eventHandler的由来:

frameworks/base/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java private static final String METHOD_PREFIX = "onBusEvent"; public void register(Object subscriber) { registerSubscriber(subscriber, DEFAULT_SUBSCRIBER_PRIORITY, null); } public void register(Object subscriber, int priority) { registerSubscriber(subscriber, priority, null); } /** * Registers a new subscriber. */ private void registerSubscriber(Object subscriber, int priority, MutableBoolean hasInterprocessEventsChangedOut) { //... // Find all the valid event bus handler methods of the subscriber MutableBoolean isInterprocessEvent = new MutableBoolean(false); Method[] methods = subscriberType.getDeclaredMethods(); for (Method m : methods) { Class[] parameterTypes = m.getParameterTypes(); isInterprocessEvent.value = false; if (isValidEventBusHandlerMethod(m, parameterTypes, isInterprocessEvent)) { Class eventType = (Class) parameterTypes[0]; ArrayList eventTypeHandlers = mEventTypeMap.get(eventType); if (eventTypeHandlers == null) { eventTypeHandlers = new ArrayList(); mEventTypeMap.put(eventType, eventTypeHandlers); } if (isInterprocessEvent.value) { try { // Enforce that the event must have a Bundle constructor eventType.getConstructor(Bundle.class); mInterprocessEventNameMap.put(eventType.getName(), (Class) eventType); if (hasInterprocessEventsChangedOut != null) { hasInterprocessEventsChangedOut.value = true; } } catch (NoSuchMethodException e) { throw new RuntimeException("Expected InterprocessEvent to have a Bundle constructor"); } } EventHandlerMethod method = new EventHandlerMethod(m, eventType); EventHandler handler = new EventHandler(sub, method, priority); eventTypeHandlers.add(handler); //保存函数 subscriberMethods.add(method); sortEventHandlersByPriority(eventTypeHandlers); if (DEBUG_TRACE_ALL) { logWithPid(" * Method: " + m.getName() + " event: " + parameterTypes[0].getSimpleName() + " interprocess? " + isInterprocessEvent.value); } } } //... } //检测对应的方法 /** * @return whether {@param method} is a valid (normal or interprocess) event bus handler method */ private boolean isValidEventBusHandlerMethod(Method method, Class[] parameterTypes, MutableBoolean isInterprocessEventOut) { int modifiers = method.getModifiers(); if (Modifier.isPublic(modifiers) && Modifier.isFinal(modifiers) && method.getReturnType().equals(Void.TYPE) && parameterTypes.length == 1) { if (EventBus.InterprocessEvent.class.isAssignableFrom(parameterTypes[0]) && method.getName().startsWith(INTERPROCESS_METHOD_PREFIX)) { isInterprocessEventOut.value = true; return true; } else if (EventBus.Event.class.isAssignableFrom(parameterTypes[0]) && method.getName().startsWith(METHOD_PREFIX)) { isInterprocessEventOut.value = false; return true; } else { if (DEBUG_TRACE_ALL) { if (!EventBus.Event.class.isAssignableFrom(parameterTypes[0])) { logWithPid(" Expected method take an Event-based parameter: " + method.getName()); } else if (!method.getName().startsWith(INTERPROCESS_METHOD_PREFIX) && !method.getName().startsWith(METHOD_PREFIX)) { logWithPid(" Expected method start with method prefix: " + method.getName()); } } } } else { if (DEBUG_TRACE_ALL) { if (!Modifier.isPublic(modifiers)) { logWithPid(" Expected method to be public: " + method.getName()); } else if (!Modifier.isFinal(modifiers)) { logWithPid(" Expected method to be final: " + method.getName()); } else if (!method.getReturnType().equals(Void.TYPE)) { logWithPid(" Expected method to return null: " + method.getName()); } } } return false; }

处理事件, 开始执行切换

frameworks/base/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java public final void onBusEvent(LaunchTaskEvent event) { mLastTaskLaunchedWasFreeform = event.task.isFreeformTask(); mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView, event.taskView, event.screenPinningRequested, event.targetTaskBounds, event.targetTaskStack); } frameworks/base/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java /** * Launches the specified {@link Task}. */ public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task, final TaskStackView stackView, final TaskView taskView, final boolean screenPinningRequested, final Rect bounds, final int destinationStack) { final ActivityOptions opts = ActivityOptions.makeBasic(); if (bounds != null) { opts.setLaunchBounds(bounds.isEmpty() ? null : bounds); } //... if (taskView == null) { // If there is no task view, then we do not need to worry about animating out occluding // task views, and we can launch immediately startTaskActivity(stack, task, taskView, opts, transitionFuture, animStartedListener); } else { LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView, screenPinningRequested); if (task.group != null && !task.group.isFrontMostTask(task)) { launchStartedEvent.addPostAnimationCallback(new Runnable() { @Override public void run() { startTaskActivity(stack, task, taskView, opts, transitionFuture, animStartedListener); } }); EventBus.getDefault().send(launchStartedEvent); } else { EventBus.getDefault().send(launchStartedEvent); startTaskActivity(stack, task, taskView, opts, transitionFuture, animStartedListener); } } Recents.getSystemServices().sendCloseSystemWindows( BaseStatusBar.SYSTEM_DIALOG_REASON_HOME_KEY); } /** * Starts the activity for the launch task. * * @param taskView this is the {@link TaskView} that we are launching from. This can be null if * we are toggling recents and the launch-to task is now offscreen. */ private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView, ActivityOptions opts, IAppTransitionAnimationSpecsFuture transitionFuture, final ActivityOptions.OnAnimationStartedListener animStartedListener) { SystemServicesProxy ssp = Recents.getSystemServices(); if (ssp.startActivityFromRecents(mContext, task.key, task.title, opts)) { // Keep track of the index of the task launch int taskIndexFromFront = 0; int taskIndex = stack.indexOfStackTask(task); if (taskIndex > -1) { taskIndexFromFront = stack.getTaskCount() - taskIndex - 1; } EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront)); } else { // Dismiss the task if we fail to launch it if (taskView != null) { taskView.dismissTask(); } // Keep track of failed launches EventBus.getDefault().send(new LaunchTaskFailedEvent()); } if (transitionFuture != null) { ssp.overridePendingAppTransitionMultiThumbFuture(transitionFuture, wrapStartedListener(animStartedListener), true /* scaleUp */); } } frameworks/base/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java /** Starts an activity from recents. */ public boolean startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName, ActivityOptions options) { if (mIam != null) { try { if (taskKey.stackId == DOCKED_STACK_ID) { // We show non-visible docked tasks in Recents, but we always want to launch // them in the fullscreen stack. if (options == null) { options = ActivityOptions.makeBasic(); } options.setLaunchStackId(FULLSCREEN_WORKSPACE_STACK_ID); } mIam.startActivityFromRecents( taskKey.id, options == null ? null : options.toBundle()); return true; } catch (Exception e) { Log.e(TAG, context.getString(R.string.recents_launch_error_message, taskName), e); } } return false; }

进入ActivityManagerService并切换

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java public final int startActivityFromRecents(int taskId, Bundle bOptions) frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java final int startActivityFromRecentsInner(int taskId, Bundle bOptions) 解决 原因: 在 RecentsTransitionHelper.java中, 打开任务的参数缺少了ActivityOptions.setLaunchStackId的设置: //frameworks/base/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task, final TaskStackView stackView, final TaskView taskView, final boolean screenPinningRequested, final Rect bounds, final int destinationStack) { final ActivityOptions opts = ActivityOptions.makeBasic(); //----新增代码---- opts.setLaunchStackId(destinationStack); if (bounds != null) { opts.setLaunchBounds(bounds.isEmpty() ? null : bounds); } //... }

编译并更新SystemUI, 完成!

扩展 Android Freeform模式 关键最后一步How to Enable Freeform Multi-Window Mode in Android NougatAndroid 7.0中的多窗口实现解析Android N 多窗口功能初探几个关键变量: //frameworks/base/core/java/android/app/ActivityManager.java /** Home activity stack ID. */ public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID; /** ID of stack where fullscreen activities are normally launched into. */ public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1; /** ID of stack where freeform/resized activities are normally launched into. */ public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1; /** ID of stack that occupies a dedicated region of the screen. */ public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1; /** ID of stack that always on top (always visible) when it exist. */ public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;


【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭