背景:
众所周知,安卓6.0以后有了权限动态申请机制,很多功能需要在运行时申请权限,下面就来一起实现一个申请运行时权限的工具类吧
前提:
首先,动态申请权限一般的写法是在activity里调用“ActivityCompat.requestPermissions”,然后重写activity的“onRequestPermissionsResult”方法获取授权结果,但是,这样写会有两个问题,
- 1.破坏activity的结构,在每个需要申请权限的activity的方法都需要重写“onRequestPermissionsResult”方法,不太好,尤其是在成熟的大项目中。
- 2.当申请权限的地方不是activity时,会比较麻烦,有人问,不在activity中在哪呢,这就要看你们的业务需求了,反正我还真遇到了这中情况,显然就无法通过重写“onRequestPermissionsResult”来获取授权回调了。
怎么做:
- 通过分析以上两点,接下来要做的已经很清晰了,我们可以通过打开一个透明的activity来实现权限申请,activity中除了动态申请权限外什么都不做,在此透明activity的“onRequestPermissionsResult”方法里,通过一个接口将授权结果回调回去
- 乍一看好像可行,实际上在大多数情况下确实是可行的,但是我在项目开发中又遇到一个问题,那就是,申请权限的那个activity不在主进程的时候,无法获取到权限授权结果,也就是需要权限的地方和透明申请权限的activity不在一个进程,导致无法回调授权结果,这时又有人可能会问,为毛会有activity不在主进程呢?嗯,项目大了什么代码都可能有,反正就不在主进程,这该怎么办呢?其实很好办,用进程间通信。好了,知道怎么做了,现在开始吧。
所用到的知识
- 1.权限的动态申请
- 2.Handler
- 3.进程间通信
开始:
RequestPermissionUtil
1.第一步首先应该是个工具类了,名字就叫RequestPermissionUtil
public class RequestPermissionUtil {
private static volatile RequestPermissionUtil instance;
private Messenger mMessenger;
private Context mContext;
private OnPermissionListener mListener;
private String[] mPermissionList;
public static RequestPermissionUtil getInstance() {
if (instance == null) {
synchronized (RequestPermissionUtil.class) {
if (instance == null) {
instance = new RequestPermissionUtil();
}
}
}
return instance;
}
private RequestPermissionUtil() {
}
}
2.当然这个类目前什么都没做,接下来给它添加一些必要的方法
public class RequestPermissionUtil {
private static volatile RequestPermissionUtil instance;
private Messenger mMessenger;
private Context mContext;
private OnPermissionListener mListener;
private String[] mPermissionList;
public static RequestPermissionUtil getInstance() {
if (instance == null) {
synchronized (RequestPermissionUtil.class) {
if (instance == null) {
instance = new RequestPermissionUtil();
}
}
}
return instance;
}
private RequestPermissionUtil() {
}
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (mListener != null && msg.getData().get(RequestPermissionService.RESP_RESULT) != null) {
mListener.onPermissionResult((Boolean) msg.getData().get(RequestPermissionService.RESP_RESULT));
}
mContext.unbindService(mServiceConnection);
return true;
}
});
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMessenger = new Messenger(service);
Message mMessage = Message.obtain(null, RequestPermissionService.MSG_FROMCLIENT);
mMessage.replyTo = new Messenger(mHandler);
mMessage.obj = mPermissionList;
try {
mMessenger.send(mMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
public void requestPermissions(Context context, String[] permissionArr, OnPermissionListener listener) {
if (context == null || listener == null) {
return;
}
mContext = context;
mListener = listener;
mPermissionList = permissionArr;
if (Build.VERSION.SDK_INT <= 22 || allOpen(permissionArr)) {
mListener.onPermissionResult(true);
} else {
Intent intent = new Intent(mContext, RequestPermissionService.class);
mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
}
private boolean allOpen(String[] permissionArr) {
if (permissionArr == null || permissionArr.length == 0) {
return true;
}
for (String str : permissionArr) {
if (ContextCompat.checkSelfPermission(mContext, str) !=
PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
public interface OnPermissionListener extends Serializable {
void onPermissionResult(boolean success);
}
}
一下加了这么多代码,下面就开始慢慢介绍这都是来干嘛的,就从下向上看
- 首先,看到的是一个OnPermissionListener接口,很明显这是来处理授权结果回调的
- 再向上看到一个allOpen函数,这个是检测权限是否开启,开启的话直接回调,就不用去申请了
- 接下来看到一个比较重要的方法“requestPermissions”,第一个参数传context,这个context主要用来开启service,第二个参数传权限列表,第三个参数传回调函数,进入函数内部,先检查当前安卓版本小于6.0或者权限已经全部开启,直接回调开启,否则的话,去绑定一个服务,其中绑定函数中“mServiceConnection”接下来会介绍
- 再向上看是一个ServiceConnection,这个类的实例mServiceConnection在bindService的时候使用,当服务连接时会调用ServiceConnection的onServiceConnected方法,我们可以在这个方法给service发送一些消息,这里我们new了一个Messenger,构造方法里传入了Binder类型的service,这样就可以通过这个Messenger给service发消息了,然后我们又获取了一个message对象,给这个的message对象的replayTo属性赋值了一个构造方法里传入了一个Handler的Messager对象,这一步是干嘛的呢?刚刚说过我们经过一些操作已经可以给service发消息了,可是service怎么向我们这里发消息呢?刚刚那个方法就是用来解决这个问题的,Service发给我们的消息都会在构造方法里的那个mHandler的回调里执行。然后我们把权限列表赋值给了msg的obj,最后通过mMessenger将消息发送给服务端,这样服务端就能接收到这个msg了。
- 再向上看,是一个Handler,刚刚说过这个handler是用来处理service发来的消息的,同时在该方法里解除对service的绑定,因为通信理论上只需要一次
RequestPermissionService
接下来介绍这个中介:RequestPermissionService,看代码
public class RequestPermissionService extends Service {
public static final int MSG_FROMCLIENT = 1000;
public static final String RESP_RESULT = "result";
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(final Message msg) {
switch (msg.what) {
case MSG_FROMCLIENT:
final Messenger mMessenger = msg.replyTo;
String[] permissionList = (String[]) msg.obj;
RequestPermissionActivity.start(getApplicationContext(), permissionList, new RequestPermissionUtil.OnPermissionListener() {
@Override
public void onPermissionResult(boolean success) {
Message mMessage = Message.obtain(null, RequestPermissionService.MSG_FROMCLIENT);
Bundle mBundle = new Bundle();
mBundle.putBoolean(RESP_RESULT, success);
mMessage.setData(mBundle);
try {
mMessenger.send(mMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
break;
}
return true;
}
});
@Override
public IBinder onBind(Intent intent) {
return new Messenger(mHandler).getBinder();
}
}
这里代码就比较少了,还是从下向上介绍
- 首先看到一个onBind方法,经常背面试题的同学都知道,这个方法会在binderService的时候被调用,返回的那个IBinder就是之前说的那个在ServiceConnection的onServiceConnected的service,这里构造方法里也传了一个Handler,这个handler同时用来接受消息和发送消息
- 再向上看那个handler,我们接受到消息后,得到要请求的权限数组,就开启权限请求了,在这里我们直接看onPermissionResult方法,获取回调中的权限申请结果,将msg发送给客户端那边
RequestPermissionActivity
接下来看这个RequestPermissionActivity,有几处细节还是值得说一下的
public class RequestPermissionActivity extends AppCompatActivity {
private static final int REQUEST_CODE = 1;
private static RequestPermissionUtil.OnPermissionListener transferOnPermissionListener;
private RequestPermissionUtil.OnPermissionListener mOnPermissionListener;
private boolean mResult = false;
public static final String EXTRA_PERMISSIONS = "permissions";
private String[] mPermissionList = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= 19) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
getIntentData();
ActivityCompat.requestPermissions(RequestPermissionActivity.this, mPermissionList, REQUEST_CODE);
}
private void getIntentData() {
mOnPermissionListener = transferOnPermissionListener;
transferOnPermissionListener = null;
mPermissionList = getIntent().getStringArrayExtra(EXTRA_PERMISSIONS);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults != null && grantResults.length > 0) {
mResult = true;
for (int tmp : grantResults) {
if (tmp != PackageManager.PERMISSION_GRANTED) {
mResult = false;
break;
}
}
}
finish();
}
@Override
public void finish() {
super.finish();
overridePendingTransition(0, 0);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mOnPermissionListener != null) {
mOnPermissionListener.onPermissionResult(mResult);
}
}
public static void start(Context context, String[] permissionList, RequestPermissionUtil.OnPermissionListener listener) {
Intent intent = new Intent(context, RequestPermissionActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(RequestPermissionActivity.EXTRA_PERMISSIONS, permissionList);
context.startActivity(intent);
transferOnPermissionListener = listener;
}
}
继续从下向上看
- 这是刚刚在service里调用的start方法,第一个参数传context用于打开Activity,第二个参数传权限数组,第三个参数传回调接口,其中加intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)保证了context不是activity的时候也能正常打开这个activity
- 再向上看destroy方法,在这里处理回调结果,保证了回调只执行一次
- finish方法去掉了动画
- onRequestPermissionsResult获取授权结果,并直接finish界面保证授权结果回调回去
总结
总的来说代码还是比较简单的,我将上面的这些代码封装成了一个lib,点击这里(github)可以下载查看示例