Android6.0權(quán)限機(jī)制(三):6.0以前國(guó)產(chǎn)手機(jī)權(quán)限處理

本篇文章已授權(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:

  1. ContextCompat.checkSelfPermission:檢查是否有某個(gè)權(quán)限
  2. 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)限

Paste_Image.png

運(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ī) 完全失效,看下源碼也不難得到印證:

Paste_Image.png

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

Paste_Image.png

是個(gè)抽象方法,不過(guò)注釋看出:只是判斷你的apk也就是你的Manifest.xml有沒(méi)有注冊(cè)這個(gè)權(quán)限,有那么就返回true。

實(shí)際開(kāi)發(fā)的問(wèn)題

  1. 如果不去做檢測(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不能崩潰!

Paste_Image.png

解決方案

  1. 在代碼中我們針對(duì)Android6.0的權(quán)限檢測(cè)(ContextCompat.checkSelfPermission和requestPermission)按照正常的寫(xiě),保證在Android6.0以上的設(shè)備正常運(yùn)行。
  2. 然后在具體的操作比如撥號(hào),拍照或者錄音,加一層tyr catch,能捕獲到異常最好,不能捕獲到的話(huà)繼續(xù)第三步。
  3. 對(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);
    }
Paste_Image.png

錄音

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

Paste_Image.png

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


Paste_Image.png

因此我們可以取錄音緩沖區(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ù)自己玩去吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容