日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術文章
文章詳情頁

Android后臺啟動Activity的實現示例

瀏覽:27日期:2022-09-19 09:15:47
概述

前幾天產品提了一個需求,想在后臺的時候啟動我們 APP 的一個 Activity,隨著 Android 版本的更新,以及各家 ROM 廠商的無限改造,這種影響用戶體驗的功能許多都受到了限制,沒辦法,雖然是比較流氓的功能,但拿人錢財替人消災,于是開啟了哼哧哼哧的調研之路。

原生Android ROM

首先從 Android 的原生 ROM 開始,根據官方的介紹,后臺啟動 Activity 的限制是從 Android 10(API 29) 才開始的,在此之前原生 ROM 是沒有這個限制的,于是我分別啟動了一個 Android 9(API 28) 和 10(API 29) 版本的模擬器,發現在 API 28 上可以直接從后臺啟動 Activity,而在 API 29 上則受到了限制無法直接啟動。參照官方 從后臺啟動 Activity 的限制 的說明,給出了一些不受限制的例外情況,此外官方的推薦是對于后臺啟動的需求,先向用戶展示一個 Notification 而不是直接啟動 Activity,然后在用戶點擊 Notification 后才處理對應的邏輯。還可以在設置 Notification 時通過 setFullScreenIntent 添加一個全屏 Intent 對象,該方法經過測試,可以在 Android 10 的模擬器上從后臺啟動一個 Activity 界面(需要 android.permission.USE_FULL_SCREEN_INTENT 權限)。代碼如下:

object NotificationUtils { private const val private const val NAME = 'notification' private var manager: NotificationManager? = null private fun getNotificationManagerManager(context: Context): NotificationManager? {if (manager == null) { manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager}return manager } fun sendNotificationFullScreen(context: Context, title: String?, content: String?) {if (Build.VERSION.SDK_INT >= 26) { clearAllNotification(context) val channel = NotificationChannel(ID, NAME, NotificationManager.IMPORTANCE_HIGH) channel.setSound(null, null) getNotificationManagerManager(context)?.createNotificationChannel(channel) val notification = getChannelNotificationQ(context, title, content) getNotificationManagerManager(context)?.notify(1, notification)} } private fun clearAllNotification(context: Context) {getNotificationManagerManager(context)?.cancelAll() } private fun getChannelNotificationQ(context: Context, title: String?, content: String?): Notification {val fullScreenPendingIntent = PendingIntent.getActivity( context, 0, DemoActivity.genIntent(context), PendingIntent.FLAG_UPDATE_CURRENT)val notificationBuilder = NotificationCompat.Builder(context, ID) .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle(title) .setContentText(content) .setSound(null) .setPriority(NotificationCompat.PRIORITY_MAX) .setCategory(Notification.CATEGORY_CALL) .setOngoing(true) .setFullScreenIntent(fullScreenPendingIntent, true)return notificationBuilder.build() }}

到現在,整體上感覺還是不錯的,現階段的 Android 原生 ROM 都能正常地從后臺啟動 Activity 界面,無論是 Android 9 還是 10 版本,都美滋滋。

定制化ROM

問題開始浮出水面,由于各大廠商對 Android 的定制化各有不一,而 Android 并沒有繼承 GPL 協議,它使用的是 Apache 開源許可協議,即第三方廠商在修改代碼后可以閉源,因此也無法得知廠商 ROM 的源碼到底做了哪些修改。有的機型增加了一項權限——后臺彈出界面,比如說在 MIUI 上便新增了這項權限且默認是關閉的,除非加入了它們的白名單,小米開放平臺的文檔 里有說明:該權限默認為拒絕的,既為應用默認不允許在后臺彈出頁面,針對特殊應用會提供白名單,例如音樂(歌詞顯示)、運動、VOIP(來電)等;白名單應用一旦出現推廣等惡意行為,將永久取消白名單。

檢測后臺彈出界面權限

在小米機型上,新增的這個 后臺彈出界面 的權限是在 AppOpsService 里擴展了新的權限,查看 AppOpsManager 源代碼,可以在里面看到許多熟悉的常量:

@SystemService(Context.APP_OPS_SERVICE)public class AppOpsManager { public static final int OP_GPS = 2; public static final int OP_READ_CONTACTS = 4; // ...}

因此可以通過 AppOpsService 來檢測是否具有 后臺彈出界面 的權限,那么這個權限對應的 OpCode 是啥呢?網上有知情人士透露這個權限的 Code 是 10021,因此可以使用 AppOpsManager.checkOpNoThrow 或 AppOpsManager.noteOpNoThrow 等系列的方法檢測該權限是否存在,不過這些方法都是 @hide 標識的,需要使用反射:

fun checkOpNoThrow(context: Context, op: Int): Boolean { val ops = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager try {val method: Method = ops.javaClass.getMethod( 'checkOpNoThrow', Int::class.javaPrimitiveType, Int::class.javaPrimitiveType, String::class.java)val result = method.invoke(ops, op, myUid(), context.packageName) as Intreturn result == AppOpsManager.MODE_ALLOWED } catch (e: Exception) {e.printStackTrace() } return false}fun noteOpNoThrow(context: Context, op: Int): Int { val ops = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager try {val method: Method = ops.javaClass.getMethod( 'noteOpNoThrow', Int::class.javaPrimitiveType, Int::class.javaPrimitiveType, String::class.java)return method.invoke(ops, op, myUid(), context.packageName) as Int } catch (e: Exception) {e.printStackTrace() } return -100}

另外如果想知道其它新增權限的 code, 可以通過上面的方法去遍歷某個范圍(如10000~10100)內的 code 的權限,然后手機操作去開關想要查詢的權限,根據遍歷的結果,就大致可以得到對應權限的 code 了。

Android P后臺啟動權限

在小米 Max3 上測試發現了兩種方式可以實現從后臺啟動 Activity 界面,其系統是基于 Android 9 的 MIUI 系統。

方式一:moveTaskToFront

這種方式不算是直接從后臺啟動 Activity,而是換了一個思路,在后臺啟動目標 Activity 之前先將應用切換到前臺,然后再啟動目標 Activity,如果有必要的話,還可以通過 Activity.moveTaskToBack 方法將之前切換到前臺的 Activity 重新移入后臺,經過測試,在 Android 10 上這個方法已經失效了...但是 10 以下的版本還是可以搶救一下的(需要聲明 android.permission.REORDER_TASKS 權限)。

啟動目標 Activity 之前先判斷一下應用是否在后臺,判斷方法可以借助 ActivityManager.getRunningAppProcesses 方法或者 Application.ActivityLifecycleCallbacks 來監聽前后臺,這兩種方法網上都有文章講解,就不贅述了。直接貼出后臺切換到前臺的代碼:

fun moveToFront(context: Context) { val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager activityManager?.getRunningTasks(100)?.forEach { taskInfo ->if (taskInfo.topActivity?.packageName == context.packageName) { Log.d('LLL', 'Try to move to front') activityManager.moveTaskToFront(taskInfo.id, 0) return} }}fun startActivity(activity: Activity, intent: Intent) { if (!isRunningForeground(activity)) {Log.d('LLL', 'Now is in background')if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { // TODO 防止 moveToFront 失敗,可以多嘗試調用幾次 moveToFront(activity) activity.startActivity(intent) activity.moveTaskToBack(true)} else { NotificationUtils.sendNotificationFullScreen(activity, '', '')} } else {Log.d('LLL', 'Now is in foreground')activity.startActivity(intent) }}

方式二:Hook

由于 MIUI 系統不開源,因此嘗試再研究研究 AOSP 源碼,死馬當活馬醫看能不能找到什么蛛絲馬跡。首先從 Activity.startActivity 方法開始追,如果閱讀過 Activity 啟動源碼流程的話可以知道 Activity.startActivity 或調用到 Instrumentation.execStartActivity 中,然后通過 Binder 調用到 AMS 相關的方法,權限認證就在 AMS 中完成,如果權限不滿足自然啟動就失敗了(Android 10)。

// APP 進程public ActivityResult execStartActivity(Context who, IBinder contextThread, ...) { // ... // 這里會通過 Binder 調用到 AMS 相關的代碼 int result = ActivityManager.getService().startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, options); // ...}// system_server進程// AMSpublic final int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) { // ...}

看一下這幾個參數:

caller: AMS 在完成相關任務后會通過它來 Binder 調用到客戶端 APP 進程來實例化 Activity 對象并回調其生命周期方法,caller 的 Binder 服務端位于 APP 進程。 callingPackage: 這個參數標識調用者包名。 ...

這里可以嘗試 Hook 一些系統的東西,具體怎么 Hook 的代碼先不給出了,經過測試在 Android 9 的小米設備上可以成功,有興趣可以自行研究談論哈,暫時不公開了,有需要的同學可以留言告訴我。或者反編譯小米 ROM 源碼,可以從里面發現一些東西。

Android Q后臺啟動權限

在上面介紹過 Android Q 版本開始原生系統也加入了后臺啟動的限制,通過通知設置 fullScreenIntent 可以在原生 Android 10 系統上從后臺啟動 Activity。查看 AOSP 源碼,可以在 AMS 找到這部分后臺權限限制的代碼,上面講到 startActivity 的流程,在 APP 進程發起請求后,會通過 Binder 跨進程調用到 system_server 進程中的 AMS,然后調用到 ActivityStarter.startActivity 方法,關于后臺啟動的限制就這這里:

// 好家伙,整整二十多個參數,嘿嘿,嘿嘿private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,String callingPackage, int realCallingPid, int realCallingUid, int startFlags,SafeActivityOptions options,boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup,PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) { // ... boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity,inTask != null, callerApp, resultRecord, resultStack); abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid, callingPid, resolvedType, aInfo.applicationInfo); abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid, callingPackage); boolean restrictedBgActivity = false; if (!abort) {restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid,callingPid, callingPackage, realCallingUid, realCallingPid, callerApp,originatingPendingIntent, allowBackgroundActivityStart, intent); } // ...}

這里的 shouldAbortBackgroundActivityStart 調用是在 Android Q 中新增的,看方法名就能菜刀這是針對后臺啟動的:

boolean shouldAbortBackgroundActivityStart(...) { final int callingAppId = UserHandle.getAppId(callingUid); if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID || callingAppId == Process.NFC_UID) {return false; } if (callingUidHasAnyVisibleWindow || isCallingUidPersistentSystemProcess) {return false; } // don’t abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission if (mService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid) == PERMISSION_GRANTED) {return false; } // don’t abort if the caller has the same uid as the recents component if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {return false; } // don’t abort if the callingUid is the device owner if (mService.isDeviceOwner(callingUid)) {return false; } // don’t abort if the callingUid has SYSTEM_ALERT_WINDOW permission if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {Slog.w(TAG, 'Background activity start for ' + callingPackage+ ' allowed because SYSTEM_ALERT_WINDOW permission is granted.');return false; } // ...}

從這個方法可以看到后臺啟動的限制和官方文檔 從后臺啟動 Activity 的限制 中的說明是可以對應上的,這里面都是針對 uid 去做權限判斷的,且是在系統進程 system_server 中完成,單純更改包名已經沒用了。。。

在一些沒有針對后臺啟動單獨做限制的 ROM 上通過 全屏通知 可以成功彈出后臺 Activity 頁面,比如說小米 A3,另外還有一臺 vivo 和一臺三星手機,具體機型忘記了;在做了限制的設備上則彈不出來,比如說紅米 Note 8 Pro。

對于紅米 Note 8 Pro 這塊硬骨頭,不停嘗試了好多方法,但其實都是碰運氣的,因為拿不到 MIUI 的源碼,后來想轉變思路,是否可以嘗試從這臺手機上 pull 出相關的 framework.jar 包然后反編譯呢?說不定就有收獲!不過需要 Root 手機,這個好辦,小米自己是有提供可以 Root 的開發版系統的,于是就去 MIUI 官網找了一下,發現這臺紅米 Note 8 Pro 機型沒有提供開發版系統(笑哭),想起來好像之前是說過低端機小米不再提供開發版了。。。好吧,手里頭沒有其它可以嘗試的手機了。

再轉念一想,是否可以直接下載穩定版的 ROM 包,解壓后有沒有工具能夠得到一些源碼相關的痕跡呢?于是下載了一個 ROM.zip 后,解壓看到里面只有一些系統映像 img 文件和 .dat.br 文件,這一塊我還不太懂,猜想就算能得到我想要的東西,整套流程花費的時間成本估計也超出預期了,所以暫時只能先放下這個想法了。后續有足夠的時間再深入研究研究吧。

總結

原生Android ROM

Android 原生 ROM 都能正常地從后臺啟動 Activity 界面,無論是 Android 9(直接啟動) 還是 10 版本(借助全屏通知)。

定制化ROM

檢測后臺彈出界面權限:

通過反射 AppOpsManager 相關方法檢測對應 opCode 的權限; opCode = 10021(小米機型); 其它機型可以嘗試遍歷得到 opCode;

Android P版本的小米:

通過Hook相關參數來后臺啟動Activity,代碼由于某些原因不能給出了,有需要的同學可以留言告訴我哈; 只測試過小米機型,其它機型不一定可用; 理論上 P 版本以下的小米應該也支持;

Android P版本的機型:

通過 moveTaskToFront 方法將應用切換到前臺; 這種方法畢竟是官方 API,因此兼容性可能更好一些; 如果切換失敗的話可以多嘗試幾次調用 moveTaskToFront 方法; 理論上 P 版本以下的機型應該也支持;

Android Q版本的機型:

通過系統全屏通知的方式調起后臺 Activity; 在一些另作了限制的 ROM 上可能調起失敗;

至于反編譯 MIUI 代碼的方式只是一個猜想,時間原因未能付諸行動。看樣子產品哥哥的需求暫時不能完全實現了,不知道有沒有做過相關研究(或者知道內情)的小伙伴能不能提供一些參考思路,雖然是一個比較流氓的功能,但是代碼是無罪的嘿嘿,朝著一個需求目標,為此思考解決方法,并從各個方向去調研,我覺得本身是一件有意思也有提升的事情!歡迎有過相關研究的同學在評論區提出建議,做好需求奧里給。

以上就是Android后臺啟動Activity的實現示例的詳細內容,更多關于Android后臺啟動Activity的資料請關注好吧啦網其它相關文章!

標簽: Android
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
免费污视频在线一区| 欧洲一区二区三区精品| av一区二区高清| 啪啪国产精品| 欧美特黄a级高清免费大片a级| 自由日本语亚洲人高潮| 午夜一区在线| 久久成人国产| 日韩一二三区在线观看| 午夜精品影视国产一区在线麻豆| 亚洲综合色婷婷在线观看| 亚洲精品国产精品粉嫩| 欧美视频二区| 麻豆一区二区在线| 日本久久成人网| 午夜在线播放视频欧美| 国产一卡不卡| 亚洲天堂av影院| 在线综合欧美| 国产欧美日韩| 亚洲风情在线资源| 国产亚洲精品v| 日本电影久久久| 精品视频在线一区二区在线| 日韩精品网站| 一区二区三区四区在线观看国产日韩| 国产日韩亚洲欧美精品| 成人一区而且| 国产精品普通话对白| 国产日产精品一区二区三区四区的观看方式| 久久久久亚洲精品中文字幕| 色婷婷精品视频| 日本亚洲视频在线| 亚洲综合电影| 四虎国产精品免费久久| www.九色在线| 综合亚洲自拍| 加勒比视频一区| 国产精品免费看| 精品视频91| 丝袜亚洲另类欧美 | 日韩av专区| 视频一区二区三区入口| 精品视频自拍| 免费久久精品视频| 国产精品成人a在线观看| 亚洲综合激情在线| 日韩在线中文| 欧美有码在线| 91成人精品| 久久影院资源站| 1024精品久久久久久久久| 国产精品欧美在线观看| 婷婷亚洲综合| 精品美女在线视频| 免费精品视频最新在线| 日韩欧美一区二区三区免费看| 日本综合精品一区| 亚洲精品一二三区区别| 精品日韩一区| 日本不卡视频在线观看| 亚洲福利一区| 国产成人免费精品| 日本中文字幕不卡| 黄色日韩在线| 精品国产欧美日韩| 天堂精品久久久久| 美女网站一区| 不卡福利视频| 国产在线视频欧美一区| 69精品国产久热在线观看| 国产精品日韩久久久| 日韩精品影视| 人在线成免费视频| 免费一级欧美片在线观看网站| 日韩一区欧美二区| 亚洲婷婷在线| 日韩综合精品| 97精品国产| 乱一区二区av| 国产精品主播| 奇米色欧美一区二区三区| 亚洲少妇诱惑| 国产精品91一区二区三区| 电影天堂国产精品| 伊人网在线播放| 激情久久99| 老牛国内精品亚洲成av人片| 欧美日韩一视频区二区| 亚洲精品影视| 少妇精品久久久一区二区三区| 国产亚洲毛片在线| 好看的av在线不卡观看| 亚洲成人国产| 欧美亚洲精品在线| 色老板在线视频一区二区| 超级白嫩亚洲国产第一| 国语对白精品一区二区| 久久精品国产999大香线蕉| 国产精品中文| 欧美91在线| 精品久久电影| 久久男人天堂| 1000部精品久久久久久久久| 亚洲一本视频| 久久国产成人| 日韩在线视频一区二区三区| 欧美亚洲三级| 国产欧美午夜| 精品91福利视频| 亚洲va中文在线播放免费| 日韩在线视频精品| 91精品一区二区三区综合| 99久久夜色精品国产亚洲狼| 激情综合网站| 男人的天堂久久精品| 日韩激情一二三区| 你懂的国产精品永久在线| 精品国产一级| 久久精品二区三区| 中文日韩欧美| 日韩av在线免费观看不卡| 国产精品66| 日韩免费小视频| 国产一区白浆| 国产探花一区| 国产成人1区| 国产在线成人| 综合一区在线| 精品国产a一区二区三区v免费| 亚洲一级影院| 日韩国产一二三区| 97精品国产一区二区三区| 日韩视频一区| 国产探花一区二区| 四虎4545www国产精品| 久久先锋影音| 精品一区二区三区中文字幕视频| 欧美日韩在线播放视频| 日韩精品亚洲aⅴ在线影院| 成人午夜毛片| 人人精品人人爱| 久久国产乱子精品免费女| 国产一区二区三区日韩精品 | 日韩精品免费一区二区夜夜嗨| 国产精品17p| 美女久久久久| 国产精品久久久免费| 久久久噜噜噜| 日韩激情av在线| av亚洲一区二区三区| 亚洲精品日本| 日韩中文在线电影| **爰片久久毛片| 欧美日韩国产探花| 欧美一区激情| 一级欧洲+日本+国产| 久久精品超碰| 红桃视频亚洲| 97人人精品| 日本高清久久| 亚洲免费高清| 久久影院资源站| 亚洲tv在线| 亚洲精品在线观看91| 久久精品国产在热久久| 日韩影院在线观看| 99精品视频在线| 久久99高清| 亚洲精品婷婷| 99久久激情| 免费看久久久| 亚洲精品人人| 亚洲精品一区二区妖精| 国产夫妻在线| 国产精品麻豆成人av电影艾秋| 久久亚洲精品伦理| 天堂日韩电影| 国产精品黑丝在线播放| 欧美综合社区国产| 香蕉精品999视频一区二区| 免费在线小视频| 国产精品一区2区3区| 蜜桃免费网站一区二区三区| 视频福利一区| 麻豆视频一区二区| 91大神在线观看线路一区| 丝袜美腿一区二区三区| 国产字幕视频一区二区| 涩涩av在线| 国产一区二区视频在线看| 国产乱子精品一区二区在线观看| 日韩精品一卡二卡三卡四卡无卡| 国产99久久| 蜜桃精品在线| 日韩在线精品| 日韩欧美精品| 日本美女一区| 亚洲www啪成人一区二区| 麻豆成全视频免费观看在线看|