這也許是Android一句話權限適配的最優 解決方案 [復制鏈接]

2019-7-4 09:57
IamCoder 閱讀:358 評論:2 贊:1
Tag:  權限 適配

現狀

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

(以直接調用打電話功能為例)

首先我們項目中可能會有這么一個方法:

/**
* 撥打指定電話
*/
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 < M) {
Utils.makeCall(BeforeActivity.this, "10086");
} else {
//6.0以上
if (ContextCompat.checkSelfPermission(BeforeActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(BeforeActivity.this, new 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();
}
}
});
}
這也許是Android一句話權限適配的最優 解決方案

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

那么關于版本兼容呢?

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

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

(這個檢查結果不一定準確,但是即使不準確,也默認成功(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 提供了跳轉到系統權限設置頁的方法,我們再來看看效果:

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

很多時候,其實綠色部分(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);
}
});

我們再來看看效果:

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

主要源碼分析

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 < permissions.length; ++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<Permission> 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<Activity> 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 < M || targetSdkVersion < M;
}

然后是檢查權限:

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 < KITKAT) {
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);
}
}

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

總結:

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

  1. 支持多項權限同時請求
  2. 支持檢查通知權限
  3. 支持系統權限頁面跳轉
  4. 支持debug模式
  5. https://github.com/soulqw/SoulPermission

我來說兩句
您需要登錄后才可以評論 登錄 | 立即注冊
facelist
所有評論(2)
kengsirLi 2019-7-4 11:21
學習了
回復
1020109096 2019-7-9 11:46
PermissionDebug.d(TAG, "refusedPermissionList.size" + out.size());   篩選被拒的權限 永遠都是 0   跟其他權限框架一樣沒什么卵用,
回復
領先的中文移動開發者社區
18620764416
7*24全天服務
意見反饋:[email protected]

掃一掃關注我們

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粵ICP備15117877號 )

两码中特期期