歡迎光臨
每天分享高質量文章

這也許是Android一句話權限適配的更優解決方案

作者:椰子罐頭

鏈接:https://juejin.im/post/5cc184cee51d453f151c7fa6

原理分析模塊非常精彩,去除對 Activity 的依賴,去除手動初始化,一定不要錯過!

現狀

關於運行時的權限不用多說,這個概念已經很久,近期工信部在強推SDK26,我這邊做了一些適配工作,其中有一項就是運行時權限,今天將對運行時權限提供一個更優雅的解決方案,如果你還不瞭解運行時權限,請移步。

Android運行時權限淺談

https://blog.csdn.net/u014626094/article/details/80962051

 

現狀:(以直接呼叫打電話功能為例)

首先我們專案中可能會有這麼一個方法:

 

/**
 * 撥打指定電話
 */
public static void makeCall(Context context, String phoneNumber) {
    Intent intent = new Intent(Intent.ACTION_CALL);
    Uri data = Uri.parse("tel:" + phoneNumber);
    intent.setData(data);
    if (!(context instanceof Activity)) {
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    }
    context.startActivity(intent);
}

那麼在適配動態權限以前,在我們任意用到打電話的業務頁面我們可能就是這麼用:

 

public void makeCall() {
    Utils.makeCall(BeforeActivity.this"10086");
}

於是乎,某一天,我們應用要適配targetSdk 26,首先我們要適配的就是動態權限,所以下麵的代碼就會變成這樣:

 

public void makeCall() {
    //6.0以下 直接即可撥打
    if (android.os.Build.VERSION.SDK_INT         Utils.makeCall(BeforeActivity.this"10086");
    } else {
        //6.0以上
        if (ContextCompat.checkSelfPermission(BeforeActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(BeforeActivity.thisnew String[]{Manifest.permission.CALL_PHONE},
                    REQUEST_CODE_CALL);
        } else {
            Utils.makeCall(BeforeActivity.this"10086");
        }
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_CODE_CALL) {
        if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(BeforeActivity.this"本次撥打電話授權失敗,請手動去設置頁打開權限,或者重試授權權限", Toast.LENGTH_SHORT).show();
        } else {
            Utils.makeCall(BeforeActivity.this"10086");
        }
    }
}

以上就是撥打電話功能新老權限版本的基本實現(還不包括shouldShowRequestPermissionRationale的部分)。

目前也有一些知名的開源庫,如PermissionsDispatcher,RXPermission等。

雖然也能實現我們的功能,但無論自己適配還是現有開源庫方案大體上都會或多或少有以下幾個問題:

 

1、每個頁面都要重寫onPermissionResult方法、維護requestCode、或者第三方庫封裝的onPermissionResult方法,如果專案龐大,適配到每個業務點會非常繁瑣

2、權限申請還區分Activity和Fragment,又要分別處理

 

3、每個權限都要寫大量的if else代碼去做版本判斷,判斷新老機型分別處理

基於第一個業務繁瑣的問題,很多應用選擇適配權限的時候,把所用到的敏感權限放在一個特定的頁面去申請,比如歡迎頁(某知名音樂播放器等),如果授權不成功,則會直接無法進入應用,這樣雖然省事,但是用戶體驗不好,我在應用一打開,提示需要電話權限,用戶會很疑惑。

這樣其實就違背了“運行時授權”的初衷,谷歌希望我們在真正呼叫的該功能的時候去請求,這樣權限請求和用戶的目的是一致的,也更容易授予權限成功。

 

那麼能不能做到如下幾個點呢?

 

1、不需要Activity和Fragment作為載體、不需要去重寫onPermissionResult。

 

2、去除版本判斷。只需要在一個工具類中把某個方法(如打電話)適配,然後全域性呼叫,做到真正的運行時請求。

 

3、一行代碼完成從權限檢查、請求、到最終完成後做事情。

答案當然是有,下麵是我們今天的主角:

SoulPermission

SoulPermission應運而生。

https://github.com/soulqw/SoulPermission/

 

當使用了SoulPermission以後,最直觀上看,我們上面的代碼就變成了這樣:

 

public void makeCall() {
    SoulPermission.getInstance()
            .checkAndRequestPermission(Manifest.permission.CALL_PHONE, new CheckRequestPermissionListener() {
                @Override
                public void onPermissionOk(Permission permission) {
                    Utils.makeCall(AfterActivity.this"10086");
                }

                @Override
                public void onPermissionDenied(Permission permission) {
                    Toast.makeText(AfterActivity.this"本次撥打電話授權失敗,請手動去設置頁打開權限,或者重試授權權限", Toast.LENGTH_SHORT).show();
                }
            });
}

解決問題:

1、解耦Activity和Fragment、不再需要Context、不再需要onPermissionResult

2、內部涵蓋版本判斷,一行代碼解決權限相關操作,無需在呼叫業務方寫權限適配代碼,繼而實現真正呼叫時請求的“真運行時權限”

3、接入成本低,零入侵,僅需要在gradle配置一行代碼

大致工作流程:

如果我以在Android手機上要做一件事(doSomeThing),那麼我最終可以有兩個結果:

 

A:可以做

B:不能做

基於上述流程,那麼SoulPermission的大致工作流程如下:

 

從開始到結束展示了我們上述打電話的流程,A即直接撥打,B即toast提示用戶,無法繼續後續操作,綠色部分流程即可選部分,即對shouldShowRequestPermissionRationale的處理,那麼完整權限流程下來,我們撥打電話的代碼就是這麼寫:

 

public void makeCall() {
    SoulPermission.getInstance()
    .checkAndRequestPermission(Manifest.permission.CALL_PHONE, new CheckRequestPermissionListener() {
        @Override
        public void onPermissionOk(Permission permission) {
            Utils.makeCall(AfterActivity.this"10086");
        }

        @Override
        public void onPermissionDenied(Permission permission) {
            //綠色框中的流程
            //用戶第一次拒絕了權限且沒有勾選"不再提示"的情況下這個值為true,此時告訴用戶為什麼需要這個權限。
            if (permission.shouldRationale) {
                new AlertDialog.Builder(AfterActivity.this)
                        .setTitle("提示")
                        .setMessage("如果你拒絕了權限,你將無法撥打電話,請點擊授予權限")
                        .setPositiveButton("授予"new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                                //用戶確定以後,重新執行請求原始流程
                                makeCall();
                            }
                        }).create().show();
            } else {
                Toast.makeText(AfterActivity.this"本次撥打電話授權失敗,請手動去設置頁打開權限,或者重試授權權限", Toast.LENGTH_SHORT).show();
            }
        }
    });
}

上述便是其在滿足運行時權限下的完整工作流程。

那麼關於版本兼容呢?

針對部分手機6.0以下手機,SoulPermission也做了兼容,可以通過AppOpps 檢查權限,內部將權限名稱做了相應的映射,它的大體流程就是下圖:

https://www.jianshu.com/p/a26f0dd024a6

 

 

(這個檢查結果不一定准確,但是即使不准確,也預設成功(A),保證我們回呼能往下走,不會阻塞流程,其他自己實現了權限系統的手機,如vivo,魅族等也是走此方法,最終走他們自己的權限申請流程)

 

基於對於新老手機版本做了控制,在權限拒絕裡面很多處理也是又可以提取的部分,我們可以把回呼再次封裝一下,進一步減少重覆代碼:

 

public abstract class CheckPermissionWithRationaleAdapter implements CheckRequestPermissionListener {

    private String rationaleMessage;

    private Runnable retryRunnable;

    /**
     * @param rationaleMessage 當用戶首次拒絕彈框時候,根據權限不同給用戶不同的文案解釋
     * @param retryRunnable    用戶點重新授權的runnable 即重新執行原方法
     */
    public CheckPermissionWithRationaleAdapter(String rationaleMessage, Runnable retryRunnable) {
        this.rationaleMessage = rationaleMessage;
        this.retryRunnable = retryRunnable;
    }

    @Override
    public void onPermissionDenied(Permission permission) {
        Activity activity = SoulPermission.getInstance().getTopActivity();
        if (null == activity) {
            return;
        }
        //綠色框中的流程
        //用戶第一次拒絕了權限、並且沒有勾選"不再提示"這個值為true,此時告訴用戶為什麼需要這個權限。
        if (permission.shouldRationale) {
            new AlertDialog.Builder(activity)
                    .setTitle("提示")
                    .setMessage(rationaleMessage)
                    .setPositiveButton("授予"new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                            //用戶確定以後,重新執行請求原始流程
                            retryRunnable.run();
                        }
                    }).create().show();
        } else {
            //此時請求權限會直接報未授予,需要用戶手動去權限設置頁,所以彈框引導用戶跳轉去設置頁
            String permissionDesc = permission.getPermissionNameDesc();
            new AlertDialog.Builder(activity)
                    .setTitle("提示")
                    .setMessage(permissionDesc + "異常,請前往設置->權限管理,打開" + permissionDesc + "。")
                    .setPositiveButton("去設置"new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                            //去設置頁
                            SoulPermission.getInstance().goPermissionSettings();
                        }
                    }).create().show();
        }
    }
}

 

然後我們在App所有打電話的入口處做一次呼叫:

 

  /**
     * 撥打指定電話
     */
    public static void makeCall(final Context context, final String phoneNumber) {
        SoulPermission.getInstance().checkAndRequestPermission(Manifest.permission.CALL_PHONE,
                new CheckPermissionWithRationaleAdapter("如果你拒絕了權限,你將無法撥打電話,請點擊授予權限",
                        new Runnable() {
                            @Override
                            public void run() {
                                //retry
                                makeCall(context, phoneNumber);
                            }
                        }) {
                    @Override
                    public void onPermissionOk(Permission permission) {
                        Intent intent = new Intent(Intent.ACTION_CALL);
                        Uri data = Uri.parse("tel:" + phoneNumber);
                        intent.setData(data);
                        if (!(context instanceof Activity)) {
                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        }
                        context.startActivity(intent);
                    }
                });
    }

那麼這樣下來,在Activity和任何業務頁面的呼叫就只有一行代碼了:

 

findViewById(R.id.bt_call).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        UtilsWithPermission.makeCall(getActivity(), "10086");
    }
});

其中完全拒絕以後,SoulPermission 提供了跳轉到系統權限設置頁的方法,我們再來看看效果:

 

很多時候,其實綠色部分(shouldShowRequestPermissionRationale)其實並不一定必要,反覆的彈框用戶可能會厭煩,大多數情況,我們這麼封裝就好:

 

public abstract class CheckPermissionAdapter implements CheckRequestPermissionListener {

    @Override
    public void onPermissionDenied(Permission permission) {
        //SoulPermission提供棧頂Activity
        Activity activity = SoulPermission.getInstance().getTopActivity();
        if (null == activity) {
            return;
        }
        String permissionDesc = permission.getPermissionNameDesc();
        new AlertDialog.Builder(activity)
                .setTitle("提示")
                .setMessage(permissionDesc + "異常,請前往設置->權限管理,打開" + permissionDesc + "。")
                .setPositiveButton("去設置"new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        //去設置頁
                        SoulPermission.getInstance().goPermissionSettings();
                    }
                }).create().show();
    }
}

我們再寫一個選擇聯繫人的方法:

 

/**
 * 選擇聯繫人
 */
public static void chooseContact(final Activity activity, final int requestCode) {
    SoulPermission.getInstance().checkAndRequestPermission(Manifest.permission.READ_CONTACTS,
            new CheckPermissionAdapter() {
                @Override
                public void onPermissionOk(Permission permission) {
                    activity.startActivityForResult(new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI), requestCode);
                }
            });
}

在Activity中也是一行解決問題:

 

findViewById(R.id.bt_choose_contact).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
      UtilsWithPermission.chooseContact(AfterActivity.this, REQUEST_CODE_CONTACT);
    }
});

代碼細節請參考demo,我們再來看看效果:

 

主要原始碼分析

1、優雅的避掉onPermissionResult

適配權限最大的痛點在於:專案業務頁面繁多,如果你想實現“真運行時權限”的話就需要在業務的Activity或者Fragment中去重寫權限請求回呼方法。

斟酌一番並且在參考了下RxPermission中對權限請求的處理,我決定用同樣的方式—用一個沒有界面的Fragment去完成我們權限請求的操作,下麵貼上部分代碼:

首先定義一個接口,用於封裝權限請求的結果

 

public interface RequestPermissionListener {

    /**
     * 得到權限檢查結果
     *
     * @param permissions 封裝權限的陣列
     */
    void onPermissionResult(Permission[] permissions);

}

然後是我們的Fragment:

 

public class PermissionSupportFragment extends Fragment implements IPermissionActions {

    /**
     * 內部維護requestCode
     */
    private static final int REQUEST_CODE = 11;

    /**
     * 傳入的回呼
     */
    private RequestPermissionListener listener;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //當狀態發生改變,比如設備旋轉時候,Fragment不會被銷毀
        setRetainInstance(true);
    }

    /**
     * 外部請求的最終呼叫方法
     * @param permissions 權限
     * @param listener    回呼
     */
    @TargetApi(M)
    @Override
    public void requestPermissions(String[] permissions, RequestPermissionListener listener) {
        requestPermissions(permissions, REQUEST_CODE);
        this.listener = listener;
    }

    @TargetApi(M)
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        Permission[] permissionResults = new Permission[permissions.length];
        //拿到授權結果以後對結果做一些封裝
        if (requestCode == REQUEST_CODE) {
            for (int i = 0; i                 Permission permission = new Permission(permissions[i], grantResults[i], this.shouldShowRequestPermissionRationale(permissions[i]));
                permissionResults[i] = permission;
            }
        }
        if (listener != null && getActivity() != null && !getActivity().isDestroyed()) {
            listener.onPermissionResult(permissionResults);
        }
    }

}

其中Permission是我們的權限名稱、授予結果、是否需要給用於一個解釋的包裝類:

 

public class Permission {

    private static final String TAG = Permission.class.getSimpleName();
    /**
     * 權限名稱
     */
    public String permissionName;

    /**
     * 授予結果
     */
    public int grantResult;

    /**
     * 是否需要給用戶一個解釋
     */
    public boolean shouldRationale;

    /**
     * 權限是否已經被授予
     */
    public boolean isGranted() {
        return grantResult == PackageManager.PERMISSION_GRANTED;
    }
//。。。

}

至此,我們已經利用自己實現的一個沒有界面的Fragment封裝了運行時權限相關的請求、RequestCode的維護、以及onPermissionResult的回呼、在我們真正呼叫的時候代碼是這樣的:

 

/**
 *
 * @param activity 棧頂 Activity
 * @param permissionsToRequest 待請求的權限
 * @param listener 回呼
 */
private void requestRuntimePermission(final Activity activity, final Permission[] permissionsToRequest, final CheckRequestPermissionsListener listener) {
    new PermissionRequester(activity)
            .withPermission(permissionsToRequest)
            .request(new RequestPermissionListener() {
                @Override
                public void onPermissionResult(Permission[] permissions) {
                    List refusedListAfterRequest = new LinkedList<>();
                    for (Permission requestResult : permissions) {
                        if (!requestResult.isGranted()) {
                            refusedListAfterRequest.add(requestResult);
                        }
                    }
                    if (refusedListAfterRequest.size() == 0) {
                        listener.onAllPermissionOk(permissionsToRequest);
                    } else {
                        listener.onPermissionDenied(PermissionTools.convert(refusedListAfterRequest));
                    }
                }
            });
}

其中PermissionRequester也就是一個簡單的構建者樣式,其中包含了對Activity的型別判斷,根據Activity型別去確定Fragment的實現:

如果是FragmentActivity的實體,則使用Support包中的Fragment,否則用預設的Fragment,這樣就兼容了有些應用的專案的基類不是AppComponentActivity(FragmentActivity)的情形,當然,原則上最低支持4.0,即預設Fragment的支持版本。

 

class PermissionFragmentFactory {

    private static final String FRAGMENT_TAG = "permission_fragment_tag";

    static IPermissionActions create(Activity activity{
        IPermissionActions action;
        if (activity instanceof FragmentActivity) {
            FragmentManager supportFragmentManager = ((FragmentActivity) activity).getSupportFragmentManager();
            PermissionSupportFragment permissionSupportFragment = (PermissionSupportFragment) supportFragmentManager.findFragmentByTag(FRAGMENT_TAG);
            if (null == permissionSupportFragment) {
                permissionSupportFragment = new PermissionSupportFragment();
                supportFragmentManager.beginTransaction()
                        .add(permissionSupportFragment, FRAGMENT_TAG)
                        .commitNowAllowingStateLoss();
            }
            action = permissionSupportFragment;
        } else {
            android.app.FragmentManager fragmentManager = activity.getFragmentManager();
            PermissionFragment permissionFragment = (PermissionFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
            if (null == permissionFragment) {
                permissionFragment = new PermissionFragment();
                activity.getFragmentManager().beginTransaction()
                        .add(permissionFragment, FRAGMENT_TAG)
                        .commitAllowingStateLoss();
            }
            action = permissionFragment;
        }
        return action;
    }
}

至此,整個請求鏈已經很像最外層暴露的CheckAndRequestPermission方法了,就差一個Activity了,那麼引數Activity怎麼來呢?

我們繼續想辦法。

2、再捨去Activity

當然是使用Application中的ActivityLifecycleCallbacks,使用它的registerActivityLifecycleCallbacks,感知Activity宣告周期變化,獲取到當前應用棧頂的Activity,這樣我們就不需要自己手動傳入了。

 

public class PermissionActivityLifecycle implements Application.ActivityLifecycleCallbacks {

    WeakReference topActWeakReference;

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        //原則上只需要onResume,兼容如果在onCreate的時候做權限申請保證此時有Activity物件
        topActWeakReference = new WeakReference<>(activity);
    }

    //.....

    @Override
    public void onActivityResumed(Activity activity) {
        topActWeakReference = new WeakReference<>(activity);
    }

   //.....
}

註冊它僅僅需要一個Application:

 

/**
 * @param context Application
 */
private void registerLifecycle(Application context{
    if (null != lifecycle) {
        context.unregisterActivityLifecycleCallbacks(lifecycle);
    }
    lifecycle = new PermissionActivityLifecycle();
    context.registerActivityLifecycleCallbacks(lifecycle);
}

這樣一來,只要呼叫了初始化方法registerLifecycle,我們就能提供提供棧頂Activity了

 

/**
     * 獲取棧頂Activity
     *
     * @return 當前應用棧頂Activity
     * @throws InitException            初始化失敗
     * @throws ContainerStatusException Activity狀態異常
     */
    private Activity getContainer() {
        // may auto init failed
        if (null == lifecycle || null == lifecycle.topActWeakReference) {
            throw new InitException();
        }
        // activity status error
        if (null == lifecycle.topActWeakReference.get() || lifecycle.topActWeakReference.get().isFinishing()) {
            throw new ContainerStatusException();
        }
        return lifecycle.topActWeakReference.get();
    }

結合起來回到我們之前申請權限的方法(省略了日誌打印和執行緒的判斷,如果需要再細看原始碼):

 

private void requestPermissions(final Permissions permissions, final CheckRequestPermissionsListener listener) {
    //check container status
    final Activity activity;
    try {
        activity = getContainer();
    } catch (Exception e) {
        //activity status error do not request
        return;
    }
    //......
    //finally request
    requestRuntimePermission(activity, permissions.getPermissions(), listener);
}

至此,我們已經能脫離Activity和Fragment,也無需重寫onPermissionResult了,只需要一個ApplicationContext初始化即可。

3、能否更簡便一點?

最後避掉Application(免初始化):

我們可以自定義ContentProvider來完成庫的初始化,我們可以參考Lifecycle組件的初始化:

http://chaosleong.github.io/2017/05/27/How-Lifecycle-aware-Components-actually-works/

 

//lifeCycle定義的初始化Provider
public class LifecycleRuntimeTrojanProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        LifecycleDispatcher.init(getContext());
        ProcessLifecycleOwner.init(getContext());
        return true;
    }
}

 

和它的Manifest檔案:

 

 <application>
        <provider
            android:name="android.arch.lifecycle.LifecycleRuntimeTrojanProvider"
            android:authorities="${applicationId}.lifecycle-trojan"
            android:exported="false"
            android:multiprocess="true" />
    application>

 

 


參照它的實現給我們提供了一個很好的思路,我們可以自定義Provider去初始化一些庫或者其他的內容,現在我們寫一個自己的initContentProvider:

 

public class InitProvider extends ContentProvider {

    @Override
    public boolean onCreate() {
        //初始化我們的庫
        SoulPermission.getInstance().autoInit((Application) getContext());
        return true;
    }
    //......
}

在庫的AndroidManifest檔案中宣告:

 

<application>
    <provider android:authorities="${applicationId}.permission.provider"
              android:name=".permission.InitProvider"
              android:multiprocess="true"
              android:exported="false"/>
application>

 

至於為什麼這個Context就是Application,我們可以參考ActivityThread中的對ContentProvider的初始化:

 

public void handleInstallProvider(ProviderInfo info) {
            //即我們的應用的Application
            installContentProviders(mInitialApplication, Arrays.asList(info));
    }

至此,我們權限申請流程就跟Activity、Fragment、乃至Context都沒有關係了。

4. 去除if&else;、涵蓋版本判斷:

 

雖然我們完成了對運行時權限的申請流程,但是畢竟只針對6.0以上機型,如果上面流程還想一句話完成的話,那我們還得兼容老的機型,so,我們需要做在方法內做一個版本判斷:

首先判斷系統版本

public static boolean isOldPermissionSystem(Context context) {
    int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
    return android.os.Build.VERSION.SDK_INT }

然後是檢查權限:

6.0以上當然是走系統Api:

class RunTimePermissionChecker implements PermissionChecker {

    private String permission;

    private Context context;

    RunTimePermissionChecker(Context context, String permission) {
        this.permission = permission;
        this.context = context;
    }

    @TargetApi(M)
    @Override
    public boolean check() {
        int checkResult = ContextCompat.checkSelfPermission(context, permission);
        return checkResult == PackageManager.PERMISSION_GRANTED;
    }

}

6.0以下、4.4以上通過AppOps反射獲取(為了保證一致性,把權限名稱引數在check方法中做了映射,把權限的String引數映射成checkOp的整形引數):

class AppOpsChecker implements PermissionChecker {

    private Context context;

    private String permission;


    AppOpsChecker(Context context, String permission) {
        this.context = context;
        this.permission = permission;
    }

    /**
     * 老的通過反射方式檢查權限狀態
     * 結果可能不准確,如果傳回false一定未授予
     * 按需在裡面添加
     * 如果沒匹配上或者異常都預設權限授予
     *
     * @return 檢查結果
     */

    @Override
    public boolean check() {
        if (null == permission) {
            return true;
        }
        switch (permission) {
            case Manifest.permission.READ_CONTACTS:
                return checkOp(4);
            case Manifest.permission.WRITE_CONTACTS:
                return checkOp(5);
            case Manifest.permission.CALL_PHONE:
                return checkOp(13);
                   ...
            default:
                break;
        }
        return true;
    }

    boolean checkOp(int op) {
        if (Build.VERSION.SDK_INT             return true;
        }
        try {
            AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
            Method method = AppOpsManager.class.getDeclaredMethod("checkOp"int.class, int.class, String.class);
            return 0 == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }
}

和版本判斷起來就是這樣:

 public static PermissionChecker create(Context context, String permission) {
    if (PermissionTools.isOldPermissionSystem(context)) {
        return new AppOpsChecker(context, permission);
    } else {
         return new RunTimePermissionChecker(context, permission);
    }
}

再到我們最終呼叫的權限檢測方法:

private boolean checkPermission(Context context, String permission) {
        return CheckerFactory.create(context, permission).check();
 }

 


最終我們權限庫一行代碼從權限檢測、權限請求聯合起來的操作就是這樣:

/**
 * 多個權限的檢查與申請
 * 在敏感操作前,先檢查權限和請求權限,當完成操作後可做後續的事情
 *
 * @param permissions 多個權限的申請  Permissions.build(Manifest.permission.CALL_PHONE,Manifest.permission.CAMERA)
 * @param listener    請求之後的回呼
 */
public void checkAndRequestPermissions(@NonNull Permissions permissions, @NonNull final CheckRequestPermissionsListener listener) {
    //首先檢查權限
    Permission[] checkResult = checkPermissions(permissions.getPermissionsString());
    //得到有多少權限被拒絕了
    final Permission[] refusedPermissionList = filterRefusedPermissions(checkResult);
    if (refusedPermissionList.length > 0) {
        //是否可以請求運行時權限,即6.0以上
        if (canRequestRunTimePermission()) {
            //請求權限,並把listener傳下去,也就是我們一開始看請求流程分析中的那個方法
            requestPermissions(Permissions.build(refusedPermissionList), listener);
        } else {
            //無法請求權限,本次操作失敗
            listener.onPermissionDenied(refusedPermissionList);
        }
    } else {
        //沒有權限被拒絕,認為所有權限都ok,回呼成功
        listener.onAllPermissionOk(checkResult);
    }
}

 

至此,我們的三個主要需求的原始碼分析基本完成,如果有啥疑問和細節上的實現,可以自行閱讀原始碼即可。

總結:

SoulPermission很好的適配了真運行時權限、除了上述三個個主要功能以外還提供以下功能:

  1. 支持多項權限同時請求

  2. 支持檢查通知權限

  3. 支持系統權限頁面跳轉

  4. 支持debug樣式

https://github.com/soulqw/SoulPermission

赞(0)

分享創造快樂