Android6.0權(quán)限申請(qǐng), 應(yīng)該說(shuō)是挺墨跡的一個(gè)事, 現(xiàn)在網(wǎng)上有一些權(quán)限申請(qǐng)的第三方, 用起來(lái)也很方便, 但是有一點(diǎn), 這些第三方集成進(jìn)你的項(xiàng)目中無(wú)疑會(huì)造成apk的體積增大, 這就好比大名鼎鼎的ButterKnife插件, 用起來(lái)很爽, 但是有人可能覺(jué)得ButterKnife會(huì)或多或少的影響性能, 對(duì)于這個(gè)問(wèn)題我覺(jué)得是見(jiàn)仁見(jiàn)智的問(wèn)題. 如果怕影響性能, 大可以用一些其他的插件, 比如用FindViewByMe插件,這個(gè)插件只是自動(dòng)生成findviewbyid的相關(guān)代碼來(lái)代替手寫(xiě)而已, 所以, 我們也可以自己寫(xiě)一個(gè)自動(dòng)生成權(quán)限申請(qǐng)的代碼的插件, 這樣即不需要引入第三方,又不需要每次都手寫(xiě), 可謂一勞永逸.下面就一起來(lái)開(kāi)發(fā)一個(gè)簡(jiǎn)單版的權(quán)限申請(qǐng)代碼自動(dòng)生成插件
注: 因?yàn)槠? 本文只介紹插件開(kāi)發(fā)的思路和過(guò)程, 如果有想要源碼和jar包的請(qǐng)關(guān)注文章末尾公眾號(hào), 發(fā)送"permission_plugin源碼"和"permission_plugin插件包"即可獲得下載鏈接
(一)設(shè)計(jì)
嚴(yán)格來(lái)說(shuō), 動(dòng)態(tài)權(quán)限申請(qǐng), 應(yīng)該是在應(yīng)用要獲取某個(gè)權(quán)限的時(shí)候再申請(qǐng), 但是很多市面上面的app, 包括一些比較知名的app都是在首頁(yè)的時(shí)候就會(huì)彈出權(quán)限申請(qǐng), 因此, 本插件也設(shè)計(jì)成在某一個(gè)界面申請(qǐng)動(dòng)態(tài)權(quán)限.
(二) 問(wèn)題
整個(gè)插件開(kāi)發(fā)過(guò)程其實(shí)就是解決這幾個(gè)問(wèn)題:
1 插件怎么知道開(kāi)發(fā)者要在哪個(gè)界面(Activity)進(jìn)行權(quán)限申請(qǐng)?
2 插件怎么知道屬于動(dòng)態(tài)申請(qǐng)的權(quán)限有哪些?
3 插件怎么知道在哪個(gè)位置生成什么代碼?
(三)具體實(shí)現(xiàn)
下面我們就從新建項(xiàng)目開(kāi)始一步步解決這幾個(gè)問(wèn)題, 進(jìn)而實(shí)現(xiàn)整個(gè)插件.
1 新建項(xiàng)目
AndroidStudio基于IntelliJ平臺(tái),因此,開(kāi)發(fā)AndroidStudio插件其本質(zhì)只是開(kāi)發(fā)IntelliJ平臺(tái)的插件, 因此, 我們要在IDEA中開(kāi)發(fā)插件, 新建項(xiàng)目見(jiàn)下圖:

將Project選擇為IntelliJ Platform Plugin, 然后next, 見(jiàn)下圖

書(shū)寫(xiě)項(xiàng)目名稱(chēng)和選擇項(xiàng)目本地保存路徑, 見(jiàn)下圖

點(diǎn)擊Finish后, 打開(kāi)項(xiàng)目, 見(jiàn)下圖:

2 開(kāi)發(fā)代碼
(1)
在src中新建一個(gè)包, 這里叫com.android_M.permission.plugin, 再在包中新建一個(gè)類(lèi), 這里叫PermissionPlugin, 再讓PermissionPlugin這個(gè)類(lèi)繼承AnAction, 重寫(xiě)actionPerformed(AnActionEvent anActionEvent)方法
public class PermissionPlugin extends AnAction {
@Override
public void actionPerformed(AnActionEvent anActionEvent) {
}
}
當(dāng)開(kāi)發(fā)者在studio中想一鍵生成代碼的時(shí)候, 會(huì)點(diǎn)擊插件觸發(fā)一個(gè)動(dòng)作事件,IntelliJ會(huì)回調(diào)AnAction類(lèi)的actionPerformed函數(shù)。因此我們只需重寫(xiě)actionPerformed函數(shù)即可。
(2)
首先,獲取project, 再獲取editor, 獲取當(dāng)前的java文件,當(dāng)前類(lèi)名,判斷是否是activity子類(lèi)
Project project = anActionEvent.getData(PlatformDataKeys.PROJECT);
Editor editor = anActionEvent.getData(PlatformDataKeys.EDITOR);
PsiFile currentEditorFile = PsiUtilBase.getPsiFileInEditor(editor, project);
String currentEditorFileName = currentEditorFile.getName();
studio中如果打開(kāi)的是MainActivity, 并且鼠標(biāo)光標(biāo)在類(lèi)的范圍里面,那么此時(shí)currentEditorFile就是MainActivity.java文件,currentEditorFileName就是:"MainActivity.java"這個(gè)字符串,這里用來(lái)判斷當(dāng)前打開(kāi)的類(lèi)是不是activity的子類(lèi)。(其實(shí)這么判斷很不嚴(yán)謹(jǐn),這里是為了方便),到這里,就解決了第一個(gè)問(wèn)題,即用戶(hù)打開(kāi)了哪個(gè)activity,并且鼠標(biāo)光圈在類(lèi)范圍內(nèi),那么就在這個(gè)類(lèi)里面生成代碼
(3)
獲取AndroidManifest.xml文件,找到所有注冊(cè)的權(quán)限,過(guò)濾掉普通權(quán)限,把剩下的危險(xiǎn)權(quán)限添加進(jìn)集合中。
// 獲取AndroidManifest.xml文件,注意,獲取的結(jié)果是數(shù)組,是因?yàn)楫?dāng)有多個(gè)module時(shí)是有多個(gè)AndroidManifest.xml的
PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, "AndroidManifest.xml", GlobalSearchScope.allScope(project));
for(int i = 0; i < psiFiles.length; i++) {
// 此時(shí)的xmlFile就是每一個(gè)AndroidManifest.xml文件
XmlFile xmlFile = (XmlFile) psiFiles[i];
}
然后開(kāi)始根據(jù)標(biāo)簽tag解析XmlFile文件,獲取到以及注冊(cè)的所有權(quán)限
xmlFile.accept(new XmlRecursiveElementVisitor() {
@Override
public void visitElement(PsiElement element) {
super.visitElement(element);
// 解析Xml標(biāo)簽
if (element instanceof XmlTag) {
XmlTag tag = (XmlTag) element;
// 獲取Tag的名字(TextView)或者自定義
String tagName = tag.getName();
if(tagName != null && !tagName.equals("") && tagName.equals("uses-permission")) {
// 獲取permission name屬性
XmlAttribute permission_name = tag.getAttribute("android:name", null);
if(permission_name == null) {
return;
}
// 獲取具體的權(quán)限值
String permissionValue = permission_name.getValue();
// 把獲取到的所有權(quán)限添加進(jìn)集合
permissionList.add(permissionValue);
}
}
}
});
到這里,獲取到了所有的權(quán)限,經(jīng)過(guò)過(guò)濾篩選后(所有的危險(xiǎn)權(quán)限都是固定的,過(guò)濾篩選部分代碼略過(guò))至此,解決了第二給問(wèn)題,即都需要申請(qǐng)哪些動(dòng)態(tài)權(quán)限
(4)
自動(dòng)生成代碼的部分必須寫(xiě)在線程中。首先判斷這個(gè)類(lèi)中是否有onCreate方法,如果沒(méi)有,就自動(dòng)生成,如果有,就在里面添加一個(gè)調(diào)用方法checkPermissions(listPermission)
WriteCommandAction.runWriteCommandAction(anActionEvent.getProject(), new Runnable() {
@Override
public void run() {
// 獲取當(dāng)前類(lèi),有幾個(gè)onCreate方法,如果沒(méi)有, 就進(jìn)行自動(dòng)生成onCreate方法
if(psiClass.findMethodsByName("onCreate", false).length == 0) {
// 想要自動(dòng)生成的代碼,是以字符串的形式作為參數(shù),創(chuàng)建PsiMethod對(duì)象,再由psiClass對(duì)象add即可(詳見(jiàn)下面貼的buildMethodOnCreate()方法實(shí)現(xiàn))
String methodOnCreate = util.buildMethodOnCreate();
PsiMethod psiMethodOnCreate = elementFactory.createMethodFromText(methodOnCreate, psiClass);
psiClass.add(psiMethodOnCreate);
}
// 如果有onCreate方法,就自動(dòng)生成checkPermissions方法,并在onCreate方法里面調(diào)用
if(psiClass.findMethodsByName("onCreate", false).length = 1) {
// 獲取onCreate方法,在里面調(diào)用checkPermissions方法
PsiMethod onCreateMethod = psiClass.findMethodsByName("onCreate", false)[0];
onCreateMethod.getBody().add(elementFactory.createStatementFromText("checkPermissions(listPermission);", psiClass));
}
// 寫(xiě)變量,(用來(lái)存放所有需要申請(qǐng)的危險(xiǎn)權(quán)限)
String variable = "private ArrayList<String> requestPermissionList = new ArrayList<String>();";
PsiField fieldFromText = elementFactory.createFieldFromText(variable, psiClass);
psiClass.add(fieldFromText);
// 寫(xiě)變量,申請(qǐng)權(quán)限后回調(diào)的requestCode
String requestCode = "private final int REQUEST_PERMISSION_CODE = 0;";
PsiField fieldFromCode = elementFactory.createFieldFromText(requestCode, psiClass);
psiClass.add(fieldFromCode);
// 寫(xiě)變量,(從AndroidManifest.xml文件讀取來(lái)的所有的危險(xiǎn)權(quán)限)
String variableArr = "private String[] listPermission = new String[]{" + sb.toString() + "};";
PsiField fieldFromArr = elementFactory.createFieldFromText(variableArr, psiClass);
psiClass.add(fieldFromArr);
// 寫(xiě)方法,(檢查權(quán)限)
String methodText = util.buildMethodText(className);
PsiMethod psiMethod = elementFactory.createMethodFromText(methodText, psiClass);
psiClass.add(psiMethod);
// 寫(xiě)申請(qǐng)權(quán)限的requestPermission方法
PsiMethod psiMethodRequestPermission = elementFactory.createMethodFromText(util.buildMethodRequestPermission(), psiClass);
psiClass.add(psiMethodRequestPermission);
// 寫(xiě)申請(qǐng)權(quán)限后的回調(diào)方法
PsiMethod psiMethodRequestPermissionResult = elementFactory.createMethodFromText(util.buildMethodRequestPermissionResult(className), psiClass);
psiClass.add(psiMethodRequestPermissionResult);
}
});
具體生成代碼的部分(只以自動(dòng)生成onCreate方法為例):
public static String buildMethodOnCreate() {
StringBuilder method = new StringBuilder();
method.append("@Override protected void onCreate(android.os.Bundle savedInstanceState) {\n");
method.append("super.onCreate(savedInstanceState);\n");
method.append("}");
return method.toString();
}
(5)
配置插件文件, 我們需要通過(guò)配置文件, 告訴用戶(hù), 插件的名稱(chēng), 功能, 版本, 以及怎么觸發(fā)等信息
打開(kāi)項(xiàng)目中resources下面的plugin.xml文件, 見(jiàn)下圖:

找到<id>標(biāo)簽, 填寫(xiě)上插件id
找到<name>標(biāo)簽,填寫(xiě)上插件名稱(chēng)
找到<version>標(biāo)簽, 填寫(xiě)上版本號(hào)
找到<description>標(biāo)簽, 填寫(xiě)上插件的簡(jiǎn)介
找到<change-notes>標(biāo)簽, 填寫(xiě)上版本變更
找到<actions>標(biāo)簽, 再新建一個(gè)<action>標(biāo)簽, 分別填寫(xiě)class, id, text, description, 重點(diǎn)是<add-to-group>標(biāo)簽, 見(jiàn)下圖

<add-to-group>標(biāo)簽表示在studio中, 點(diǎn)擊code這個(gè)菜單后, 在第一個(gè)位置顯示插件, 點(diǎn)擊就可觸發(fā)了
(6)
至此, 插件開(kāi)發(fā)完畢,接下來(lái)是打成jar包
點(diǎn)擊IDEA的build菜單, 選中Prepare Plugin Module...選項(xiàng)進(jìn)行打包, 完成后, 會(huì)在項(xiàng)目的.iml文件下方生成一個(gè).jar文件, 這個(gè)就是插件包了

(7)
應(yīng)用
打開(kāi)android studio,點(diǎn)擊file --> settings --> 左側(cè)點(diǎn)擊plugin, 選擇下面的install plugin from disk, 選擇要應(yīng)用的插件包, 然后restart即可.

重啟studio后在AndroidManifest.xml中添加幾個(gè)普通權(quán)限和幾個(gè)普通危險(xiǎn)權(quán)限, 隨便打開(kāi)一個(gè)后綴是Activity的類(lèi), 鼠標(biāo)放在類(lèi)的內(nèi)部, 點(diǎn)擊studio中的Code菜單, 點(diǎn)擊插件名稱(chēng)就自動(dòng)生成了權(quán)限相關(guān)代碼. 效果見(jiàn)下面動(dòng)圖. 至此, 全部完成
