1. 程式人生 > >適配android6.0:執行時許可權檢查機制

適配android6.0:執行時許可權檢查機制

前言

清明放假終於結束了,趕緊寫點東西來脈動回來。這是一篇偏概念性的文章,文字偏多,所以別捉急,慢慢看。

現在高版本的android系統市場佔有率提升的非常快,這依賴於智慧手機越來越便宜,越來越普遍,新手機一般都會搭載高版本的android系統,來豐富使用者的體驗,但是也逐漸的暴露出了很多的問題,最嚴重的就是使用者的安全問題。

之前很多應用會申請很多的許可權,尤其是第三方sdk,我們也不知道到底他們要用這些許可權做什麼,只要把許可權寫到配置檔案中,系統就默許了許可權。

Google發現了這會給使用者造成非常大的困擾和安全問題,例如app隨意的讀取手機的聯絡人,獲取使用者的地理位置等等,於是在Android 6.0 開始了新的許可權機制:執行時檢查,不再預設許可權行為。這也是對開發者影響最大的一點,那麼如果適配android 6.0呢?

正文

執行時許可權檢查機制

在app執行時,使用了敏感許可權,會提示使用者是否要授予這個app對應的許可權,使用者有拒絕和同意的權利,如果拒絕最好要提示使用者,拒絕會造成什麼樣的影響,這樣使用者能明白申請的原因,重新授予許可權。

哪些是敏感許可權

<!-- CALENDAR 日曆組 -->
    <uses-permission android:name="android.permission.READ_CALENDAR" />
    <uses-permission android:name="android.permission.WRITE_CALENDAR"
/>
<!-- CAMERA 相機拍照組 --> <uses-permission android:name="android.permission.CAMERA" /> <!-- CONTACTS 聯絡人組 --> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" /> <uses-permission
android:name="android.permission.GET_ACCOUNTS" />
<!-- LOCATION 定位組 --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- MICROPHONE 麥克風組 --> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <!-- PHONE 組 --> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.READ_CALL_LOG" /> <uses-permission android:name="android.permission.WRITE_CALL_LOG" /> <uses-permission android:name="android.permission.USE_SIP" /> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" /> <!-- SENSORS 感測器組 --> <uses-permission android:name="android.permission.BODY_SENSORS" /> <!-- SMS 組 --> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.RECEIVE_SMS" /> <uses-permission android:name="android.permission.READ_SMS" /> <uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" /> <uses-permission android:name="android.permission.RECEIVE_MMS" /> <!-- STORAGE 儲存組 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

這是從別處複製過來的,看的出來只要是和使用者相關的許可權,幾乎都屬於敏感許可權,值得注意的是有些許可權,部分手機已經進行了優化,例如我使用的360手機,讀寫sd卡許可權就是默許的,如果你也遇到了這個情況,也不用驚訝。

如何申請許可權

android 6.0 把許可權的申請和回執有兩處:Activity和Fragment。其他的地方目前不可以。申請的方法:

 // 檢查是否應被授權
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
    // 已經被授權,直接進行操作
} else {
    // 沒有授權,需要進行授權申請
    // Context  申請的許可權   申請的請求碼
    ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.ACCESS_COARSE_LOCATION}, 0);
}

先檢查是否已經授予了許可權,如果沒有就去申請許可權。處理結果在:

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    switch(requestCode){
        case 0:
            // 處理使用者授權的返回結果
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

            } else {
                    // 授權失敗
            }
            break;
        default:
            break;
    }
}

這樣整個授權流程就弄清楚了,兩個api,重寫onRequestPermissionsResult。那麼問題來了,如果直接在Activity中申請許可權還好一點,如果是原來的Dialog裡呢?

介面式開發

這個概念已經是老朋友了,而且我們也經常用,例如自定義View,在onClick呼叫自定義的點選介面,這就是介面式開發,把實際的功能實現交給其他人,控制元件本身作為一個媒介。

android 6.0很明顯是希望使用者改變原來的習慣,或者說是規範編碼習慣,例如,有些Dialog的程式碼非常的龐大,有10個按鈕,Dialog裡就會有10個功能邏輯,典型的就是第三方分享(按鈕真心多啊)。

Dialog大概就是對話方塊的意思,從概念上就能感受到,他是一個很輕量的東西,就跟跑腿的一樣,老闆說問問客人要吃啥,服務員問完客人在回來告訴老闆,老闆知道結果後,就給客人做什麼。

寫一個小Demo

只貼一下MainActivity的程式碼把:

public class MainActivity extends AppCompatActivity implements TipDialog.OnTipDialogButtonOnClickListener {

    private static final String PERMISSION = Manifest.permission.CAMERA;
    private ConstraintLayout container;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        container = (ConstraintLayout) findViewById(R.id.container);

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TipDialog tipDialog = new TipDialog(MainActivity.this);
                tipDialog.setTitle("是否開啟攝像頭");
                tipDialog.setOnTipDialogButtonOnClickListener(MainActivity.this);
                tipDialog.show();
            }
        });

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 0:
                // 處理使用者授權的返回結果
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    openCamera();
                } else {
                    // 授權失敗
                    Toast.makeText(MainActivity.this, "未授予攝像頭許可權,無法使用", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
    }

    @Override
    public void onTipDialogCancelButtonClick(TipDialog dialog) {
        dialog.dismiss();
    }

    @Override
    public void onTipDialogSureButtonClick(TipDialog dialog) {
        dialog.dismiss();
        openCamera();
    }

    private void openCamera() {
        // 檢查是否應被授權
        if (ContextCompat.checkSelfPermission(MainActivity.this, PERMISSION) != PackageManager.PERMISSION_GRANTED) {
//            // 沒有授權,需要進行授權申請
//            // Context  申請的許可權   申請的請求碼
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{PERMISSION}, 0);
        } else {
            container.addView(new FlashLightSurfaceView(this), 0, 0);
        }
    }
}

這個demo 非常簡單,首先我自定義了一個FlashLightSurfaceView,他裡面打開了Camera,使用攝像頭前判斷許可權,沒有的話申請許可權,被拒絕就提示沒有許可權無法使用。

強調一點:onRequestPermissionsResult只能回撥這個Activity發起的許可權申請(requestPermissions)。

但是這個demo卻沒有什麼代表性,因為他只能在原生android執行正常,例如模擬器,但是真機就很難說了。

真機執行情況

已經有很多朋友已經踩過這個坑了,由於了國內產商都對系統進行了定製,我發現對許可權這部分影響真的是太大了,我遇到主要有以下幾種情況:

1、僅僅是申請許可權,但是當時沒有使用到許可權對應的Api,不會彈出許可權申請視窗。

2、當使用到對應許可權的API,app會自動申請許可權,彈出許可權申請視窗,而這個申請結果我們是無法通過onRequestPermissionsResult來獲取結果的。

3、如果直接拒絕了許可權申請,仍然返回PackageManager.PERMISSION_GRANTED。

是不是很尷尬?整個許可權流程已經被蹂躪的慘不忍睹,這還怎麼接著搞事情?

解決辦法

我目前發現的最好的辦法就是 try-catch,使用了沒有許可權的API,系統就會報錯甚至崩潰,那我就直接在外圍加上try-catch,如果丟擲了問題,百分之八十都是許可權的問題,那麼修改一下:


    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        try {
            if (camera == null) {
                int count = Camera.getNumberOfCameras();
                if (count > 0) {
                    camera = Camera.open();
                    camera.setPreviewDisplay(holder);
                } else {
                    Toast.makeText(getContext(), "沒有找到攝像頭...", Toast.LENGTH_SHORT).show();
                }
            }
        } catch (Exception e) {
            if (camera != null)
                camera.release();
            camera = null;
            Toast.makeText(getContext(), "未授予攝像頭許可權,無法使用", Toast.LENGTH_SHORT).show();
        }
    }

總結

android6.0 的許可權執行機制是好的,但是沒想到會變成目前這麼亂套的情況,給我們的適配也增加了很大的難度。目前流行的各大廠商可能也有自己的考慮,把許可權這一塊似乎都想把控在自己的手裡,造成這樣的局面,也是很尷尬。

雖然通過一些小手段可以暫時彌補,但是最關鍵的還是希望各位廠商大大們還是儘量保持原生的許可權機智吧…

ok,那就到這裡了,如果你有更好的解決android 6.0的許可權適配的問題,請留下您的技巧,讓大家共同進步。

補充

忘了強調一個細節,在申請許可權的時候,不能申請Manifest.permission_group.xxxx,否則是不會彈出許可權申請的提示的。你要問我為啥,我只能猜測,設計者更希望你只申請一個,而不是一組…