赛博朋克2077最低配置-80070002

activitymanager
2023年4月3日发(作者:oracleclient)

Android后台启动Activity的实现⽰例

⽬录

概述

原⽣AndroidROM

定制化ROM

检测后台弹出界⾯权限

AndroidP后台启动权限

AndroidQ后台启动权限

总结

概述

前⼏天产品提了⼀个需求,想在后台的时候启动我们APP的⼀个Activity,随着Android版本的更新,以及各家ROM⼚商的

⽆限改造,这种影响⽤户体验的功能许多都受到了限制,没办法,虽然是⽐较流氓的功能,但拿⼈钱财替⼈消灾,于是开启了

哼哧哼哧的调研之路。

原⽣AndroidROM

⾸先从Android的原⽣ROM开始,根据官⽅的介绍,后台启动Activity的限制是从Android10(API29)才开始的,在此之前

原⽣ROM是没有这个限制的,于是我分别启动了⼀个Android9(API28)和10(API29)版本的模拟器,发现在API28上可以

直接从后台启动Activity,⽽在API29上则受到了限制⽆法直接启动。参照官⽅的限制的说明,给出了⼀些不受限制的例外

情况,此外官⽅的推荐是对于后台启动的需求,先向⽤户展⽰⼀个Notification⽽不是直接启动Activity,然后在⽤户点击

Notification后才处理对应的逻辑。还可以在设置Notification时通过setFullScreenIntent添加⼀个全屏Intent对象,该⽅法经

过测试,可以在Android10的模拟器上从后台启动⼀个Activity界⾯(需要

_FULL_SCREEN_INTENT权限)。代码如下:

objectNotificationUtils{

privateconstvalID="channel_1"

privateconstvalNAME="notification"

privatevarmanager:NotificationManager?=null

privatefungetNotificationManagerManager(context:Context):NotificationManager?{

if(manager==null){

manager=temService(CATION_SERVICE)as?NotificationManager

}

returnmanager

}

funsendNotificationFullScreen(context:Context,title:String?,content:String?){

if(_INT>=26){

clearAllNotification(context)

valchannel=NotificationChannel(ID,NAME,ANCE_HIGH)

nd(null,null)

getNotificationManagerManager(context)?.createNotificationChannel(channel)

valnotification=getChannelNotificationQ(context,title,content)

getNotificationManagerManager(context)?.notify(1,notification)

}

}

privatefunclearAllNotification(context:Context){

getNotificationManagerManager(context)?.cancelAll()

}

privatefungetChannelNotificationQ(context:Context,title:String?,content:String?):Notification{

valfullScreenPendingIntent=ivity(

context,

0,

ent(context),

_UPDATE_CURRENT

)

valnotificationBuilder=r(context,ID)

.setSmallIcon(_launcher_foreground)

.setContentTitle(title)

.setContentText(content)

.setSound(null)

.setPriority(TY_MAX)

.setCategory(RY_CALL)

.setOngoing(true)

.setFullScreenIntent(fullScreenPendingIntent,true)

()

}

}

到现在,整体上感觉还是不错的,现阶段的Android原⽣ROM都能正常地从后台启动Activity界⾯,⽆论是Android9还是

10版本,都美滋滋。

定制化ROM

问题开始浮出⽔⾯,由于各⼤⼚商对Android的定制化各有不⼀,⽽Android并没有继承GPL协议,它使⽤的是Apache开

源许可协议,即第三⽅⼚商在修改代码后可以闭源,因此也⽆法得知⼚商ROM的源码到底做了哪些修改。有的机型增加了⼀

项权限——后台弹出界⾯,⽐如说在MIUI上便新增了这项权限且默认是关闭的,除⾮加⼊了它们的⽩名单,的⽂档⾥有说

明:该权限默认为拒绝的,既为应⽤默认不允许在后台弹出页⾯,针对特殊应⽤会提供⽩名单,例如⾳乐(歌词显⽰)、运

动、VOIP(来电)等;⽩名单应⽤⼀旦出现推⼴等恶意⾏为,将永久取消⽩名单。

检测后台弹出界⾯权限

在⼩⽶机型上,新增的这个后台弹出界⾯的权限是在AppOpsService⾥扩展了新的权限,查看AppOpsManager源代码,

可以在⾥⾯看到许多熟悉的常量:

@SystemService(_OPS_SERVICE)

publicclassAppOpsManager{

publicstaticfinalintOP_GPS=2;

publicstaticfinalintOP_READ_CONTACTS=4;

//...

}

因此可以通过AppOpsService来检测是否具有后台弹出界⾯的权限,那么这个权限对应的OpCode是啥呢?⽹上有知情⼈

⼠透露这个权限的Code是10021,因此可以使⽤pNoThrow或NoThrow

等系列的⽅法检测该权限是否存在,不过这些⽅法都是@hide标识的,需要使⽤反射:

funcheckOpNoThrow(context:Context,op:Int):Boolean{

valops=temService(_OPS_SERVICE)asAppOpsManager

try{

valmethod:Method=hod(

"checkOpNoThrow",Int::imitiveType,Int::imitiveType,String::

)

valresult=(ops,op,myUid(),eName)asInt

returnresult==_ALLOWED

}catch(e:Exception){

tackTrace()

}

returnfalse

}

funnoteOpNoThrow(context:Context,op:Int):Int{

valops=temService(_OPS_SERVICE)asAppOpsManager

try{

valmethod:Method=hod(

"noteOpNoThrow",Int::imitiveType,Int::imitiveType,String::

)

(ops,op,myUid(),eName)asInt

}catch(e:Exception){

tackTrace()

}

return-100

}

另外如果想知道其它新增权限的code,可以通过上⾯的⽅法去遍历某个范围(如10000~10100)内的code的权限,然后⼿机操

作去开关想要查询的权限,根据遍历的结果,就⼤致可以得到对应权限的code了。

AndroidP后台启动权限

在⼩⽶Max3上测试发现了两种⽅式可以实现从后台启动Activity界⾯,其系统是基于Android9的MIUI系统。

⽅式⼀:moveTaskToFront

这种⽅式不算是直接从后台启动Activity,⽽是换了⼀个思路,在后台启动⽬标Activity之前先将应⽤切换到前台,然后再启

动⽬标Activity,如果有必要的话,还可以通过skToBack⽅法将之前切换到前台的Activity重新移⼊后台,

经过测试,在Android10上这个⽅法已经失效了...但是10以下的版本还是可以抢救⼀下的(需要声明

R_TASKS权限)。

启动⽬标Activity之前先判断⼀下应⽤是否在后台,判断⽅法可以借助ningAppProcesses⽅法或者

tyLifecycleCallbacks来监听前后台,这两种⽅法⽹上都有⽂章讲解,就不赘述了。直接贴出后台切换到前

台的代码:

funmoveToFront(context:Context){

valactivityManager=temService(TY_SERVICE)as?ActivityManager

activityManager?.getRunningTasks(100)?.forEach{taskInfo->

if(ivity?.packageName==eName){

Log.d("LLL","Trytomovetofront")

skToFront(,0)

return

}

}

}

funstartActivity(activity:Activity,intent:Intent){

if(!isRunningForeground(activity)){

Log.d("LLL","Nowisinbackground")

if(_INT

//TODO防⽌moveToFront失败,可以多尝试调⽤⼏次

moveToFront(activity)

ctivity(intent)

skToBack(true)

}else{

tificationFullScreen(activity,"","")

}

}else{

Log.d("LLL","Nowisinforeground")

ctivity(intent)

}

}

⽅式⼆:Hook

由于MIUI系统不开源,因此尝试再研究研究AOSP源码,死马当活马医看能不能找到什么蛛丝马迹。⾸先从

ctivity⽅法开始追,如果阅读过Activity启动源码流程的话可以知道ctivity或调⽤到

artActivity中,然后通过Binder调⽤到AMS相关的⽅法,权限认证就在AMS中完成,如果权限不满

⾜⾃然启动就失败了(Android10)。

//APP进程

publicActivityResultexecStartActivity(Contextwho,IBindercontextThread,...){

//...

//这⾥会通过Binder调⽤到AMS相关的代码

intresult=vice().startActivity(whoThread,ePackageName(),

intent,eTypeIfNeeded(tentResolver()),

token,target!=null?dedID:null,requestCode,0,null,options);

//...

}

//system_server进程

//AMS

publicfinalintstartActivity(IApplicationThreadcaller,StringcallingPackage,Intentintent,

StringresolvedType,IBinderresultTo,StringresultWho,intrequestCode,

intstartFlags,ProfilerInfoprofilerInfo,BundlebOptions){

//...

}

看⼀下这⼏个参数:

caller:AMS在完成相关任务后会通过它来Binder调⽤到客户端APP进程来实例化Activity对象并回调其⽣命周期⽅

法,caller的Binder服务端位于APP进程。

callingPackage:这个参数标识调⽤者包名。

...

这⾥可以尝试Hook⼀些系统的东西,具体怎么Hook的代码先不给出了,经过测试在Android9的⼩⽶设备上可以成功,有

兴趣可以⾃⾏研究谈论哈,暂时不公开了,有需要的同学可以留⾔告诉我。或者反编译⼩⽶ROM源码,可以从⾥⾯发现⼀些

东西。

AndroidQ后台启动权限

在上⾯介绍过AndroidQ版本开始原⽣系统也加⼊了后台启动的限制,通过通知设置fullScreenIntent可以在原⽣Android10

系统上从后台启动Activity。查看AOSP源码,可以在AMS找到这部分后台权限限制的代码,上⾯讲到startActivity的流程,

在APP进程发起请求后,会通过Binder跨进程调⽤到system_server进程中的AMS,然后调⽤到

ctivity⽅法,关于后台启动的限制就这这⾥:

//好家伙,整整⼆⼗多个参数,嘿嘿,嘿嘿

privateintstartActivity(IApplicationThreadcaller,Intentintent,IntentephemeralIntent,

StringresolvedType,ActivityInfoaInfo,ResolveInforInfo,

IVoiceInteractionSessionvoiceSession,IVoiceInteractorvoiceInteractor,

IBinderresultTo,StringresultWho,intrequestCode,intcallingPid,intcallingUid,

StringcallingPackage,intrealCallingPid,intrealCallingUid,intstartFlags,

SafeActivityOptionsoptions,

booleanignoreTargetSecurity,booleancomponentSpecified,ActivityRecord[]outActivity,

TaskRecordinTask,booleanallowPendingRemoteAnimationRegistryLookup,

PendingIntentRecordoriginatingPendingIntent,booleanallowBackgroundActivityStart){

//...

booleanabort=!tartAnyActivityPermission(intent,aInfo,resultWho,

requestCode,callingPid,callingUid,callingPackage,ignoreTargetSecurity,

inTask!=null,callerApp,resultRecord,resultStack);

abort|=!tartActivity(intent,callingUid,

callingPid,resolvedType,ationInfo);

abort|=!missionPolicyInternal().checkStartActivity(intent,callingUid,

callingPackage);

booleanrestrictedBgActivity=false;

if(!abort){

restrictedBgActivity=shouldAbortBackgroundActivityStart(callingUid,

callingPid,callingPackage,realCallingUid,realCallingPid,callerApp,

originatingPendingIntent,allowBackgroundActivityStart,intent);

}

//...

}

这⾥的shouldAbortBackgroundActivityStart调⽤是在AndroidQ中新增的,看⽅法名就能菜⼑这是针对后台启动的:

booleanshouldAbortBackgroundActivityStart(...){

finalintcallingAppId=Id(callingUid);

if(callingUid==_UID||callingAppId==_UID

||callingAppId==_UID){

returnfalse;

}

if(callingUidHasAnyVisibleWindow||isCallingUidPersistentSystemProcess){

returnfalse;

}

//don'tabortifthecallingUidhasSTART_ACTIVITIES_FROM_BACKGROUNDpermission

if(ermission(START_ACTIVITIES_FROM_BACKGROUND,callingPid,callingUid)

==PERMISSION_GRANTED){

returnfalse;

}

//don'tabortifthecallerhasthesameuidastherecentscomponent

if(erRecents(callingUid)){

returnfalse;

}

//don'tabortifthecallingUidisthedeviceowner

if(ceOwner(callingUid)){

returnfalse;

}

//don'tabortifthecallingUidhasSYSTEM_ALERT_WINDOWpermission

if(temAlertWindowPermission(callingUid,callingPid,callingPackage)){

Slog.w(TAG,"Backgroundactivitystartfor"+callingPackage

+"allowedbecauseSYSTEM_ALERT_WINDOWpermissionisgranted.");

returnfalse;

}

//...

}

从这个⽅法可以看到后台启动的限制和官⽅⽂档的限制中的说明是可以对应上的,这⾥⾯都是针对uid去做权限判断的,且

是在系统进程system_server中完成,单纯更改包名已经没⽤了。。。

在⼀些没有针对后台启动单独做限制的ROM上通过全屏通知可以成功弹出后台Activity页⾯,⽐如说⼩⽶A3,另外还有⼀

台vivo和⼀台三星⼿机,具体机型忘记了;在做了限制的设备上则弹不出来,⽐如说红⽶Note8Pro。

对于红⽶Note8Pro这块硬⾻头,不停尝试了好多⽅法,但其实都是碰运⽓的,因为拿不到MIUI的源码,后来想转变思路,

是否可以尝试从这台⼿机上pull出相关的包然后反编译呢?说不定就有收获!不过需要Root⼿机,这个好

办,⼩⽶⾃⼰是有提供可以Root的开发版系统的,于是就去MIUI官⽹找了⼀下,发现这台红⽶Note8Pro机型没有提供开

发版系统(笑哭),想起来好像之前是说过低端机⼩⽶不再提供开发版了。。。好吧,⼿⾥头没有其它可以尝试的⼿机了。

再转念⼀想,是否可以直接下载稳定版的ROM包,解压后有没有⼯具能够得到⼀些源码相关的痕迹呢?于是下载了⼀个

后,解压看到⾥⾯只有⼀些系统映像img⽂件和.⽂件,这⼀块我还不太懂,猜想就算能得到我想要的东西,

整套流程花费的时间成本估计也超出预期了,所以暂时只能先放下这个想法了。后续有⾜够的时间再深⼊研究研究吧。

总结

原⽣AndroidROM

Android原⽣ROM都能正常地从后台启动Activity界⾯,⽆论是Android9(直接启动)还是10版本(借助全屏通知)。

定制化ROM

检测后台弹出界⾯权限:

通过反射AppOpsManager相关⽅法检测对应opCode的权限;

opCode=10021(⼩⽶机型);

其它机型可以尝试遍历得到opCode;

AndroidP版本的⼩⽶:

通过Hook相关参数来后台启动Activity,代码由于某些原因不能给出了,有需要的同学可以留⾔告诉我哈;

只测试过⼩⽶机型,其它机型不⼀定可⽤;

理论上P版本以下的⼩⽶应该也⽀持;

AndroidP版本的机型:

通过moveTaskToFront⽅法将应⽤切换到前台;

这种⽅法毕竟是官⽅API,因此兼容性可能更好⼀些;

如果切换失败的话可以多尝试⼏次调⽤moveTaskToFront⽅法;

理论上P版本以下的机型应该也⽀持;

AndroidQ版本的机型:

通过系统全屏通知的⽅式调起后台Activity;

在⼀些另作了限制的ROM上可能调起失败;

⾄于反编译MIUI代码的⽅式只是⼀个猜想,时间原因未能付诸⾏动。看样⼦产品哥哥的需求暂时不能完全实现了,不知道有

没有做过相关研究(或者知道内情)的⼩伙伴能不能提供⼀些参考思路,虽然是⼀个⽐较流氓的功能,但是代码是⽆罪的嘿嘿,

朝着⼀个需求⽬标,为此思考解决⽅法,并从各个⽅向去调研,我觉得本⾝是⼀件有意思也有提升的事情!欢迎有过相关研究

的同学在评论区提出建议,做好需求奥⾥给。

以上就是Android后台启动Activity的实现⽰例的详细内容,更多关于Android后台启动Activity的资料请关注其它相关⽂章!

更多推荐

activitymanager