手把手教你實現一個通用的安卓權限請求工具

背景:

衆所周知,安卓6.0以後有了權限動態申請機制,很多功能需要在運行時申請權限,下面就來一起實現一個申請運行時權限的工具類吧

前提:

首先,動態申請權限一般的寫法是在activity裏調用“ActivityCompat.requestPermissions”,然後重寫activity的“onRequestPermissionsResult”方法獲取授權結果,但是,這樣寫會有兩個問題,

  • 1.破壞activity的結構,在每個需要申請權限的activity的方法都需要重寫“onRequestPermissionsResult”方法,不太好,尤其是在成熟的大項目中。
  • 2.當申請權限的地方不是activity時,會比較麻煩,有人問,不在activity中在哪呢,這就要看你們的業務需求了,反正我還真遇到了這中情況,顯然就無法通過重寫“onRequestPermissionsResult”來獲取授權回調了。

怎麼做:

  1. 通過分析以上兩點,接下來要做的已經很清晰了,我們可以通過打開一個透明的activity來實現權限申請,activity中除了動態申請權限外什麼都不做,在此透明activity的“onRequestPermissionsResult”方法裏,通過一個接口將授權結果回調回去
  2. 乍一看好像可行,實際上在大多數情況下確實是可行的,但是我在項目開發中又遇到一個問題,那就是,申請權限的那個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)可以下載查看示例

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章