前言
現(xiàn)在來談 Android 6.0 運(yùn)行時(shí)權(quán)限適配,可以說是很過時(shí)了,可是為什么還要寫呢?
一是試用了目前 GitHub 上排名比較靠前的開源項(xiàng)目,確實(shí)都很棒,但是在易用性還是難以令人滿意,便萌生了自己擼一個(gè)的想法。
二是看了下目前國(guó)內(nèi)主流的應(yīng)用,發(fā)現(xiàn)很多都還沒有適配 Android 6.0 ,因此覺得這篇文章還有它的意義。
使用
既然上面說到了易用性,那我們先來看看使用方法
在需要申請(qǐng)權(quán)限的地方調(diào)用
PermissionReq.with(this) // Activity or Fragment
.permissions(Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE) // 需要申請(qǐng)的權(quán)限
.result(new PermissionReq.Result() { // 申請(qǐng)結(jié)果回調(diào)
@Override
public void onGranted() { // 申請(qǐng)成功
// do something
}
@Override
public void onDenied() { // 申請(qǐng)失敗
// do something
}
})
.request();
在 Activity 基類和 Fragment 基類中添加以下代碼
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
PermissionReq.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
API 設(shè)計(jì)采用了比較流行的流式調(diào)用,不知道大家看了是什么感覺,我覺得使用起來比較簡(jiǎn)單,而且不會(huì)破壞原來的代碼結(jié)構(gòu)。
說到這里,我多說一句,現(xiàn)在有很多開源框架都是使用注解的方式來回調(diào)申請(qǐng)結(jié)果的,我覺得用這種方式雖然代碼層次變少了,但是可讀性變差了,而且可能會(huì)破壞原來代碼結(jié)構(gòu)。
這里還有個(gè)亮點(diǎn)不知道大家注意到?jīng)],我們沒有用到 RequestCode ,那 RequestCode 哪里去了呢,在接下來的內(nèi)容中我會(huì)告訴大家。
這也是我最喜歡的地方,不需要在每個(gè)申請(qǐng)權(quán)限的地方定義一個(gè) RequestCode ,更不用擔(dān)心 RequestCode 會(huì)重復(fù),因?yàn)?PermissionReq 已經(jīng)幫大家處理好了。
源碼解析
看完使用方式后我們來看下內(nèi)部實(shí)現(xiàn),我們按照流程來看
首先我們要檢查 App 注冊(cè)了哪些權(quán)限,如果要申請(qǐng)的權(quán)限壓根就沒有在 Manifest 中注冊(cè),那么肯定會(huì)失敗的
initManifestPermission(activity);
for (String permission : mPermissions) {
if (!sManifestPermissionSet.contains(permission)) {
if (mResult != null) {
mResult.onDenied();
}
return;
}
}
private static Set<String> sManifestPermissionSet;
private static synchronized void initManifestPermission(Context context) {
if (sManifestPermissionSet == null) {
sManifestPermissionSet = new HashSet<>();
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
String[] permissions = packageInfo.requestedPermissions;
Collections.addAll(sManifestPermissionSet, permissions);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}
為了線程安全我們添加了 synchronized 修飾符。
如果要申請(qǐng)的權(quán)限已經(jīng)在 Manifest 中注冊(cè)了,我們接下來就要區(qū)分下系統(tǒng)版本了,如果系統(tǒng)版本低于 26 直接返回成功,否則才需要申請(qǐng)權(quán)限
這段代碼比較簡(jiǎn)單,我就不貼了
如果系統(tǒng)版本 >= 26 ,那么才開始我們真正的申請(qǐng)流程
檢查要申請(qǐng)的權(quán)限是否已經(jīng)被允許,如果已經(jīng)被允許,那么就沒必要再申請(qǐng)了
List<String> deniedPermissionList = getDeniedPermissions(activity, mPermissions);
if (deniedPermissionList.isEmpty()) {
if (mResult != null) {
mResult.onGranted();
}
return;
}
private static List<String> getDeniedPermissions(Context context, String[] permissions) {
List<String> deniedPermissionList = new ArrayList<>();
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
deniedPermissionList.add(permission);
}
}
return deniedPermissionList;
}
如果要申請(qǐng)的權(quán)限沒有全部被允許,那么我們就需要向系統(tǒng)發(fā)送申請(qǐng)了
生成 RequestCode
int requestCode = genRequestCode();
private static AtomicInteger sRequestCode = new AtomicInteger(0);
private static int genRequestCode() {
return sRequestCode.incrementAndGet();
}
還記得上面說我們?cè)谑褂脮r(shí)不需要定義 RequestCode 嗎,至此,RequestCode 終于浮出水面
我們?cè)趦?nèi)部使用一個(gè)靜態(tài)自增的 AtomicInteger 作為 RequestCode ,保證 RequestCode 不會(huì)重復(fù),使用 AtomicInteger 而不直接使用 int 是為了線程安全
向系統(tǒng)發(fā)送申請(qǐng)
String[] deniedPermissions = deniedPermissionList.toArray(new String[deniedPermissionList.size()]);
requestPermissions(mObject, deniedPermissions, requestCode);
sResultArray.put(requestCode, mResult);
@TargetApi(Build.VERSION_CODES.M)
private static void requestPermissions(Object object, String[] permissions, int requestCode) {
if (object instanceof Activity) {
((Activity) object).requestPermissions(permissions, requestCode);
} else if (object instanceof Fragment) {
((Fragment) object).requestPermissions(permissions, requestCode);
}
}
申請(qǐng)時(shí)區(qū)分來源,申請(qǐng)后把 Result 放入 Array 中保存起來,等待申請(qǐng)結(jié)果到達(dá)
申請(qǐng)結(jié)果到達(dá)后通知申請(qǐng)者
public static void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Result result = sResultArray.get(requestCode);
if (result == null) {
return;
}
sResultArray.remove(requestCode);
for (int grantResult : grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
result.onDenied();
return;
}
}
result.onGranted();
}
到這里我們的申請(qǐng)權(quán)限流程已經(jīng)走完了,源碼也看完了。
完整代碼
為了方便大家使用,我貼一下完整代碼
public class PermissionReq {
private static AtomicInteger sRequestCode = new AtomicInteger(0);
private static SparseArray<Result> sResultArray = new SparseArray<>();
private static Set<String> sManifestPermissionSet;
public interface Result {
void onGranted();
void onDenied();
}
private Object mObject;
private String[] mPermissions;
private Result mResult;
private PermissionReq(Object object) {
mObject = object;
}
public static PermissionReq with(@NonNull Activity activity) {
return new PermissionReq(activity);
}
public static PermissionReq with(@NonNull Fragment fragment) {
return new PermissionReq(fragment);
}
public PermissionReq permissions(@NonNull String... permissions) {
mPermissions = permissions;
return this;
}
public PermissionReq result(@Nullable Result result) {
mResult = result;
return this;
}
public void request() {
Activity activity = getActivity(mObject);
if (activity == null) {
throw new IllegalArgumentException(mObject.getClass().getName() + " is not supported");
}
initManifestPermission(activity);
for (String permission : mPermissions) {
if (!sManifestPermissionSet.contains(permission)) {
if (mResult != null) {
mResult.onDenied();
}
return;
}
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
if (mResult != null) {
mResult.onGranted();
}
return;
}
List<String> deniedPermissionList = getDeniedPermissions(activity, mPermissions);
if (deniedPermissionList.isEmpty()) {
if (mResult != null) {
mResult.onGranted();
}
return;
}
int requestCode = genRequestCode();
String[] deniedPermissions = deniedPermissionList.toArray(new String[deniedPermissionList.size()]);
requestPermissions(mObject, deniedPermissions, requestCode);
sResultArray.put(requestCode, mResult);
}
public static void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Result result = sResultArray.get(requestCode);
if (result == null) {
return;
}
sResultArray.remove(requestCode);
for (int grantResult : grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
result.onDenied();
return;
}
}
result.onGranted();
}
@TargetApi(Build.VERSION_CODES.M)
private static void requestPermissions(Object object, String[] permissions, int requestCode) {
if (object instanceof Activity) {
((Activity) object).requestPermissions(permissions, requestCode);
} else if (object instanceof Fragment) {
((Fragment) object).requestPermissions(permissions, requestCode);
}
}
private static List<String> getDeniedPermissions(Context context, String[] permissions) {
List<String> deniedPermissionList = new ArrayList<>();
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
deniedPermissionList.add(permission);
}
}
return deniedPermissionList;
}
private static synchronized void initManifestPermission(Context context) {
if (sManifestPermissionSet == null) {
sManifestPermissionSet = new HashSet<>();
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
String[] permissions = packageInfo.requestedPermissions;
Collections.addAll(sManifestPermissionSet, permissions);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}
private static Activity getActivity(Object object) {
if (object != null) {
if (object instanceof Activity) {
return (Activity) object;
} else if (object instanceof Fragment) {
return ((Fragment) object).getActivity();
}
}
return null;
}
private static int genRequestCode() {
return sRequestCode.incrementAndGet();
}
}
總結(jié)
本文主要介紹了如何快速、簡(jiǎn)單的適配 Android 6.0 運(yùn)行時(shí)權(quán)限,雖然寫的比較晚了,但還是希望能幫到大家。
如果你在閱讀本文時(shí)發(fā)現(xiàn)什么問題或者紕漏,或者你有不同的看法,歡迎指出!