EasyPermissions-- Android許可權管理
當前對於大部分手機來說,Android6.0及以上的版本已經是基本配置了,我們在開發的時候,需要對6.0的新特性進行適配,最讓我們熟悉的就是6.0以上許可權的動態申請,今天我們來學習一下許可權動態申請相關的知識。
對於動態許可權的申請,Google為我們提供了EasyPermissions這個框架,該框架可以幫我們更加簡單的完成許可權申請的操作。本篇文章我們來學習如何使用這個框架及該框架是怎麼封裝的。
我們學習一個框架的時候,肯定是想先知道怎麼使用這個框架,所以我們先來看EasyPermissions如何使用!
一、如何使用EasyPermissions?
此處,我們根據上面Google在gitHub中的示例程式碼進行使用的介紹。示例非常簡單,只有兩個類,一個Activity,一個Fragment,顯示瞭如何在這兩種場景下使用EasyPermissions。
首先,在我們的build.gradle檔案中先新增EasyPermissions
implementation 'pub.devrel:easypermissions:1.2.0'
我們先看Activity中的程式碼是如何寫的。
先在Activity的回撥onRequestPermissionsResult(此為我們申請許可權的回撥)方法中,將回調的資料透傳給EasyPermissions,讓EasyPermissions幫我們管理許可權回撥。
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); // EasyPermissions handles the request result. EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); }
我們的準備工作,將許可權回撥給EasyPermissions幫我們管理之後,就可以進行我們的許可權申請了,我們知道,在請求許可權的時候,需要先判斷是否有這個許可權,那麼我們來看該如何用EasyPermissions判斷是否擁有某個許可權。
private boolean hasCameraPermission() {
return EasyPermissions.hasPermissions(this, Manifest.permission.CAMERA);
}
非常簡單,Google給的示例程式碼中,只需要呼叫EasyPermissions的hasPermissions方法就行了這個方法的原始碼介紹是這樣的,其是一個可變長引數,當所有許可權都滿足的時候,返回true,否則返回false。/** * Check if the calling context has a set of permissions. * * @param context the calling context. * @param perms one ore more permissions, such as {@link Manifest.permission#CAMERA}. * @return true if all permissions are already granted, false if at least one permission is not * yet granted. * @see Manifest.permission */ public static boolean hasPermissions(@NonNull Context context,@Size(min = 1) @NonNull String... perms)
當判定有許可權的時候,我們去做該做的事就行了,那沒有許可權怎麼辦呢?
當然是用EasyPermissions請求許可權啦!
public void cameraTask() {
if (hasCameraPermission()) {
// Have permission, do the thing!
Toast.makeText(this, "TODO: Camera things", Toast.LENGTH_LONG).show();
} else {
// Ask for one permission
EasyPermissions.requestPermissions(
this,
getString(R.string.rationale_camera),
RC_CAMERA_PERM,
Manifest.permission.CAMERA);
}
}
請求許可權只需要呼叫EasyPermissions的requestPermissions方法即可,讓我們看一下這個方法的各個引數都是什麼作用,先看一下原始碼中的註釋。
/**
* Request a set of permissions, showing a rationale if the system requests it.
*
* @param host requesting context.
* @param rationale a message explaining why the application needs this set of permissions;
* will be displayed if the user rejects the request the first time.
* @param requestCode request code to track this request, must be < 256.
* @param perms a set of permissions to be requested.
* @see Manifest.permission
*/
public static void requestPermissions(
@NonNull Activity host, @NonNull String rationale,
int requestCode, @Size(min = 1) @NonNull String... perms)
第一個為Activity物件,第二個是一個字串,其展示的場景是 當用戶第一次拒絕這個許可權時,將展示這個資訊(個人手機實際測試,第一次彈出許可權請求,點選拒絕並且不再提醒的時候,將沒有這個資訊展示的場景)。第三個是requestCode,是做校驗用的,需要注意的是,必須小於256。第四個引數就是我們需要請求的許可權,同樣是一個可變長引數。 接下來我們看四個回撥,分別是在我們請求許可權時對應的回撥資訊,EasyPermissions類內部的兩個介面。
/**
* Callback interface to receive the results of {@code EasyPermissions.requestPermissions()}
* calls.
*/
public interface PermissionCallbacks extends ActivityCompat.OnRequestPermissionsResultCallback {
void onPermissionsGranted(int requestCode, @NonNull List<String> perms);
void onPermissionsDenied(int requestCode, @NonNull List<String> perms);
}
/**
* Callback interface to receive button clicked events of the rationale dialog
*/
public interface RationaleCallbacks {
void onRationaleAccepted(int requestCode);
void onRationaleDenied(int requestCode);
}
我們的Activity實現這兩個介面之後,其對應的回撥將傳遞回來。PermissminCallbacks是許可權的回撥,當權限被拒絕的時候,會執行onPermissionsDenied方法,當權限通過的時候會執行onPermissionsGranted方法。而RationaleCallbacks就是資訊提示框對應的回撥,也就是我們上面請求許可權第二個引數展示的那個提示框對應的回撥,當用戶同意的時候,執行onRationaleAccepted方法,取消的時候執行onRationaleDenied方法,也是考慮的很全面了,能滿足我們實際使用的場景需求。上面四個方法的引數均是對應許可權的requestCode和許可權資訊,此處不做過多解釋。 當我們不再提示並拒絕許可權申請的時候,再次使用EasyPermissions的requestPermissions申請許可權,將彈出提示資訊,確認後跳轉到系統對該應用的設定介面,提醒使用者手動開啟許可權。
上面這個介面在示例中是這樣呼叫的:
@Override
public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size());
// (Optional) Check whether the user denied any permissions and checked "NEVER ASK AGAIN."
// This will display a dialog directing them to enable the permission in app settings.
if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
Log.d(TAG, "AppSettingsDialog show" );
new AppSettingsDialog.Builder(this).build().show();
}
}
Google自定義了一個AppSettingsDialog(其實是一個Activity),我們可以通過傳參來修改顯示的樣式,跳轉相關的資訊已經幫我們封裝好了,使用起來很便捷。
另外,在啟動這個的時候,實際的啟動方式是startActivityForResult的,所以我們應該重寫Activity的onActivityResult方法,接收許可權設定之後的結果,示例中的參考程式碼如下:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == AppSettingsDialog.DEFAULT_SETTINGS_REQ_CODE) {
String yes = getString(R.string.yes);
String no = getString(R.string.no);
// Do something after user returned from app settings screen, like showing a Toast.
Toast.makeText(
this,
getString(R.string.returned_from_app_settings_to_activity,
hasCameraPermission() ? yes : no,
hasLocationAndContactsPermissions() ? yes : no,
hasSmsPermission() ? yes : no),
Toast.LENGTH_LONG)
.show();
}
}
判斷一下許可權,然後做我們需要執行的邏輯。當我們的許可權請求被允許之後(去系統設定頁面的不會直接回調到這裡,建議在onActivityResult時處理),需要通過註解接收,Google對其描述是這樣的
- Use of the
AfterPermissionGranted
annotation. This is optional, but provided for convenience. If all of the permissions in a given request are granted, all methods annotated with the proper request code will be executed(be sure to have an unique request code). The annotated method needs to be void and without input parameters (instead, you can use onSaveInstanceState in order to keep the state of your suppressed parameters). This is to simplify the common flow of needing to run the requesting method after all of its permissions have been granted. This can also be achieved by adding logic on theonPermissionsGranted
callback.
@AfterPermissionGranted(RC_CAMERA_PERM)
public void cameraTask() {
if (hasCameraPermission()) {
// Have permission, do the thing!
Toast.makeText(this, "TODO: Camera things", Toast.LENGTH_LONG).show();
} else {
// Ask for one permission
EasyPermissions.requestPermissions(
this,
getString(R.string.rationale_camera),
RC_CAMERA_PERM,
Manifest.permission.CAMERA);
}
}
示例中的這個寫法很合理,請求許可權後繼續執行該方法,進行後續操作,大家可以參考使用。
最後,對於許可權請求的提示框,Google也給我們提供了自定義的介面,
/**
* Request a set of permissions.
*
* @param request the permission request
* @see PermissionRequest
*/
public static void requestPermissions(PermissionRequest request)
請求許可權的時候,直接傳遞一個PermissionRequest物件。 private PermissionRequest(PermissionHelper helper,
String[] perms,
int requestCode,
String rationale,
String positiveButtonText,
String negativeButtonText,
int theme) {
mHelper = helper;
mPerms = perms.clone();
mRequestCode = requestCode;
mRationale = rationale;
mPositiveButtonText = positiveButtonText;
mNegativeButtonText = negativeButtonText;
mTheme = theme;
}
使用PermissionRequest.Builder物件來設定自定義的Request,參考示例如下。
EasyPermissions.requestPermissions(
new PermissionRequest.Builder(this, RC_CAMERA_AND_LOCATION, perms)
.setRationale(R.string.camera_and_location_rationale)
.setPositiveButtonText(R.string.rationale_ask_ok)
.setNegativeButtonText(R.string.rationale_ask_cancel)
.setTheme(R.style.my_fancy_style)
.build());
Builder的構造方法中,第一個為Activity或Fragment物件,第二個是requestCode,就是我們一開始使用的Code,在回撥的時候使用的,第三個是許可權的可變長引數。下面依次為設定提示語,設定按鈕的文字,設定style樣式。
Fragment和Activity的使用基本是一致的,大家可以下載文章開頭的Google示例程式碼,一看便知。
好了,EasyPermissions的使用就到此為止了,上面的示例已經可以滿足我們平時開發對於許可權的需求了,接下來我們分析一下Google對於EastPermissions是怎麼封裝的,與我們平時自己直接使用又有哪些有點和好處。
二、EasyPermissions的原始碼分析
個人水平有限,此處按照我們請求呼叫的順序,跟進原始碼,做簡單的分析,不一定能完全解釋每一行原始碼,有一些內容也可能理解有限,沒有完美的理解,盡力而為!
我們先從請求許可權開始,看一下requestPermissions方法。
public static void requestPermissions(
@NonNull Activity host, @NonNull String rationale,
int requestCode, @Size(min = 1) @NonNull String... perms) {
requestPermissions(
new PermissionRequest.Builder(host, requestCode, perms)
.setRationale(rationale)
.build());
}
此處直接預設構造了一個PermissionRequest物件,然後呼叫過載的requestPermissions方法。
/**
* Request a set of permissions.
*
* @param request the permission request
* @see PermissionRequest
*/
public static void requestPermissions(PermissionRequest request) {
// Check for permissions before dispatching the request
if (hasPermissions(request.getHelper().getContext(), request.getPerms())) {
notifyAlreadyHasPermissions(
request.getHelper().getHost(), request.getRequestCode(), request.getPerms());
return;
}
// Request permissions
request.getHelper().requestPermissions(
request.getRationale(),
request.getPositiveButtonText(),
request.getNegativeButtonText(),
request.getTheme(),
request.getRequestCode(),
request.getPerms());
}
先檢測是否有許可權,檢測方法如下,非常簡單,註釋也很清楚,此處不再解釋。 /**
* Check if the calling context has a set of permissions.
*
* @param context the calling context.
* @param perms one ore more permissions, such as {@link Manifest.permission#CAMERA}.
* @return true if all permissions are already granted, false if at least one permission is not
* yet granted.
* @see Manifest.permission
*/
public static boolean hasPermissions(@NonNull Context context,
@Size(min = 1) @NonNull String... perms) {
// Always return true for SDK < M, let the system deal with the permissions
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
Log.w(TAG, "hasPermissions: API version < M, returning true by default");
// DANGER ZONE!!! Changing this will break the library.
return true;
}
// Null context may be passed if we have detected Low API (less than M) so getting
// to this point with a null context should not be possible.
if (context == null) {
throw new IllegalArgumentException("Can't check permissions for null context");
}
for (String perm : perms) {
if (ContextCompat.checkSelfPermission(context, perm)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
如果有許可權,回撥onRequestPermissionRequest方法,此方法在我們請求許可權成功,Activity的同名方法中,我們也會手動拋給這個方法,所以在後面一起分析,notifyAlreadyHasPermissions方法如下:
/**
* Run permission callbacks on an object that requested permissions but already has them by
* simulating {@link PackageManager#PERMISSION_GRANTED}.
*
* @param object the object requesting permissions.
* @param requestCode the permission request code.
* @param perms a list of permissions requested.
*/
private static void notifyAlreadyHasPermissions(@NonNull Object object,
int requestCode,
@NonNull String[] perms) {
int[] grantResults = new int[perms.length];
for (int i = 0; i < perms.length; i++) {
grantResults[i] = PackageManager.PERMISSION_GRANTED;
}
onRequestPermissionsResult(requestCode, perms, grantResults, object);
}
再回來繼續requestPermissions方法,當沒有許可權時,通過request物件獲取PermissionHelper物件,然後執行requestPermissions方法,將request中的佈局資訊,請求code,許可權內容傳了進去。
requestPermissions方法如下。
public void requestPermissions(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms) {
if (shouldShowRationale(perms)) {
showRequestPermissionRationale(
rationale, positiveButton, negativeButton, theme, requestCode, perms);
} else {
directRequestPermissions(requestCode, perms);
}
}
根據許可權先判斷是否需要顯示提示資訊(其實就是確認是否是第一次請求該許可權),shouldShowRationale方法如下:
private boolean shouldShowRationale(@NonNull String... perms) {
for (String perm : perms) {
if (shouldShowRequestPermissionRationale(perm)) {
return true;
}
}
return false;
}
遍歷這個可變長引數,依次確認是否需要顯示提示資訊。
如果不需要展示提示資訊,就直接執行directRequestPermissions方法,directRequestPermissions和showRequestPermissionRationale均是抽象方法,那麼我們來看PermissionHelper是的具體實現。
檢視PermissionRequest類可以看到,PermissionHelper的初始化是執行PermissionHelper的newInstance方法。
public Builder(@NonNull Activity activity, int requestCode,
@NonNull @Size(min = 1) String... perms) {
mHelper = PermissionHelper.newInstance(activity);
mRequestCode = requestCode;
mPerms = perms;
}
@NonNull
public static PermissionHelper<? extends Activity> newInstance(Activity host) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return new LowApiPermissionsHelper<>(host);
}
if (host instanceof AppCompatActivity)
return new AppCompatActivityPermissionHelper((AppCompatActivity) host);
else {
return new ActivityPermissionHelper(host);
}
}
當小於23的時候,LowApiPermissionsHelper的對應方法都應該是無效的,所以其都丟擲了異常或者不作處理,我們正常使用也不應該會執行到這個類裡面。
@Override
public void directRequestPermissions(int requestCode, @NonNull String... perms) {
throw new IllegalStateException("Should never be requesting permissions on API < 23!");
}
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
return false;
}
@Override
public void showRequestPermissionRationale(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms) {
throw new IllegalStateException("Should never be requesting permissions on API < 23!");
}
ActivityPermissionHelper和AppCompatActivityPermissionHelper重寫的shouldShowRequestPermissionRationale、directRequestPermissions方法是一樣的,均交給了ActivityCompat去處理,和我們自己適配的時候操作一樣。程式碼如下:
@Override
public void directRequestPermissions(int requestCode, @NonNull String... perms) {
ActivityCompat.requestPermissions(getHost(), perms, requestCode);
}
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
return ActivityCompat.shouldShowRequestPermissionRationale(getHost(), perm);
}
他們的不同點在於showRequestPermissionRationale,我們來看showRequestPermissionRationale的實現。
ActivityPermissionHelper的父類BaseFrameworkPermissionsHelper:
@Override
public void showRequestPermissionRationale(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms) {
FragmentManager fm = getFragmentManager();
// Check if fragment is already showing
Fragment fragment = fm.findFragmentByTag(RationaleDialogFragment.TAG);
if (fragment instanceof RationaleDialogFragment) {
Log.d(TAG, "Found existing fragment, not showing rationale.");
return;
}
RationaleDialogFragment
.newInstance(positiveButton, negativeButton, rationale, theme, requestCode, perms)
.showAllowingStateLoss(fm, RationaleDialogFragment.TAG);
}
AppCompatActivityPermissionHelper的父類BaseSupportPermissionsHelper
@Override
public void showRequestPermissionRationale(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms) {
FragmentManager fm = getSupportFragmentManager();
// Check if fragment is already showing
Fragment fragment = fm.findFragmentByTag(RationaleDialogFragmentCompat.TAG);
if (fragment instanceof RationaleDialogFragmentCompat) {
Log.d(TAG, "Found existing fragment, not showing rationale.");
return;
}
RationaleDialogFragmentCompat
.newInstance(rationale, positiveButton, negativeButton, theme, requestCode, perms)
.showAllowingStateLoss(fm, RationaleDialogFragmentCompat.TAG);
}
區別是對Fragment操作的不同。接下來是Dialog的展示環節,我們稍後再看,先回到,許可權請求的主要流程上面。
在前面我們看到了onRequestPermissionsResult的呼叫,Activity的同名方法中供我們也直接透傳給了EasyPermissions的onRequestPermissionsResult,現在我們來看onRequestPermissionsResult方法:
/**
* Handle the result of a permission request, should be called from the calling {@link
* Activity}'s {@link ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(int,
* String[], int[])} method.
* <p>
* If any permissions were granted or denied, the {@code object} will receive the appropriate
* callbacks through {@link PermissionCallbacks} and methods annotated with {@link
* AfterPermissionGranted} will be run if appropriate.
*
* @param requestCode requestCode argument to permission result callback.
* @param permissions permissions argument to permission result callback.
* @param grantResults grantResults argument to permission result callback.
* @param receivers an array of objects that have a method annotated with {@link
* AfterPermissionGranted} or implement {@link PermissionCallbacks}.
*/
public static void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults,
@NonNull Object... receivers) {
// Make a collection of granted and denied permissions from the request.
List<String> granted = new ArrayList<>();
List<String> denied = new ArrayList<>();
for (int i = 0; i < permissions.length; i++) {
String perm = permissions[i];
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
granted.add(perm);
} else {
denied.add(perm);
}
}
// iterate through all receivers
for (Object object : receivers) {
// Report granted permissions, if any.
if (!granted.isEmpty()) {
if (object instanceof PermissionCallbacks) {
((PermissionCallbacks) object).onPermissionsGranted(requestCode, granted);
}
}
// Report denied permissions, if any.
if (!denied.isEmpty()) {
if (object instanceof PermissionCallbacks) {
((PermissionCallbacks) object).onPermissionsDenied(requestCode, denied);
}
}
// If 100% successful, call annotated methods
if (!granted.isEmpty() && denied.isEmpty()) {
runAnnotatedMethods(object, requestCode);
}
}
}
引數不做過多解釋,可以看到,將回調的許可權分類,先判斷是否通過了, 然後呼叫接收者對應的許可權回撥,也是我們文章第一部分提到的PermissionCallbacks介面。如果全部成功,執行runAnnotatedMethods方法,看名字就是將執行我們註解所標註的方法,進行後續事件的執行。 /**
* Find all methods annotated with {@link AfterPermissionGranted} on a given object with the
* correct requestCode argument.
*
* @param object the object with annotated methods.
* @param requestCode the requestCode passed to the annotation.
*/
private static void runAnnotatedMethods(@NonNull Object object, int requestCode) {
Class clazz = object.getClass();
if (isUsingAndroidAnnotations(object)) {
clazz = clazz.getSuperclass();
}
while (clazz != null) {
for (Method method : clazz.getDeclaredMethods()) {
AfterPermissionGranted ann = method.getAnnotation(AfterPermissionGranted.class);
if (ann != null) {
// Check for annotated methods with matching request code.
if (ann.value() == requestCode) {
// Method must be void so that we can invoke it
if (method.getParameterTypes().length > 0) {
throw new RuntimeException(
"Cannot execute method " + method.getName() + " because it is non-void method and/or has input parameters.");
}
try {
// Make method accessible if private
if (!method.isAccessible()) {
method.setAccessible(true);
}
method.invoke(object);
} catch (IllegalAccessException e) {
Log.e(TAG, "runDefaultMethod:IllegalAccessException", e);
} catch (InvocationTargetException e) {
Log.e(TAG, "runDefaultMethod:InvocationTargetException", e);
}
}
}
}
clazz = clazz.getSuperclass();
}
}
開始的判斷,如果使用的AndroidAnnotations,就給clazz重新賦值(暫時還未了解該框架,待以後補充原因),然後遍歷該物件的方法,查詢AfterPermissionGranted註釋,當code匹配,並且該方法沒有引數的時候,使用invoke執行該方法。 整體來說這個流程還時沒有特別晦澀難懂的,Google用很簡潔的語言和邏輯為我們封裝了這樣一個請求框架,對於我們自己封裝的許可權請求來說,完全可以參考這個模式來嘗試一下。
接下來我們繼續看UI部分的程式碼是如何編寫的。
以AppCompatActivityPermissionHelper的父類BaseSupportPermissionsHelper作為分析,其呼叫的程式碼是這樣的
RationaleDialogFragmentCompat
.newInstance(rationale, positiveButton, negativeButton, theme, requestCode, perms)
.showAllowingStateLoss(fm, RationaleDialogFragmentCompat.TAG);
RationaleDialogFragmentCompat其實就是一個自定義的Dialog,並不是很複雜,原始碼如下:
/**
* {@link AppCompatDialogFragment} to display rationale for permission requests when the request
* comes from a Fragment or Activity that can host a Fragment.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class RationaleDialogFragmentCompat extends AppCompatDialogFragment {
public static final String TAG = "RationaleDialogFragmentCompat";
private EasyPermissions.PermissionCallbacks mPermissionCallbacks;
private EasyPermissions.RationaleCallbacks mRationaleCallbacks;
public static RationaleDialogFragmentCompat newInstance(
@NonNull String rationaleMsg,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String[] permissions) {
// Create new Fragment
RationaleDialogFragmentCompat dialogFragment = new RationaleDialogFragmentCompat();
// Initialize configuration as arguments
RationaleDialogConfig config = new RationaleDialogConfig(
positiveButton, negativeButton, rationaleMsg, theme, requestCode, permissions);
dialogFragment.setArguments(config.toBundle());
return dialogFragment;
}
/**
* Version of {@link #show(FragmentManager, String)} that no-ops when an IllegalStateException
* would otherwise occur.
*/
public void showAllowingStateLoss(FragmentManager manager, String tag) {
if (manager.isStateSaved()) {
return;
}
show(manager, tag);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (getParentFragment() != null) {
if (getParentFragment() instanceof EasyPermissions.PermissionCallbacks) {
mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) getParentFragment();
}
if (getParentFragment() instanceof EasyPermissions.RationaleCallbacks){
mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) getParentFragment();
}
}
if (context instanceof EasyPermissions.PermissionCallbacks) {
mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) context;
}
if (context instanceof EasyPermissions.RationaleCallbacks) {
mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) context;
}
}
@Override
public void onDetach() {
super.onDetach();
mPermissionCallbacks = null;
mRationaleCallbacks = null;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Rationale dialog should not be cancelable
setCancelable(false);
// Get config from arguments, create click listener
RationaleDialogConfig config = new RationaleDialogConfig(getArguments());
RationaleDialogClickListener clickListener =
new RationaleDialogClickListener(this, config, mPermissionCallbacks, mRationaleCallbacks);
// Create an AlertDialog
return config.createSupportDialog(getContext(), clickListener);
}
}
Dialog在建立時,onCreateDialog方法呼叫了RationaleDialogConfig的createSupportDialog方法,config物件中既有我們傳入的資料,又有新建立的ClickListener物件,看一下這個方法。
AlertDialog createSupportDialog(Context context, Dialog.OnClickListener listener) {
AlertDialog.Builder builder;
if (theme > 0) {
builder = new AlertDialog.Builder(context, theme);
} else {
builder = new AlertDialog.Builder(context);
}
return builder
.setCancelable(false)
.setPositiveButton(positiveButton, listener)
.setNegativeButton(negativeButton, listener)
.setMessage(rationaleMsg)
.create();
}
AlertDialog的建立,並不是很複雜的東西,將我們開始傳入的資料試用一下就可以了。 最後看一下這個ClickListener的onClick方法,當我們點選了之後會怎麼做?
@Override
public void onClick(DialogInterface dialog, int which) {
int requestCode = mConfig.requestCode;
if (which == Dialog.BUTTON_POSITIVE) {
String[] permissions = mConfig.permissions;
if (mRationaleCallbacks != null) {
mRationaleCallbacks.onRationaleAccepted(requestCode);
}
if (mHost instanceof Fragment) {
PermissionHelper.newInstance((Fragment) mHost).directRequestPermissions(requestCode, permissions);
} else if (mHost instanceof android.app.Fragment) {
PermissionHelper.newInstance((android.app.Fragment) mHost).directRequestPermissions(requestCode, permissions);
} else if (mHost instanceof Activity) {
PermissionHelper.newInstance((Activity) mHost).directRequestPermissions(requestCode, permissions);
} else {
throw new RuntimeException("Host must be an Activity or Fragment!");
}
} else {
if (mRationaleCallbacks != null) {
mRationaleCallbacks.onRationaleDenied(requestCode);
}
notifyPermissionDenied();
}
}
private void notifyPermissionDenied() {
if (mCallbacks != null) {
mCallbacks.onPermissionsDenied(mConfig.requestCode, Arrays.asList(mConfig.permissions));
}
}
如果點選確認,先回調對應的方法,然後建立對應的PermissionHelper物件,執行後續方法。如果取消,回撥對應的方法。最後,我們看一下跳轉到系統頁面的那一部分,就是在選擇不再提示後,彈出的AppSettingsDialog如何實現的。
public class AppSettingsDialog implements Parcelable
其實這個Dialog是一個數據類,將我們要顯示和傳遞的資訊儲存下來。 @NonNull
public AppSettingsDialog build() {
mRationale = TextUtils.isEmpty(mRationale) ?
mContext.getString(R.string.rationale_ask_again) : mRationale;
mTitle = TextUtils.isEmpty(mTitle) ?
mContext.getString(R.string.title_settings_dialog) : mTitle;
mPositiveButtonText = TextUtils.isEmpty(mPositiveButtonText) ?
mContext.getString(android.R.string.ok) : mPositiveButtonText;
mNegativeButtonText = TextUtils.isEmpty(mNegativeButtonText) ?
mContext.getString(android.R.string.cancel) : mNegativeButtonText;
mRequestCode = mRequestCode > 0 ? mRequestCode : DEFAULT_SETTINGS_REQ_CODE;
return new AppSettingsDialog(
mActivityOrFragment,
mThemeResId,
mRationale,
mTitle,
mPositiveButtonText,
mNegativeButtonText,
mRequestCode);
}
Builder的build方法建立了這個物件,show的時候將這些資訊放入Intent傳進去,啟動了一個新的Activity。
/**
* Display the built dialog.
*/
public void show() {
startForResult(AppSettingsDialogHolderActivity.createShowDialogIntent(mContext, this));
}
AppSettingsDialogHolderActivity並不複雜,裡面內建了一個真正的Dialog,當點選的時候也幫我們配置好了對應的Intent,Activity接收到系統設定介面資訊之後,結束關閉這個Activity並且將資料傳遞回去,public class AppSettingsDialogHolderActivity extends AppCompatActivity implements DialogInterface.OnClickListener {
private static final int APP_SETTINGS_RC = 7534;
private AlertDialog mDialog;
public static Intent createShowDialogIntent(Context context, AppSettingsDialog dialog) {
return new Intent(context, AppSettingsDialogHolderActivity.class)
.putExtra(AppSettingsDialog.EXTRA_APP_SETTINGS, dialog);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDialog = AppSettingsDialog.fromIntent(getIntent(), this).showDialog(this, this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss();
}
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == Dialog.BUTTON_POSITIVE) {
startActivityForResult(
new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", getPackageName(), null)),
APP_SETTINGS_RC);
} else if (which == Dialog.BUTTON_NEGATIVE) {
setResult(Activity.RESULT_CANCELED);
finish();
} else {
throw new IllegalStateException("Unknown button type: " + which);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
setResult(resultCode, data);
finish();
}
}
好了,這次的分析就到此為止了,從每一個模組看來,其實都是我們平時用到過的東西,並不是很複雜,重要的是將這些東西結合起來,做出這麼一個簡潔方便的框架。