本篇文章已授權(quán)微信公眾號(hào) hongyangAndroid (鴻洋)獨(dú)家發(fā)布
Android6.0權(quán)限機(jī)制(一):介紹
Android6.0權(quán)限機(jī)制(二):封裝
Android6.0權(quán)限機(jī)制(三):6.0以前國(guó)產(chǎn)手機(jī)權(quán)限處理
前言
Android是在6.0加入的權(quán)限機(jī)制,但是不少?lài)?guó)產(chǎn)手機(jī)比如華為小米等,在6.0之前的設(shè)備已經(jīng)在設(shè)置里面有權(quán)限開(kāi)關(guān)。調(diào)皮的某個(gè)用戶(hù)會(huì)關(guān)掉這個(gè)權(quán)限,然后去拍照什么的,直接崩掉了。之前大牛郭霖在csdn做了一期權(quán)限機(jī)制的教學(xué)直播,結(jié)束后我問(wèn)了他這個(gè)問(wèn)題,他的回答是簡(jiǎn)單try catch即可無(wú)需深究。那我想結(jié)合實(shí)際項(xiàng)目研究下這個(gè)問(wèn)題。
Android權(quán)限機(jī)制簡(jiǎn)介
Android是在6.0之前只需要在menifest注冊(cè),用戶(hù)安裝app會(huì)有一個(gè)權(quán)限列表,類(lèi)似一份協(xié)議表示用戶(hù)對(duì)此已經(jīng)知曉。但是實(shí)際中哪個(gè)用戶(hù)會(huì)認(rèn)真去看呢?導(dǎo)致很多app濫用權(quán)限給用戶(hù)造成風(fēng)險(xiǎn),于是在6.0后Android推出9組危險(xiǎn)權(quán)限,要求開(kāi)發(fā)者不僅要在menifest注冊(cè)還要?jiǎng)討B(tài)申請(qǐng)權(quán)限,比如調(diào)用拍照會(huì)彈出權(quán)限提示,只有用戶(hù)自己點(diǎn)了確定才能繼續(xù)拍照。
測(cè)試
了解了Android6.0權(quán)限機(jī)制后,我們用系統(tǒng)的權(quán)限API對(duì)國(guó)產(chǎn)手機(jī)測(cè)試一下。
測(cè)試設(shè)備:華為,系統(tǒng)版本4.4
測(cè)試內(nèi)容:拍照,通話(huà),錄音
測(cè)試API:
- ContextCompat.checkSelfPermission:檢查是否有某個(gè)權(quán)限
- requestPermission:主動(dòng)申請(qǐng)某個(gè)權(quán)限,看看回調(diào)結(jié)果
測(cè)試代碼:
String[] permissions = new String[]{Manifest.permission.CALL_PHONE,Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO};
for(int i=0;i<permissions.length;i++){
String permission = permissions[i];
int check = ContextCompat.checkSelfPermission(MyActivity.this, permission);
if(check== PackageManager.PERMISSION_GRANTED){
Log.d("dml","權(quán)限通過(guò)");
}else{
Log.d("dml","無(wú)權(quán)限");
}
requestPermission(permissions, new OnPermissionCallback() {
@Override
public void onGranted() {
Log.d("dml","權(quán)限申請(qǐng)成功");
}
@Override
public void onDenied(List<String> deniedPermissions) {
Log.d("dml","權(quán)限被拒絕");
}
});
}
先進(jìn)入設(shè)置關(guān)閉這個(gè)app的電話(huà),照相和錄音權(quán)限

運(yùn)行app,發(fā)現(xiàn)并沒(méi)有彈出讓用戶(hù)開(kāi)關(guān)權(quán)限的Dialiog,并且檢查權(quán)限返回權(quán)限通過(guò)??纯慈罩荆?/p>
D/dml: android.permission.CALL_PHONE權(quán)限通過(guò)
D/dml: android.permission.CALL_PHONE權(quán)限申請(qǐng)成功
D/dml: android.permission.CAMERA權(quán)限通過(guò)
D/dml: android.permission.CAMERA權(quán)限申請(qǐng)成功
D/dml: android.permission.RECORD_AUDIO權(quán)限通過(guò)
D/dml: android.permission.RECORD_AUDIO權(quán)限申請(qǐng)成功
結(jié)論
檢查權(quán)限和申請(qǐng)權(quán)限的api在華為4.4手機(jī) 完全失效,看下源碼也不難得到印證:

當(dāng)運(yùn)行設(shè)備在23以下也就是6.0以下的設(shè)備時(shí),權(quán)限申請(qǐng)其實(shí)是通過(guò)PackageManager.checkPermission()來(lái)進(jìn)行,看下這個(gè)方法:

是個(gè)抽象方法,不過(guò)注釋看出:只是判斷你的apk也就是你的Manifest.xml有沒(méi)有注冊(cè)這個(gè)權(quán)限,有那么就返回true。
實(shí)際開(kāi)發(fā)的問(wèn)題
- 如果不去做檢測(cè),直接調(diào)用會(huì)如何呢?
經(jīng)過(guò)測(cè)試以華為Android4.4的手機(jī)為例,關(guān)閉上述三項(xiàng)權(quán)限進(jìn)行操作,發(fā)現(xiàn)系統(tǒng)都彈出了沒(méi)有權(quán)限的Toast,不一樣的是:
打電話(huà):不能進(jìn)入撥號(hào)界面,也沒(méi)有閃退,沒(méi)有異常輸出
打開(kāi)攝像頭:直接閃退,有異常輸出
錄音:沒(méi)有閃退,沒(méi)有異常,錄音開(kāi)啟了但是沒(méi)有數(shù)據(jù)(如果我們直接用了這些空數(shù)據(jù),極大可能崩潰)
廠(chǎng)商ROM只是給出了無(wú)權(quán)限提示,但我們要做的就是保證app不能崩潰!

解決方案
- 在代碼中我們針對(duì)Android6.0的權(quán)限檢測(cè)(ContextCompat.checkSelfPermission和requestPermission)按照正常的寫(xiě),保證在Android6.0以上的設(shè)備正常運(yùn)行。
- 然后在具體的操作比如撥號(hào),拍照或者錄音,加一層tyr catch,能捕獲到異常最好,不能捕獲到的話(huà)繼續(xù)第三步。
- 對(duì)具體機(jī)型我們加入if判斷,對(duì)操作數(shù)據(jù)做合法性判斷,比如錄音生成的數(shù)據(jù),下面以華為為例:
還是上面的代碼,我們對(duì)三種操作都加上try catch:
public class MyActivity extends BaseActivity {
private Button btn1,btn2,btn3;
private Camera camera;
private AudioRecord mRecorder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
btn1 = (Button) findViewById(R.id.button);
btn2 = (Button) findViewById(R.id.button2);
btn3 = (Button) findViewById(R.id.button3);
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try{
call();
}catch (Exception e){
Log.e("dml","exception = " + e.getMessage());
}
}
});
btn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try{
openCamera();
}catch (Exception e){
Log.e("dml","exception = " + e.getMessage());
}
}
});
btn3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try{
startRecord();
}catch (Exception e){
Log.e("dml","exception = " + e.getMessage());
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if(mRecorder!=null){
mRecorder.stop();
}
}
private void call(){
Intent intent = new Intent();
intent.setData(Uri.parse("tel://1212121212"));
intent.setAction(Intent.ACTION_CALL);
startActivity(intent);
}
private void openCamera(){
camera = Camera.open(0);
}
private void startRecord(){
new Thread(new Runnable() {
@Override
public void run() {
int bufferSize = AudioRecord.getMinBufferSize(8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
mRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize * 2);
mRecorder.startRecording();
byte[] tempBuffer = new byte[bufferSize];
while(true){
int recordCountSize = mRecorder.read(tempBuffer, 0, bufferSize);
}
}
}).start();
}
}
拍照
這次拍照沒(méi)有閃退了,并且捕獲到了異常,那直接在catch里面彈出Dialog就行了
E/dml: exception = Fail to connect to camera service
撥號(hào)
但是撥號(hào)失敗卻沒(méi)有任何異常,如何處理呢?我們可以在設(shè)置一個(gè)布爾值標(biāo)志位(isNewCal),監(jiān)聽(tīng)到撥打電話(huà)的狀態(tài)設(shè)為true,在撥出電話(huà)延遲500ms檢測(cè)這個(gè)標(biāo)志位,如果還是false說(shuō)明撥打失敗,然后排除移動(dòng)網(wǎng)絡(luò)異常,其他情況全部算作沒(méi)有權(quán)限:
TelephonyManager tm = (TelephonyManager)getSystemService(Service.TELEPHONY_SERVICE);
tm.listen(new PhoneStateListener(){
@Override
public void onCallStateChanged(int state, String incomingNumber) {
// TODO Auto-generated method stub
super.onCallStateChanged(state, incomingNumber);
if(state==TelephonyManager.CALL_STATE_OFFHOOK){
isNewCall = true;
}
}
}, PhoneStateListener.LISTEN_CALL_STATE);
第二種方法處理?yè)芴?hào)權(quán)限問(wèn)題,就是換一種Intent,不要直接撥號(hào)而是跳到系統(tǒng)撥號(hào)界面,讓用戶(hù)自己點(diǎn)擊撥號(hào)。
private void jumpToDial(){
Intent intent = new Intent(Intent.ACTION_DIAL,Uri.parse("tel:1234567"));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}

錄音
錄音代碼加了try catch,但是關(guān)閉權(quán)限沒(méi)有捕獲任何異常(這里指華為,像三星手機(jī)startRecording會(huì)拋出異常),但是可以看出錄出來(lái)的數(shù)據(jù)緩沖區(qū)全部為0:

我們打開(kāi)權(quán)限再看下:

因此我們可以取錄音緩沖區(qū)的部分?jǐn)?shù)據(jù),如果全部為零,那么當(dāng)作沒(méi)權(quán)限立即通知錄音并給出Dialog提示,這里我取緩沖區(qū)的前20個(gè)byte:
private void startRecord(){
new Thread(new Runnable() {
@Override
public void run() {
int bufferSize = AudioRecord.getMinBufferSize(8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
mRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize * 2);
mRecorder.startRecording();
byte[] tempBuffer = new byte[bufferSize];
byte[] checkBuffer = null;
int valuePlus = 0;
while(true){
int recordCountSize = mRecorder.read(tempBuffer, 0, bufferSize);
if(recordCountSize<=0){
if(mRecorder!=null){
mRecorder.stop();//這個(gè)和機(jī)型無(wú)關(guān),recordCountSize<=0肯定是異常
}
}
//注意下面的代碼要加上機(jī)型判斷(根據(jù)你們測(cè)試的反饋),因?yàn)橛械臋C(jī)型就算打開(kāi)權(quán)限 這里前面幾千個(gè)byte就是為0
if(checkBuffer==null){
checkBuffer = new byte[20];
System.arraycopy(tempBuffer,0,checkBuffer,0,20);
for(int value:checkBuffer){
valuePlus+=value;
}
if(valuePlus==0){
if(mRecorder!=null){
mRecorder.stop();
}
break;
}
}
}
}
}).start();
}
總結(jié)
try catch + 數(shù)據(jù)異常判斷的思路,上面我主要針對(duì)華為手機(jī)做了部分權(quán)限的適配,不過(guò)在公司測(cè)試來(lái)看也兼容魅族三星,如果除了大部分主流機(jī)型還有個(gè)別不能兼容怎么辦?我的建議是讓這些調(diào)皮的用戶(hù)自己玩去吧。