前言
感觉已经很久没有写博客了,5月份之后一直在学习kotlin,边学边用,算是入门了吧;然后又突然对热更新技术很有兴趣,又去学习了一段时间,无奈毕竟我是凡人一个,只能膜拜那些大神啊;最近又在随大流,开始好奇AI领域,国内资料太少,很多还收费,好不容易找到个国外免费的,可惜这英文能力太弱,实在是累极啊。
就当我正在浑浑噩噩之时,突和朋友讨论起的Android权限申请的问题,最后我们得出这样一个结论:目前各个比较流行的权限管理开源库都有一个比较麻烦的使用步骤。
虽说有些开源库依靠注解和APT技术来简化了权限申请的流程,但我感觉还是不够简单,比如github上star比较多的一个开源库
star足有8000往上之多,可见Android权限申请的重要性与使用的频繁性。可就这么个要频繁使用的东西,很多Android程序员都被其复杂的流程所困扰,为了申请到权限,不得不在自己的代码里加上一些看似毫无关联的代码逻辑,比如,Activity或Fragment必需重写的一个方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
即使你使用了一些开源库,比如PermissionsDispatcher,你也要重载这个方法,并且在里面加上类似xxx.onRequestPermissionsResult这样的代码,告诉你引用的开源库,某些权限回调了,你引用的开源库才能继续执行回调。
那么,就没有什么办法可以让我们只是用注解,无需插入其他逻辑代码即可实现权限申请了吗?当然有,答案是肯定的,不然我今天也不会来写这篇文章了(机智如我)?。并且我可以负责任的告诉大家,方法肯定不止我这一种,大家有兴趣的话,可以顺着我今天这篇文章的思路,去尝试一下其他方式,说不定结果会出乎你的意料哦。
先给出项目的地址
一、原理简介
1.1 技术扫盲
今天给大家带来的这个开源库呢,也算是我最近研究热更新技术得到启发而实现的,原理和技术其实都非常简单,只涉及到两个方面:
- gradle transform api
- javassist
使用这两个方面的技术说到底,就是为了获取到一些指定的class文件,并且在class文件被打包成dex文件之前,往class文件里插入代码。
关于javassist和gradle transform api的技术主要参考资料如下:
尤其是第四个文章链接末尾有其demo的github地址,很多javassist的用法都可以在demo里找到,大家需要慢慢研究。比如,我最开始想实现这个开源库的时候,就遇到了无法插入涉及android api的代码,当时感觉已经没有希望了,隔了一两天,我还是不想放弃,再次一阵google之后,总算找到了解决办法,所以真的非常感谢这些大神们啊。
上面的参考不是必须的,大家可以先开始尝试着跟着第二个链接里内容去写一个自己的gradle plugin插件,在实现的过程中遇到了什么问题,在回来参考其他链接里的知识,循序渐进,肯定能有所增益的。
前面说了,目前大多数权限库都有一个不太顺手的地方(不是缺点哈),就是要程序员手动添加一些代码,感觉用起来不是那么方便,那么,如果有什么办法可以自动往我们需要权限的类或方法里加上申请权限的代码,岂不美哉?刚才已经说过了javassist和gradle transform api技术的作用,那么我们就可以利用他们来完成往类或方法里插入权限申请的代码啦。
1.2问题&解决办法
现在我们仅仅是知道了可以在编译时修改class文件,往某些特定的class文件里插入代码,那么接下来我们就至少要考虑以下几个问题:
- 如何找出哪些class文件需要插入代码?
- 如何找出哪些方法需要申请权限?需要申请哪些权限?
- 需要申请权限的方法申请权限成功后需要如何处理?
- permission权限申请库采用的是回调的形式来通知权限申请结果,我们可否在该class文件任何地方插入权限申请的代码?
对于第1个和第2个问题,我们可以用注解很好的去解决,编译时注解即可,大家不要在意我的simplepermission_ano里的注解是运行时的,习惯了,没改过来?。
至于第3个问题,这个是需要琢磨一下的,因为大部分情况下,我们的某个方法要申请权限总是意味着我们的方法里面的一些操作需要依赖于某些权限,并且有些是必须要先得到权限后才可以执行的操作,那么我们就不能单纯的在这方法里插入申请权限的代码,还要干预这个方法的逻辑,但是我们的权限申请流程并不是阻塞式的,而是异步回调式的,这样我们就没办法在这个方法内部去干预其逻辑了。那么是否就无计可施了呢?其实不然,我们可以把权限申请的代码插入到该方法的最前部分,根据方法上的注解里的一个值来判断该方法是否需要等到权限回调成功了在执行后续逻辑,这样我们就可以在权限申请回调成功的地方插入调用刚才申请权限的那个方法的代码,重新执行其内部逻辑(因为我们重新调用了那个方法)不就可以了吗?不过这又牵涉到另一个问题,如果该方法是一个无参无返回值的方法,那就非常简单啦,但是如果该方法有参数该怎么办?我们在权限申请回调成功的地方再次调用该方法就无法继续写啦,因为我们没有参数啊!于是我又想到了一个办法,在申请权限的类里加入一个全局变量(不带泛型的Map),当某个申请权限的方法被调用时,这个方法其实已经被我们插入了判断应用程序是否拥有其注解里包含的权限的代码了,如果应用程序已经拥有了那些权限,那我们便不再干预其后续逻辑;如果应用程序还未拥有那些权限,则用一个不带泛型的List把该方法的参数挨个存储起来,然后把这个List放到全局的那个不带泛型的Map里,当然,放到map里时的那个key,就是方法的注解里的一个唯一值。然后,和刚才的逻辑一样,在权限申请回调成功的地方,根据回调里的那个唯一值作为key,从map里取出次方法的参数,插入再次调用此方法的代码,问题不就解决了吗?。不过,对于带有返回值的方法,目前我也没有想到有什么好的方法去解决,但是我相信,一定是可以解决的,我会好好琢磨琢磨的。
金无赤足,人无完人,对于最后一个问题,由于本人能力有限和javassist的一些限制,目前本库无法在内部类里使用,这是一个非常大的遗憾,希望随着时间的推移,javassist能够支持插入内部类吧。
二、没有代码的代码实现
其实代码实现部分真没什么好说的,项目我都放到了github,大家可以先直接去看本开源库的README,先了解permission权限申请库的基本用法,在去看simplepermissionplugin的源码,就会很快明白是怎么实现的啦。至于simplepermission_ano库,你可以直接忽略,里面就两个注解而已。simplepermissionplugin源码我注释的非常清楚,里面所有涉及到的技术,前面给的那几篇参考文章里都只会多不会少。
在这里呢,我也不会贴很多代码啦,我只是想通过一点代码对比,让大家看看,正常使用permission权限库和用gradle插件&注解方式使用permission权限库的区别。
正常情况下使用permission权限库的代码如下
public class MainActivity extends AppCompatActivity implements View.OnClickListener
private TextView mTextMessage;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextMessage = (TextView) findViewById(R.id.message);
mTextMessage.setOnClickListener(this);
}
/**
* 此方法需要一个 Manifest.permission.READ_CONTACTS权限
*
* @param text
*/
private void setText(final String text) {
PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(10010, this,
new String[]{Manifest.permission.READ_CONTACTS}, new PermissionsRequestCallback() {
@Override
public void onGranted(int requestCode, String permission) {
}
@Override
public void onDenied(int requestCode, String permission) {
}
@Override
public void onDeniedForever(int requestCode, String permission) {
}
@Override
public void onFailure(int requestCode, String[] deniedPermissions) {
}
@Override
public void onSuccess(int requestCode) {
mTextMessage.setText(text);
}
});
}
@Override
public void onClick(View v) {
setText("哈哈哈哈哈哈");
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
PermissionsManager.getInstance().notifyPermissionsChange(permissions,grantResults);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
用gradle插件&注解方式使用permission权限库的代码如下
@PermissionNotify
public class MainActivity extends AppCompatActivity implements View.OnClickListener
private TextView mTextMessage;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextMessage = (TextView) findViewById(R.id.message);
mTextMessage.setOnClickListener(this);
}
/**
* 此方法需要一个 Manifest.permission.READ_CONTACTS权限
*
* @param text
*/
@PermissionRequest(
requestCode = 10010,
requestPermissions =
{Manifest.permission.READ_CONTACTS}
, needReCall = true
)
private void setText(final String text) {
mTextMessage.setText(text);
}
@Override
public void onClick(View v) {
setText("哈哈哈哈哈哈");
}
}
最后在app module的build/intermediates/transforms/SimplePermissionTransform/debug(或release)/24(可能是其他数字)/包名 文件夹下可以看到加上了注解的类的实际编译结果,类似如下
@PermissionNotify
public class MainActivity extends AppCompatActivity implements OnClickListener, PermissionsRequestCallback {
private TextView mTextMessage;
private final Map requestPermissionMethodParams = new HashMap();
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131361818);
this.mTextMessage.setOnClickListener(this);
}
@PermissionRequest(
requestCode = 10010,
requestPermissions = { Manifest.permission.READ_CONTACTS },
needReCall = true
)
private void setText(final String text) {
String[] var2 = new String[]{ "android.permission.READ_CONTACTS"};
boolean var3 = PermissionsManager.getInstance().hasAllPermissions(this, var2);
if (!var3) {
ArrayList var4 = new ArrayList();
var4.add(text);
this.requestPermissionMethodParams.put(10010, var4);
PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(10010, this, var2, this);
} else {
this.mTextMessage.setText(text);
}
}
public void onClick(View v) {
this.setText("哈哈哈哈哈哈");
}
public void onGranted(int var1, String var2) {
}
public void onDenied(int var1, String var2) {
}
public void onDeniedForever(int var1, String var2) {
}
public void onFailure(int var1, String[] var2) {
}
public void onSuccess(int var1) {
Object var2 = this.requestPermissionMethodParams.get(var1);
if (var1 == 10010) {
this.setText((String)((List)var2).get(0));
}
}
public void onRequestPermissionsResult(int var1, String[] var2, int[] var3) {
PermissionsManager.getInstance().notifyPermissionsChange(var2, var3);
super.onRequestPermissionsResult(var1, var2, var3);
}
}
我想,通过这个对比,大家肯定能看出来区别在哪里吧。简化了很多逻辑代码,当然也增加了一些代码,不过,两种方式孰优孰劣,大家一眼便知。
三、结语
本库看上去用起来似乎很简单,但是具体的细节实现上却并不是那么容易,由于个人能力和一些技术的限制,我还面临很多问题。比如本开源库只支持在Activity或Fragment里使用;又比如这个开源库不能放到内部类里等等等等,具体的可查看本开源库README的第4条,这些都是目前我无法解决的问题,希望能有大神提点一二,感激不尽。
这个库其实更深一点的意义,就是在这里抛砖引玉,希望大家能够挖掘出更好的Android权限管理开源库,造福千千万万的Android程序员啊。为什么这么说呢?其实很简单,这个库权限申请的部分是simplepermission里的代码实现的,我所开发的插件就必须受到simplepermission这个库的一些限制,但是思路却可以不受限制,说不定大家手里有非常好的开源圈子看不到的Adroid权限申请库,如果大家把手里的Adroid权限申请库顺着我这个思路也写个gradle插件,让Android权限申请的使用更加的简便,功能更加丰富,岂不是造福广大的Android程序猿啊,想想还有点小激动呢_。