美文网首页
投屏优化

投屏优化

作者: 爬不动的卡文丶 | 来源:发表于2022-12-14 15:44 被阅读0次

    原博文地址:https://www.jianshu.com/p/6984790a73c3

    为防止网页失效我复制一份过来

    最近做项目时遇到这样一个问题,原本需求是这样的:

    在一个播放界面,播放时退出当前界面,或者点击home键时,窗口上会显示一个小的悬浮窗,点击这个悬浮窗,就会跳转至播放界面。

    很显然这个悬浮窗是全局的,即时程序退至后台,依然坚挺的显示在界面只上。

    然后测试发现这样一个问题:

    当点击home之后,立即点击悬浮窗跳转,会有一段时间的延迟才会跳转。如果放置一段时间再进行点击,则能立即跳转,没有问题。

    当点击返回,finish掉当前播放页,不管是立即还是放置一会儿再点击,都是ok的。

    附上跳转代码

    Intentintent=newIntent(applicationContext,PlayActivity.class);intent.putExtra(InteractionFmMainActivity.INFO_ID_KEY,PlayActivity.sParamsIdKey);intent.putExtra(InteractionFmMainActivity.INFO_TYPE_KEY,PlayActivity.sParamsInfoTypeKey);intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);applicationContext.startActivity(intent);

    WHY

    通过搜索引擎,得知这原来是Google官方就是这么设置的!

    不从后台启动 Activity 准则

    在谷歌的 Android API Guides 中,特意提醒开发者不要在后台启动 activity,包括在 Service 和 BroadcastReceiver 中,这样的设计是为了避免在用户毫不知情的情况下突然中断用户正在进行的工作,在http://developer.android.com/guide/practices/seamlessness.html#interrupt中有如下解释:

    That is, don't call startActivity() from BroadcastReceivers or Services running in the background. Doing so will interrupt whatever application is currently running, and result in an annoyed user. Perhaps even worse, your Activity may become a "keystroke bandit" and receive some of the input the user was in the middle of providing to the previous Activity. Depending on what your application does, this could be bad news.

    需要违反“不从后台启动 Activity”准则的特例

    特例:即便如此,手机厂商的开发者们在开发基于系统级的应用的时候,可能仍然需要有从 Service 或 BroadcastReceiver 中 startActivity 的需求,往往这样的前提是连这样的 Service 或 BroadcastReceiver 也是由用户的某些操作而触发的,Service 或 BroadcastReceiver 只是充当了即将启动 activity 之前的一些代理参数检查工作以便决定是否需要 start 该 activity。

    除非是上述笔者所述的特殊情况,应用开发者都应该遵循 “不要从后台启动 Activity”准则。

    一个需要特别注意的问题是,特例中所述的情况还会遇到一个问题,就是当通过 home 键将当前 activity 置于后台时,任何在后台startActivity 的操作都将会延迟 5 秒,除非该应用获取了 "android.permission.STOP_APP_SWITCHES" 权限。

    关于延迟 5 秒的操作在com.android.server.am.ActivityManagerService 中的 stopAppSwitches() 方法中,系统级的应用当获取了 "android.permission.STOP_APP_SWITCHES" 后将不会调用到这个方法来延迟通过后台启动 activity 的操作,事实上 android 原生的 Phone 应用就是这样的情况,它是一个获取了"android.permission.STOP_APP_SWITCHES" 权限的系统级应用,当有来电时,一个从后台启动的 activity 将突然出现在用户的面前,警醒用户有新的来电,这样的设计是合理的。

    所以,当你需要开发类似 Phone 这样的应用时,需要做如下工作:

    1. root 你的手机;

    2. 在 AndroidManifest.xml 中添加 "android.permission.STOP_APP_SWITCHES" 用户权限;

    3. 将你开发的应用程序 push 到手机的 /system/app 目录中。

    解决方案

    显然上述的解决方案是行不通的,光是要求root手机,就有点过分了,试问哪个用户会为了你一个app大费周章去root手机,简直是得不偿失。那么真的就无计可施了吗?

    答案当然是NO,不然鄙人也不会在这做这个问题记录

    在Stack Overflow上有这么一个帖子:

    https://stackoverflow.com/questions/5600084/starting-an-activity-from-a-service-after-home-button-pressed-without-the-5-seco#

    最后有一位大神这么回答:

    Intent intent = new Intent(context, A.class);

    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    PendingIntent pendingIntent =

    PendingIntent.getActivity(context, 0, intent, 0);

    try {

    pendingIntent.send();

    } catch (PendingIntent.CanceledException e) {

    e.printStackTrace();

    }

    他将intent用PendingIntent包裹后,进行启动,于是我也按照这方法修改了自己的代码:

    Intentintent=newIntent(applicationContext,PlayActivity.class);intent.putExtra(InteractionFmMainActivity.INFO_ID_KEY,PlayActivity.sParamsIdKey);intent.putExtra(InteractionFmMainActivity.INFO_TYPE_KEY,PlayActivity.sParamsInfoTypeKey);intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);try{PendingIntentpendingIntent=PendingIntent.getActivity(ApplicationGlobal.getGlobalContext(),0,intent,0);pendingIntent.send();}catch(Exceptione){e.printStackTrace();}

    经测试后确实是完美解决了问题,感谢这位大神的解答!

    刨根问底

    那么究竟为什么会有这个问题呢,下面我们从源码的角度进行剖析。

    1.事件分发前的拦截过程

    Home事件在分发前的关键拦截过程:

    ......if(keyCode==KeyEvent.KEYCODE_HOME){if(!down){.........launchHomeFromHotKey();return-1;}........}}voidlaunchHomeFromHotKey(){.....try{ActivityManagerNative.getDefault().stopAppSwitches();}catch(RemoteException e){}.....}

    最后会走到ActivityManagerService的stopAppSwitches()方法

    publicvoidstopAppSwitches(){if(checkCallingPermission(android.Manifest.permission.STOP_APP_SWITCHES)!=PackageManager.PERMISSION_GRANTED){thrownewSecurityException("Requires permission "+android.Manifest.permission.STOP_APP_SWITCHES);}synchronized(this){mAppSwitchesAllowedTime=SystemClock.uptimeMillis()+APP_SWITCH_DELAY_TIME;mDidAppSwitch=false;mHandler.removeMessages(DO_PENDING_ACTIVITY_LAUNCHES_MSG);Messagemsg=mHandler.obtainMessage(DO_PENDING_ACTIVITY_LAUNCHES_MSG);mHandler.sendMessageDelayed(msg,APP_SWITCH_DELAY_TIME);}}

    2.startActivity的启动流程

    关于启动流程,网上已经有很多的相关资料,在这里我们只分析ActivityStackSupervisor类的startActivityLocked的方法,在此方法内我们可以发现在执行下个流程的startActivityUncheckedLocked方法前,会有个条件判断,如下:

    final ActivityStack stack=getFocusedStack();if(stack.mResumedActivity==null||stack.mResumedActivity.info.applicationInfo.uid!=callingUid){if(!mService.checkAppSwitchAllowedLocked(callingPid,callingUid,"Activity start")){PendingActivityLaunch pal=newPendingActivityLaunch(r,sourceRecord,startFlags,stack);mService.mPendingActivityLaunches.add(pal);setDismissKeyguard(false);ActivityOptions.abort(options);returnActivityManager.START_SWITCHES_CANCELED;}}

    由于是后台服务启动的Activity,所以stack.mResumedActivity.info.applicationInfo.uid != callingUid的值肯定为true,其中callingUid为后台服务的UID,stack.mResumedActivity.info.applicationInfo.uid为当前前台显示Activity的UID。

    继续分析ActivityManagerService类的checkAppSwitchAllowedLocked的方法:

    intcheckComponentPermission(Stringpermission,intpid,intuid,intowningUid,booleanexported){...returnActivityManager.checkComponentPermission(permission,uid,owningUid,exported);}

    最后分析ActivityManager类的checkComponentPermission的方法。

    publicstaticintcheckComponentPermission(Stringpermission,intuid,intowningUid,booleanexported){if(uid==0||uid==Process.SYSTEM_UID){returnPackageManager.PERMISSION_GRANTED;}....try{returnAppGlobals.getPackageManager().checkUidPermission(permission,uid);}catch(RemoteExceptione){Slog.e(TAG,"PackageManager is dead?!?",e);}returnPackageManager.PERMISSION_DENIED;}

    由上可以发现后台服务的UID如果为Process.SYSTEM_UID,或者启动的Activity具有android.Manifest.permission.STOP_APP_SWITCHES权限,就不会进入延时5s启动Activity流程,而是进入startActivityUncheckedLocked方法正常启动Activity。

    3.原因分析

    经过一、二的分析,再在关键地方加入日志,把callingUid的值打印出来,最后发现在应用中点击悬浮窗进行跳转操作时(或者直接home后,点击应用图标),callingUid的值为1000,与Process.SYSTEM_UID相等,这种情况是完全ok的,Activity会立即启动。而从桌面点击悬浮窗按钮进行跳转时,callingUid的值为50122,进入到延时5s启动Activity的流程。

    解决跨屏幕投屏和退出投屏从后台拉起页面慢的问题

    1,以理想M平台为例,如果是按照理想给的官方文档来进行投屏,是封装一个播放的VIEW通过调用理想SDK的API去进行切换中控屏幕投屏播放操作,投屏和退出投屏都会比较快。

    2,我们在实现时一时没有办法拆分出来单独的播放器View,我们采用的startActivity 指定屏幕id启动播放。

    重点:

    在使用上述方法的时候,使用pendingintent来包裹,退出投屏的时候启动副驾屏幕activity,可以解决投屏切换慢的问题。

    但是并不完美切换回来拉起播放的Activity,需要初始化,和请求视屏URL重新载入播放,还是会有一些延迟。

    我们可以采取步骤:

    1,开启投屏的时候,startActivity启动播放页面到display 0中控屏,但是副驾屏幕的播放页面并不关闭,副驾页面stop退到后台,副驾打开投屏遮罩页面。

    2,中控屏幕点击退出投屏的时候,使用上述方式使用pendingintent跳转副驾,开起一个《《《透明的页面》》》并在onCreate的时候就把它finish掉,同时finish掉投屏遮罩页面,这样就暴露出了栈下面之前stop的播放页面,并且可以继续播放。

    3,在主驾display 0退出投屏的时候,记录一下播放的进度,倍速,清晰度这些需要记录的播放设置,在副驾播放页面在stop被调到前台播放的时候同步上去,这样就完成了投屏的优化,经过测试,页面切换确实比较快。

    相关文章

      网友评论

          本文标题:投屏优化

          本文链接:https://www.haomeiwen.com/subject/xzclqdtx.html