背景:
衆所周知,安卓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)可以下載查看示例