Android元件化開發實戰:封裝許可權管理請求框架
導讀:
通過自己動手封裝一個簡單的Android許可權管理請求框架,學習如何釋出開源庫到 Jitpack
/ Jcenter
,從而激發自己的學習興趣,以後自己也能多多造輪子,成為開源界的輪子哥。元件要求簡單易懂易用,提供鏈式呼叫,因此開發元件需要相應的函數語言程式設計思想,函數語言程式設計在處理連續複雜邏輯的程式碼上有天然的優勢,其風格以清晰著稱,是我們封裝工具類元件的不二選擇。
沒有接觸過元件化開發的童鞋,可以先看下面這兩篇文章:
1、Android元件化和外掛化的概念:https://blog.csdn.net/qq15577969/article/details/109594307
2、Android元件化開發簡單示例:https://blog.csdn.net/qq15577969/article/details/109596071
【Android許可權動態請求框架】的github地址:https://github.com/respost/OmgPermission
元件化流程:
- 建立模組,封裝自己的許可權框架
- 將開源庫釋出到
JitPack
倉庫
一、建立Android
專案
1、開啟Android Studio,建立新專案
2、選擇建立空活動 “Empty Activity” → “Next” 下一步
3、填寫專案名稱,包名,儲存路徑,Language語言選擇“java” → Finish
二、建立許可權元件模組
1、在專案的"APP"上右鍵 → New → 新建一個 Module
2、 選擇 Android Library
→ Next
3、名稱填寫library
三、編寫關鍵程式碼
1、建立Permission.java類,這個類主要是將許可權進行分組的,程式碼如下:
import android.Manifest;
import android.os.Build;
/**
* 許可權類
* @author 安陽 QQ:15577969
* @version 1.0
* @team 美奇軟體開發工作室
* @date 2020/11/23 12:54
*/
public final class Permission {
//將許可權共分為9組,每組只要有一個許可權申請成功,就預設整組許可權都可以使用了。
public static final String[] CALENDAR;
public static final String[] CAMERA;
public static final String[] CONTACTS;
public static final String[] LOCATION;
public static final String[] MICROPHONE;
public static final String[] PHONE;
public static final String[] SENSORS;
public static final String[] SMS;
public static final String[] STORAGE;
static {
/**
* Android系統從6.0開始將許可權分為一般許可權和危險許可權:
* 1、一般許可權指不涉及使用者隱私的一些許可權,比如Internet許可權。
* 2、危險許可權指涉及獲取使用者隱私的一些操作所需要的許可權,比如讀取使用者地理位置的許可權。
* Android在對許可權進行分類的同時,還將危險型別的許可權進行了分組劃分,因此我們在申請許可權的時候要一組一組的申請。
*/
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
CALENDAR = new String[]{};
CAMERA = new String[]{};
CONTACTS = new String[]{};
LOCATION = new String[]{};
MICROPHONE = new String[]{};
PHONE = new String[]{};
SENSORS = new String[]{};
SMS = new String[]{};
STORAGE = new String[]{};
} else {
CALENDAR = new String[]{
Manifest.permission.READ_CALENDAR,
Manifest.permission.WRITE_CALENDAR};
CAMERA = new String[]{
Manifest.permission.CAMERA};
CONTACTS = new String[]{
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS,
Manifest.permission.GET_ACCOUNTS};
LOCATION = new String[]{
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION};
MICROPHONE = new String[]{
Manifest.permission.RECORD_AUDIO};
PHONE = new String[]{
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.CALL_PHONE,
Manifest.permission.READ_CALL_LOG,
Manifest.permission.WRITE_CALL_LOG,
Manifest.permission.USE_SIP,
Manifest.permission.PROCESS_OUTGOING_CALLS};
SENSORS = new String[]{
Manifest.permission.BODY_SENSORS};
SMS = new String[]{
Manifest.permission.SEND_SMS,
Manifest.permission.RECEIVE_SMS,
Manifest.permission.READ_SMS,
Manifest.permission.RECEIVE_WAP_PUSH,
Manifest.permission.RECEIVE_MMS};
STORAGE = new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE};
}
}
}
2、建立PermissionUtils.java工具類,這個類主要是封裝一些許可權的通用方法,程式碼如下:
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.fragment.app.Fragment;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* 許可權的工具類
*
* @author 安陽 QQ:15577969
* @version 1.0
* @team 美奇軟體開發工作室
* @date 2020/11/23 13:09
*/
public class PermissionUtils {
/**
* 判斷Android系統版本是否大於6.0
*
* @return
*/
public static boolean judgeVersion() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
/**
* 從申請的許可權中找出未授予的許可權
*
* @param activity
* @param permission
* @return
*/
@TargetApi(value = Build.VERSION_CODES.M)
public static List<String> findDeniedPermissions(Activity activity, String... permission) {
List<String> denyPermissions = new ArrayList<>();
for (String value : permission) {
if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED) {
denyPermissions.add(value);
}
}
return denyPermissions;
}
/**
* 尋找相應的註解方法
*
* @param c1 要尋找的那個類
* @param c2 響應的註解標記
* @return
*/
public static List<Method> findAnnotationMethods(Class c1, Class<? extends Annotation> c2) {
List<Method> methods = new ArrayList<>();
for (Method method : c1.getDeclaredMethods()) {
if (method.isAnnotationPresent(c2)) {
methods.add(method);
}
}
return methods;
}
public static <A extends Annotation> Method findMethodPermissionFailWithRequestCode(Class clazz, Class<A> permissionFailClass, int requestCode) {
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(permissionFailClass)) {
if (requestCode == method.getAnnotation(PermissionFail.class).requestCode()) {
return method;
}
}
}
return null;
}
/**
* 找到相應的註解方法(requestCode請求碼與需要的一樣)
*
* @param m
* @param c
* @param requestCode
* @return
*/
public static boolean isEqualRequestCodeFromAnntation(Method m, Class c, int requestCode) {
if (c.equals(PermissionFail.class)) {
return requestCode == m.getAnnotation(PermissionFail.class).requestCode();
} else if (c.equals(PermissionSuccess.class)) {
return requestCode == m.getAnnotation(PermissionSuccess.class).requestCode();
} else {
return false;
}
}
public static <A extends Annotation> Method findMethodWithRequestCode(Class c, Class<A> annotation, int requestCode) {
for (Method method : c.getDeclaredMethods()) {
if (method.isAnnotationPresent(annotation)) {
if (isEqualRequestCodeFromAnntation(method, annotation, requestCode)) {
return method;
}
}
}
return null;
}
public static <A extends Annotation> Method findMethodPermissionSuccessWithRequestCode(Class c, Class<A> permissionFailClass, int requestCode) {
for (Method method : c.getDeclaredMethods()) {
if (method.isAnnotationPresent(permissionFailClass)) {
if (requestCode == method.getAnnotation(PermissionSuccess.class).requestCode()) {
return method;
}
}
}
return null;
}
public static Activity getActivity(Object object) {
if (object instanceof Fragment) {
return ((Fragment) object).getActivity();
} else if (object instanceof Activity) {
return (Activity) object;
}
return null;
}
}
3、建立成功和失敗的回撥介面類,程式碼如下:
PermissionSuccess.java
/**
* @author 安陽 QQ:15577969
* @version 1.0
* @team 美奇軟體開發工作室
* @date 2020/11/23 13:12
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PermissionSuccess {
int requestCode();
}
PermissionFail.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author 安陽 QQ:15577969
* @version 1.0
* @team 美奇軟體開發工作室
* @date 2020/11/23 13:12
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionFail {
int requestCode();
}
4、建立OmgPermission.java物件類,這個類就是我們封裝的許可權框架主體,程式碼如下:
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.fragment.app.Fragment;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* 動態許可權的物件
* @author 安陽 QQ:15577969
* @version 1.0
* @team 美奇軟體開發工作室
* @date 2020/11/23 12:56
*/
public class OmgPermission {
//許可權集合
private String[] mPermissions;
//請求碼
private int mRequestCode;
//物件
private Object object;
//許可權回撥方法
private static PermissionCallback permissionCallback;
/**
* 構造方法
*/
private OmgPermission(Object object) {
this.object = object;
}
/**
* with函式是將某物件作為函式的引數,在函式塊內可以通過 this 指代該物件。
* 返回值為函式塊的最後一行或指定return表示式。
*/
public static OmgPermission with(Activity activity){
return new OmgPermission(activity);
}
public static OmgPermission with(Fragment fragment){
return new OmgPermission(fragment);
}
/**
* 獲取許可權組集合
* @param permissions
* @return
*/
public OmgPermission permissions(String... permissions){
this.mPermissions = permissions;
return this;
}
/**
* 新增請求碼
* @param requestCode
* @return
*/
public OmgPermission addRequestCode(int requestCode){
this.mRequestCode = requestCode;
return this;
}
@TargetApi(value = Build.VERSION_CODES.M)
public void request(){
permissionCallback = null;
requestPermissions(object, mRequestCode, mPermissions);
}
@TargetApi(value = Build.VERSION_CODES.M)
public void request(PermissionCallback callback){
if(callback!=null) {
permissionCallback = callback;
}
requestPermissions(object, mRequestCode, mPermissions);
}
/**
* 活動請求許可權
* @param activity
* @param requestCode
* @param permissions
*/
public static void needPermission(Activity activity, int requestCode, String[] permissions){
permissionCallback = null;
requestPermissions(activity, requestCode, permissions);
}
public static void needPermission(Activity activity, int requestCode, String permission){
permissionCallback = null;
needPermission(activity, requestCode, new String[] { permission });
}
/**
* 活動請求許可權,帶回撥方法
* @param activity
* @param requestCode
* @param permissions
* @param callback
*/
public static void needPermission(Activity activity, int requestCode, String[] permissions
,OmgPermission.PermissionCallback callback) {
if (callback != null) {
permissionCallback = callback;
}
requestPermissions(activity, requestCode, permissions);
}
public static void needPermission(Activity activity, int requestCode, String permission,PermissionCallback callback){
if (callback != null) {
permissionCallback = callback;
}
needPermission(activity, requestCode, new String[] { permission });
}
/**
* 碎片請求許可權
* @param fragment
* @param requestCode
* @param permissions
*/
public static void needPermission(Fragment fragment, int requestCode, String[] permissions){
permissionCallback = null;
requestPermissions(fragment, requestCode, permissions);
}
public static void needPermission(Fragment fragment, int requestCode, String permission){
permissionCallback = null;
needPermission(fragment, requestCode, new String[] { permission });
}
/**
* 碎片請求許可權,帶回撥方法
* @param fragment
* @param requestCode
* @param permissions
* @param callback
*/
public static void needPermission(Fragment fragment, int requestCode, String[] permissions
,OmgPermission.PermissionCallback callback) {
if (callback != null) {
permissionCallback = callback;
}
requestPermissions(fragment, requestCode, permissions);
}
public static void needPermission(Fragment fragment, int requestCode, String permission,PermissionCallback callback){
if (callback != null) {
permissionCallback = callback;
}
needPermission(fragment, requestCode, new String[] { permission });
}
/**
* 請求許可權
* @param object
* @param requestCode
* @param permissions
*/
@TargetApi(value = Build.VERSION_CODES.M)
private static void requestPermissions(Object object, int requestCode, String[] permissions){
//判斷系統版本是否大於6.0
if(!PermissionUtils.judgeVersion()) {
if (permissionCallback != null) {
permissionCallback.permissionSuccess(requestCode);
}else {
doExecuteSuccess(object, requestCode);
}
return;
}
List<String> deniedPermissions = PermissionUtils.findDeniedPermissions(PermissionUtils.getActivity(object), permissions);
/**
* 先檢查是否有沒有授予的許可權,有的話請求,沒有的話就直接執行許可權授予成功的介面/註解方法
*/
if(deniedPermissions.size() > 0){
if(object instanceof Activity){
((Activity)object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
} else if(object instanceof Fragment){
((Fragment)object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
} else {
throw new IllegalArgumentException(object.getClass().getName() + " is not supported");
}
} else {
if (permissionCallback != null) {
permissionCallback.permissionSuccess(requestCode);
}else {
doExecuteSuccess(object, requestCode);
}
}
}
private static void doExecuteSuccess(Object activity, int requestCode) {
Method executeMethod = PermissionUtils.findMethodWithRequestCode(activity.getClass(),
PermissionSuccess.class, requestCode);
executeMethod(activity, executeMethod);
}
private static void doExecuteFail(Object activity, int requestCode) {
Method executeMethod = PermissionUtils.findMethodWithRequestCode(activity.getClass(),
PermissionFail.class, requestCode);
executeMethod(activity, executeMethod);
}
private static void executeMethod(Object activity, Method executeMethod) {
if(executeMethod != null){
try {
if(!executeMethod.isAccessible()) executeMethod.setAccessible(true);
executeMethod.invoke(activity, new Object[]{});
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
public static void onRequestPermissionsResult(Activity activity, int requestCode, String[] permissions,
int[] grantResults) {
requestResult(activity, requestCode, permissions, grantResults);
}
public static void onRequestPermissionsResult(Fragment fragment, int requestCode, String[] permissions,
int[] grantResults) {
requestResult(fragment, requestCode, permissions, grantResults);
}
/**
* 回撥介面不為空的話,先執行回撥介面的方法,若為空,則尋找響應的註解方法。
* @param obj
* @param requestCode
* @param permissions
* @param grantResults
*/
private static void requestResult(Object obj, int requestCode, String[] permissions,
int[] grantResults){
List<String> deniedPermissions = new ArrayList<>();
for(int i=0; i<grantResults.length; i++){
if(grantResults[i] != PackageManager.PERMISSION_GRANTED){
deniedPermissions.add(permissions[i]);
}
}
if(deniedPermissions.size() > 0){
if(permissionCallback!=null){
permissionCallback.permissionFail(requestCode);
}else {
doExecuteFail(obj, requestCode);
}
} else {
if(permissionCallback!=null){
permissionCallback.permissionSuccess(requestCode);
}else {
doExecuteSuccess(obj, requestCode);
}
}
}
public interface PermissionCallback{
//請求許可權成功
void permissionSuccess(int requsetCode);
//請求許可權失敗
void permissionFail(int requestCode);
}
}
四、本地呼叫許可權框架
1、新增依賴,在主專案的build.gradle檔案的dependencies{}配置裡,新增如下語句:
dependencies {
//整合許可權請求框架
implementation project(':library')
}
2、簡單示例用法:
public class MainActivity extends AppCompatActivity {
//聯絡人請求碼
private final int REQUEST_CONTACT = 100;
//儲存請求碼
private final int REQUEST_STORAGE = 200;
//相機請求碼
private final int REQUEST_CAMERA = 300;
private Button storageButton;
private Button cameraButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/**
* 請求許可權
* request()方法的引數可以有也可以沒有,有且不為空,就會回撥PermissionCallback的響應的回撥方法,沒有或為空,則回撥響應的註解方法。
*/
OmgPermission.with(MainActivity.this)
//新增請求碼
.addRequestCode(REQUEST_CAMERA)
//單獨申請一個許可權
//.permissions(Manifest.permission.CAMERA)
//同時申請多個許可權
.permissions(Manifest.permission.READ_CONTACTS, Manifest.permission.RECEIVE_SMS, Manifest.permission.WRITE_CONTACTS)
.request(new OmgPermission.PermissionCallback(){
@Override
public void permissionSuccess(int requestCode) {
Toast.makeText(MainActivity.this, "成功授予聯絡人許可權,請求碼: " + requestCode, Toast.LENGTH_SHORT).show();
}
@Override
public void permissionFail(int requestCode) {
Toast.makeText(MainActivity.this, "授予聯絡人許可權失敗,請求碼: " + requestCode, Toast.LENGTH_SHORT).show();
}
});
}
/**
* 回撥註解方法
* 當request()沒有引數的時候,就會在當前類裡面尋找相應的註解方法
*/
@PermissionSuccess(requestCode = REQUEST_STORAGE)
public void permissionSuccess() {
Toast.makeText(MainActivity.this, "回撥註解方法:成功授予讀寫許可權" , Toast.LENGTH_SHORT).show();
}
@PermissionFail(requestCode = REQUEST_STORAGE)
public void permissionFail() {
Toast.makeText(MainActivity.this, "回撥註解方法:授予讀寫許可權失敗" , Toast.LENGTH_SHORT).show();
}
@PermissionSuccess(requestCode = REQUEST_CONTACT)
public void permissionSuccessContact() {
Toast.makeText(MainActivity.this, "回撥註解方法:成功授予聯絡人許可權" , Toast.LENGTH_SHORT).show();
}
@PermissionFail(requestCode = REQUEST_CONTACT)
public void permissionFailContact() {
Toast.makeText(MainActivity.this, "回撥註解方法:授予聯絡人許可權失敗" , Toast.LENGTH_SHORT).show();
}
@PermissionSuccess(requestCode = REQUEST_CAMERA)
public void permissionSuccessCamera() {
Toast.makeText(MainActivity.this, "回撥註解方法:成功授予相機許可權" , Toast.LENGTH_SHORT).show();
}
@PermissionFail(requestCode = REQUEST_CAMERA)
public void permissionFailCamera() {
Toast.makeText(MainActivity.this, "回撥註解方法:授予相機許可權失敗" , Toast.LENGTH_SHORT).show();
}
/**
* 申請許可權的系統回撥方法
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
OmgPermission.onRequestPermissionsResult(MainActivity.this, requestCode, permissions, grantResults);
}
}
3、點選按鈕時,使用needPermission()方法動態申請許可權:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
//聯絡人請求碼
private final int REQUEST_CONTACT = 100;
//儲存請求碼
private final int REQUEST_STORAGE = 200;
//相機請求碼
private final int REQUEST_CAMERA = 300;
private Button storageButton;
private Button cameraButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//獲取控制元件
storageButton=(Button) findViewById(R.id.storageButton);
cameraButton=(Button) findViewById(R.id.cameraButton);
//設定監聽
storageButton.setOnClickListener(this);
cameraButton.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
//申請儲存許可權按鈕
case R.id.storageButton:
/**
* 請求許可權
* 如果沒有callback作為引數,就會去呼叫響應的註解方法
*/
OmgPermission.needPermission(MainActivity.this, REQUEST_STORAGE, Permission.STORAGE);
break;
//申請相機許可權按鈕
case R.id.cameraButton:
/**
* 請求許可權
*/
OmgPermission.needPermission(MainActivity.this, REQUEST_CAMERA, Permission.CAMERA,new OmgPermission.PermissionCallback(){
@Override
public void permissionSuccess(int requestCode) {
Toast.makeText(MainActivity.this, "成功授予相機許可權", Toast.LENGTH_SHORT).show();
}
@Override
public void permissionFail(int requestCode) {
Toast.makeText(MainActivity.this, "授予相機許可權失敗", Toast.LENGTH_SHORT).show();
}
});
break;
}
}
}
五、釋出開源庫到 JitPack
JitPack的簡介:
JitPack實際上是一個自定義的Maven倉庫,不過它的流程極度簡化,只需要輸入Github專案地址就可釋出專案,大大方便了像我這種懶得配置環境的人。JitPack允許你把git 託管的專案(支援github和碼雲),輕鬆釋出到 jitpack的 maven 倉庫上,它所有內容都通過內容分發網路(CDN)使用加密 https 連線獲取!
1、在專案的build.gradle
(project級別)檔案
配置裡,新增 maven的地址和github
外掛依賴:
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
//新增maven的github外掛
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0'
}
}
allprojects {
repositories {
google()
jcenter()
//maven地址
maven { url "https://jitpack.io" }
}
}
我這裡用的JitPack版本是2.0,因為的Gradle版本是3.5.2,對應的JitPack是2.0,如果你的Gradle版本比較高或者比較低,需要自己查詢JitPack對應的版本號
2、在 library模組
的 build.gradle
下 apply
外掛和新增 group分組:
apply plugin: 'com.android.library'
//----------------------maven外掛 start---------------------//
//apply maven外掛
apply plugin: 'com.github.dcendents.android-maven'
//定義github分組,這裡的respost改為你github的賬號名
group='com.github.respost'
//----------------------maven外掛 end ---------------------//
android {
}
3、新增好後儲存設定,然後點選提示資訊裡的“Sync Now”進行專案同步。
4、在命令列中輸入 gradlew install
,從而構建你的 library
到你的本地 maven
倉庫
等待 BUILD SUCCESSFUL,
若 BUILD FAIL
,說明構建失敗,這時候你就要按照失敗提示去排錯,排錯完後在執行一遍 gradlew install
命令,直到出現 BUILD SUCCESSFUL
5、上傳專案到github上,如果不會的,可以參考這篇文章:
https://blog.csdn.net/qq15577969/article/details/107607507
不會使用
github管理專案的小白,建議把我部落格裡的Git欄目的文章都學習一遍!
直達地址:
https://blog.csdn.net/qq15577969/category_10239294.html
6、專案上傳到github後,在github專案介面的右側欄點選“
Create a new release” :
7、填寫release的資訊,如下:
8、瀏覽器訪問Jitpack官網 ,在搜尋欄裡輸入你專案的github網址,然後點選“Look up”
9、點選“Get it”進行編譯,圖示變成如下的綠色,表示編譯成功
10、再次點選綠色的“Get it”按鈕,就可以檢視到開源庫的依賴地址了
六、在專案中,使用我們自己的開源庫
關於第三方框架(開源庫)的作用,直接參考這篇文章,https://blog.csdn.net/qq15577969/article/details/109515808
相關文章
- android 6.0許可權機制的簡單封裝(支援批量申請許可權)Android封裝
- Android 採用AOP方式封裝6.0許可權管理Android封裝
- Android6.0------許可權申請管理(單個許可權和多個許可權申請)Android
- 如何用 Vue 實現前端許可權控制(路由許可權 + 檢視許可權 + 請求許可權)Vue前端路由
- Android 6.0 在執行時請求許可權Android
- MySQL許可權管理實戰MySql
- django開發之許可權管理(一)——許可權管理詳解(許可權管理原理以及方案)、不使用許可權框架的原始授權方式詳解Django框架
- android 許可權元件設計Android元件
- Android開發-更”聰明”的申請許可權方式Android
- android動態許可權到自定義許可權框架Android框架
- EDP .Net開發框架--許可權框架
- android強制申請許可權Android
- 封裝、許可權修飾符、封裝的案例封裝
- SpringSecurity許可權管理系統實戰—九、資料許可權的配置SpringGse
- Flutter 網路請求框架封裝Flutter框架封裝
- 基於註解的6.0許可權動態請求框架——JPermission框架
- DRF內建許可權元件之自定義許可權管理類元件
- Django實戰1-許可權管理功能實現-01:搭建開發環境Django開發環境
- Android開發在Activity外申請許可權呼叫相機開啟相簿Android
- Android動態請求許可權的工具類(轉載、非原創)Android
- Hyperf 使用 hyperf-permission 元件實現許可權管理元件
- Android 6.0 執行時許可權管理最佳實踐Android
- 遲來的乾貨 | Kafka許可權管理實戰Kafka
- 在Android上優雅的申請許可權Android
- Android優雅地申請動態許可權Android
- 原生Android之(6.0及以上)許可權申請Android
- Shiro許可權框架框架
- INGECMF 開源專案 內容管理框架 auth許可權管理 招募框架
- Android SELinux許可權AndroidLinux
- Android 通知許可權Android
- 封裝axios請求封裝iOS
- 基於 MVP 的 Android 元件化開發框架實踐MVPAndroid元件化框架
- .NET 平臺 WPF 通用許可權開發框架 (ABP)框架
- SpringSecurity許可權管理系統實戰—四、整合SpringSecurity(上)SpringGse
- SpringSecurity許可權管理系統實戰—五、整合SpringSecurity(下)SpringGse
- SpringSecurity許可權管理系統實戰—六、SpringSecurity整合JWTSpringGseJWT
- android 6.0許可權申請機制(簡單案例)Android
- html5的Notification桌面通知如何請求許可權?HTML