流程回顧:
在上一次https://www.cnblogs.com/webor2006/p/13269742.html對於動態權限的整個執行流程進行了一個分析,接下來則開始擼碼從0開始打造屬於自己的權限申請框架,在正式擼碼之前先來簡單回顧一下整體權限申請的一個流程:
權限檢測流程:
顯示申請權限的流程:
權限申請流程:
編譯時注解處理器:
用通常的方式來申請權限:
這里咱們先不用高大上的框架來申請權限,而是采用最最通用直白的方式,然后再慢慢基於它進行演變,這里以申請sdcard的權限為例,具體代碼就不細說了,基本上都用過:
package com.permissionarchstudy.test; import android.Manifest; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; public class MainActivity extends AppCompatActivity { private static final int RESULT_CODE_PERMISSIONS_WRITE_EXTERNAL_STORAGE = 100; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { //TODO } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, RESULT_CODE_PERMISSIONS_WRITE_EXTERNAL_STORAGE); } } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case RESULT_CODE_PERMISSIONS_WRITE_EXTERNAL_STORAGE: StringBuilder builder = new StringBuilder(); for (int i = 0; i < grantResults.length; i++) { if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { builder.append(permissions[i]); } } if (builder.length() > 0) { new AlertDialog.Builder(this).setTitle("權限授權提示") .setMessage("請授權一下權限,以繼續功能的使用\n\n" + builder.toString()) .setPositiveButton("好的", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }).create().show(); } break; } } }
清單中增加一個sdcard的寫入權限:
接着運行一下:
有木有發現,這種傳統申請權限的代碼寫法還是比較麻煩的,反正如果是讓我來自己申請我是比較畏難的,所以說接下來就得改善這種比較笨重的寫法,要是能讓權限申請成功與失敗能回調到具體的方法中,像這樣:
開始改造:
這里需要用到編譯時注解技術,像之前https://www.cnblogs.com/webor2006/p/10582178.html學習手寫ButterKnife時已經用過了,這里就不過多的來闡述其步驟,直接開干:
定義注解:
這里新建一個java library:
定義注解處理器:
接下來又來新建一個java library,用於進行注解的解析處理,也就是AnnotationProcessor,這塊也已經用了好多次了,套路比較簡單,如下:
然后添加annotation的依賴:
然后注解處理還得依賴於一個輔助庫,這樣對於處理器的注冊就不用咱們手動弄了,如下:
編寫注解生成邏輯:
新建一個處理類:
package com.permissionstudy.libcompiler; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.TypeElement; public class RuntimePermissionAbstractProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; } }
然后將它注冊一下,這里由於用了三方的這個依賴:
所以說此時注冊非常之簡單了,加個注解就完事了,如下:
指定要處理的注解類型:
這里覆寫一個方法:
指定支持JDK的版本:
初始化兩個輔助工具:
收集在我們Activity中所有標上注解的方法:
接下來寫啥呢?這里得從最終應用的角度來思考了,目前咱們的注解其實是應用到我們的方法上的,所以先添加一下依賴:
這里有個小細節需要反問一下,為啥這里主app只依賴libcompiler,依然能使用libannotation的注解呢?api,提到它就秒懂了,傳遞依賴:
好,接下來則來到注解處理器中收集所有標上注解的方法,既然要收集,則肯定得定義一個集合存放嘍,所以定義一個集合:
其中用到個實體,里面內容為空先占個位:
接下來則來開始進行注解的掃描收集:
接下來需要過濾一下方法是否是我們想要的類型,如下:
也有可能此方法是一個私有的或者是抽象未實現的,這種也不滿足要求,所以這里新建一個工具類:
經過過濾之后,此時元素就是一個有效的方法,此時則需要將元素進行收集,既然是要存放到一個HashMap里,那key存啥呢?這里存方法的類名,那如何獲取方法的類名呢?先看一下代碼,這里會涉及到幾種Element的類型:
上面的代碼有點難以理解,這是因為得理解幾種類型的Element,下面先來看一下:
有了key之后,接下來則可以將其緩存到HashMap當中了,如下:
但是很明顯此時MethodInfo中木有存任何信息,所以接下來再來處理一下:
此時則需要在MethodInfo類中定義三個HashMap:
其中貌似方法的入參這塊木有用到:
這里其實可以改造一下咱們的方法,如下:
也就是會將所有已授權或拒絕的權限也返回到我們的方法當中,此時咱們就可以用到這個入參了,如下:
private boolean handleAnnotationInfo(RoundEnvironment roundEnvironment, Class<? extends Annotation> annotaiton) { //根據注解來獲得所有的元素,在這里也就是方法 Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotaiton); for (Element element : elements) { if (!checkMethodValidate(element, annotaiton)) { return false; } ExecutableElement methodElement = (ExecutableElement) element; TypeElement enclosingElement = (TypeElement) methodElement.getEnclosingElement(); String className = enclosingElement.getQualifiedName().toString(); MethodInfo methodInfo = methodMap.get(className); if (methodInfo == null) { methodInfo = new MethodInfo(); methodMap.put(className, methodInfo); } Annotation annotationClass = methodElement.getAnnotation(annotaiton);//獲得注解的類型 String methodName = methodElement.getSimpleName().toString();//獲得方法的名稱 List<? extends VariableElement> parameters = methodElement.getParameters();//獲得方法的入參 if (annotationClass instanceof PermissionGranted) { if (parameters == null || parameters.size() < 1) { String message = "the method %s marked by annotation %s must have an unique parameter [String[] permissions]"; throw new IllegalArgumentException(String.format(message, methodName, annotationClass.getClass().getSimpleName())); } int requestCode = ((PermissionGranted) annotationClass).value(); methodInfo.grantedMethodMap.put(requestCode, methodName); } else if (annotationClass instanceof PermissionDenied) { if (parameters == null || parameters.size() < 1) { String message = "the method %s marked by annotation %s must have an unique parameter [String[] permissions]"; throw new IllegalArgumentException(String.format(message, methodName, annotationClass.getClass().getSimpleName())); } int requestCode = ((PermissionDenied) annotationClass).value(); methodInfo.deniedMethodMap.put(requestCode, methodName); } else if (annotationClass instanceof PermissionRational) { int requestCode = ((PermissionRational) annotationClass).value(); methodInfo.rationalMethodMap.put(requestCode, methodName); } } return true; }
編譯時自動生成文件:
生成目標:
接下來則需要通過注解處理器來生成一個文件,該文件會將授權的結果給回調到咱們相應的方法上來,具體最終生成的樣子長這樣:
看到這個文件名是不是能聯想到ButterKnife,基本思路雷同。
具體實現:
其代碼的生成在之前也學習過,基本上就是用string的拼接來實現,下面開始。
接下來咱們需要獲得要生成的文件名稱,也就是:
怎么獲取呢,如下:
其中獲得類名的工具方法如下:
接下來需要實現一個接口:
這里將這個接口的聲明放到另一個java library當中,然后這里統一可以import這里面的類,如下:
然后app添加它的依賴:
此時咱們就可以import這里面的所有類了,如下:
接下來則可以定義類了:
接下來則需要來生成里面的方法了:
這里則需要根據里面的三個集合來進行方法的生成,所以模板代碼走起:
接下來咱們需要定義一下這個接口了:
其中為啥要定義一個泛型T呢?其實是指Activity,因為最終咱們要回調到Activity中的某個方法上來,所以需要有一個source,其中rational這個應用代碼還木有在Activity中定義,下面定義一下:
此時對於Processor中的這塊需要修改一下:
接下來則來實現具體方法的重載了,也比較簡單,如下:
其它兩個方法也類似,一口氣貼出來了:
另外在RuntimePermissionAbstractProcessor.handleAnnotationInfo()中有處報錯了:
這是因為我們給MethodInfo定義了一個帶兩個參數的構造方法,沒有傳參:
修改一下:
目前先學到這吧,關於剩下的邏輯放下篇了。