後臺彈出界面權限踩坑

後臺彈出界面權限踩坑

   最近在處理MIUI系統中應用後臺彈出界面權限時踩了一些坑,總結下經驗,方便遇到同樣問題的人。

後臺彈出界面權限

   Android系統/應用自帶了很多權限,爲了限制應用的一些行爲。但“魔高一尺,道高一丈”,現有的一些權限其實不能完全限制一些應用的行爲,所以一些產商會在權限管理中自行添加相關的權限。

在這裏插入圖片描述

   上圖中,後臺彈出界面權限就是MIUI系統自行添加的權限。顧名思義其作用就是限制應用在後臺彈出界面的權利,默認關閉,需手動賦予該權限應用才能在後臺拉起Activity等界面。

   其在系統中的定義如下

//聲明爲 hide,即應用無法通過hook來修改該權限。
    /** @hide Background start Activity */
       public static final int OP_BACKGROUND_START_ACTIVITY = 10021;

   查看了下源碼,其設置的時機是在PermissionConfiguration.java被加載時,在其內部的static方法塊中進行設置以及初始化的。

 {
 // Background start Activity
Permission permission = new Permission(PermissionManager.PERM_ID_BACKGROUND_START_ACTIVITY,
           R.string.HIPS_Perm_background_start_activity,
           R.string.HIPS_Perm_background_start_activity_Desc,
           PermissionManager.GROUP_SETTINGS,
            PermissionManager.ACTION_ACCEPT, PermissionManager.ACTION_ACCEPT);
  permission.setOps(AppOpsManager.OP_BACKGROUND_START_ACTIVITY);
    permission.addFlag(Permission.FLAG_ALWAY_ADD);
   permission.addFlag(Permission.FLAG_NO_ASK);
   sPermissions.add(permission);
}

   MIUI在系統中專門使用ExtraActivityManagerService來控制這些權限的行爲。當你應用嘗試在後臺打開一個界面並且沒有授予該權限時,系統就會打印以下日誌,並且攔截該行爲。

2020-06-09 16:22:42.379 31608-31608/com.kuaiest.video I/Timeline: Timeline: Activity_launch_request time:234147729 intent:Intent { flg=0x10800000 cmp=com.kuaiest.video/.feature.screenlock.TesttAcitivity }
2020-06-09 16:22:42.402 1472-3398/? I/ActivityManager: START u0 {flg=0x10800000 cmp=com.kuaiest.video/.feature.screenlock.TesttAcitivity} from uid 10487
2020-06-09 16:22:42.403 1472-3398/? D/com.android.server.am.ExtraActivityManagerService: MIUILOG- Permission Denied Activity : Intent { flg=0x10800000 cmp=com.kuaiest.video/.feature.screenlock.TesttAcitivity } pkg : com.kuaiest.video uid : 10487 tuid : 10052

繞過方案

   爲了繞過該權限,嘗試了幾種方案如下:

1.前臺服務方式

   開始想到這個權限主要是針對後臺應用,於是想着在恰當的時機啓動個前臺服務,然後在前臺服務中進行界面彈出操作,實現如下。

    //啓動服務
    if (!TestFService.serviceIsLive) {
        // Android 8.0使用startForegroundService在前臺啓動新服務
        mForegroundService = new Intent(this, TestFService.class);
        mForegroundService.putExtra("Foreground", "This is a foreground service.");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(mForegroundService);
        } else {
            startService(mForegroundService);
        }
    } else {
        Toast.makeText(this, "前臺服務正在運行中...", Toast.LENGTH_SHORT).show();
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        LogUtils.d(TAG, "onStartCommand");
        // 標記服務啓動
        TestFService.serviceIsLive = true;
        // 數據獲取
        String data = intent.getStringExtra("Foreground");
        Toast.makeText(this, data, Toast.LENGTH_SHORT).show();
        Intent intent1 = new Intent(this, TesttAcitivity.class);
        intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
        startActivity(intent1);
        return super.onStartCommand(intent, flags, startId);
    }

   嘗試無果,該方式無法繞過這個權限。

2.引導用戶開啓方式

   這種方式是網上一些筆者分享的經驗,但這個方法只是提示用戶去權限管理中開啓權限,而不是繞過權限,所以該方法也不是很好的解決方案。如果有需要可以看下網上這個方法的介紹——後臺彈出界面權限適配方案

3.堆棧調度方式

   這個方法是一個取巧的辦法,能夠讓應用在一些場景下繞過該權限的限制,其實就是利用ams獲取到當前後臺應用的堆棧,然後將堆棧強行推到前臺使應用恢復到前臺,從而繞過權限限制彈出對應的界面,具體看以下方法描述。

    private  void isRunningForegroundToApp(Context context, final Class Class) {
        ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        //利用系統方法獲取當前Task堆棧, 數目可按實際情況來規劃,這裏只是演示
        List<ActivityManager.RunningTaskInfo> taskInfoList = activityManager.getRunningTasks(20);

       
        for (ActivityManager.RunningTaskInfo taskInfo : taskInfoList) {
            //遍歷找到本應用的 task,並將它切換到前臺
            if (taskInfo.baseActivity.getPackageName().equals(context.getPackageName())) {
                Log.d(TAG, "timerTask  pid " + taskInfo.id);
                Log.d(TAG, "timerTask  processName " + taskInfo.topActivity.getPackageName());
                Log.d(TAG, "timerTask  getPackageName " + context.getPackageName());
                activityManager.moveTaskToFront(taskInfo.id, ActivityManager.MOVE_TASK_WITH_HOME);
                Intent intent = new Intent(context, Class);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                context.startActivity(intent);
                break;
            }
        }
    }

   目前嘗試的方法挺多的,只有上述的幾種方式靠譜些,如果有新的方法可以提出來交流,交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章