1. 程式人生 > >以呼叫系統相機拍照為例瞭解Android 6.0執行時許可權

以呼叫系統相機拍照為例瞭解Android 6.0執行時許可權

首先扯點別的,聽說這個週末是好天氣,想約她一起去公園賞賞梅花,只有我自己估計她也不一定去啊,哈哈。

在android6.0及以上系統,Android在安裝一個應用的時候不再需要列出一大堆許可權,讓使用者點選同意以後才可以安裝。Instead, 當應用在執行的時候,如果要使用危險許可權的時候需要明確告知使用者並得到使用者許可後方可進行一些Android系統認為是危險行為的操作。另外當你在程式執行過程中開啟了某些許可權,使用者後來還是可以在應用設定裡面關閉這些許可權。

Android 系統危險許可權列表:許可權是分組的,如果一個許可權組中的任意一個許可權被允許了,同組的其他許可權也會被允許。

許可權組 許可權
CALENDAR READ_CALENDAR
CALENDAR WRITE_CALENDAR
CAMERA CAMERA
CONTACTS READ_CONTACTS
CONTACTS WRITE_CONTACTS
CONTACTS GET_ACCOUNTS
LOCATION ACCESS_FINE_LOCATION
LOCATION ACCESS_COARSE_LOCATION
MICROPHONE RECORD_AUDIO
PHONE READ_PHONE_STATE
PHONE CALL_PHONE
PHONE READ_CALL_LOG
PHONE WRITE_CALL_LOG
PHONE ADD_VOICEMAIL
PHONE USE_SIP
PHONE PROCESS_OUTGOING_CALLS
SENSORS BODY_SENSORS
SMS SEND_SMS
SMS RECEIVE_SMS
SMS READ_SMS
SMS RECEIVE_WAP_PUSH
SMS RECEIVE_MMS
STORAGE READ_EXTERNAL_STORAGE
STORAGE WRITE_EXTERNAL_STORAGE

以呼叫系統相機拍照為例瞭解Android6.0的執行時許可權,使用android studio 自帶的模擬器,API 選擇24.

呼叫系統相機進行拍照,如果想儲存全尺寸的大圖的時候(我們在系統公共儲存目錄DCIM下新建一個資料夾用來儲存我們拍攝的圖片),需要傳遞一個路徑給系統相機用來儲存拍攝的圖片。

//拍照的程式碼
 private void takePhoto() {
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
            //建立一個File
            photoFile = ImageUtil.createImageFile();
            if (photoFile != null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    //如果是7.0及以上的系統使用FileProvider的方式建立一個Uri
                    Log.e(TAG, "Build.VERSION.SDK_INT >= Build.VERSION_CODES.N");
                    photoURI = FileProvider.getUriForFile(this, "com.hm.camerademo.fileprovider", photoFile);
                    takePictureIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    takePictureIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                } else {
                    //7.0以下使用這種方式建立一個Uri
                    photoURI = Uri.fromFile(photoFile);
                }
                //將Uri傳遞給系統相機
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
                startActivityForResult(takePictureIntent, TAKE_PHOTO);
            }
        }
    }

因為需要建立一個File 用來儲存拍攝的圖片,這就需要有WRITE_EXTERNAL_STORAGE的許可權,所以我們在呼叫takePhoto()這段程式碼之前就應該先檢查我們的App是否已經有了WRITE_EXTERNAL_STORAGE許可權,如果沒有,需要先申請,申請成功後再呼叫takePhoto()進行拍照。

1:首先:在AndroidManifest.xml 中宣告我們需要的許可權

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

2:呼叫ContextCompat.checkSelfPermission(Context context, String permission)檢查是否有WRITE_EXTERNAL_STORAGE許可權,如果沒有就申請許可權,否則直接拍照.


 if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                //申請許可權,REQUEST_TAKE_PHOTO_PERMISSION是自定義的常量
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                        REQUEST_TAKE_PHOTO_PERMISSION);
            }
        } else {
            //有許可權,直接拍照
            takePhoto();
        }

3:重寫onRequestPermissionsResult(int requestCode, String[] permissions,int[] grantResults)方法,檢查許可權申請是否成功。

 @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_TAKE_PHOTO_PERMISSION) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            //申請成功,可以拍照
                takePhoto();
            } else {
                Toast.makeText(this, "CAMERA PERMISSION DENIED", Toast.LENGTH_SHORT).show();
            }
            return;
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

以上三步就是最簡單的流程。
這裡寫圖片描述

從上邊的效果圖中可以看到,如果當動態申請許可權的時候如果使用者點選了ALLOW的話,許可權申請就成功,然後就可以拍照了,一切順利,那麼當用戶點選了DENY結果會是咋樣呢?來試一試。
這裡寫圖片描述

可以看到,當我們第一次點選DENY的時候,請求許可權失敗,然後我們點選拍照,再次申請許可權的時候,系統還是會彈出一個對話方塊讓使用者進行確認,如果這時候使用者點選了ALLOW那麼還是可以愉快的進行拍照的。如果使用者點選了,DENY,但是沒有勾選Don’t ask again 的話,下次拍照進行許可權申請的時候還是會彈出這個對話方塊,如果這時候使用者點選ALLOW 的話,許可權就會申請成功,可以進行拍照。但是如果使用者點選了DENY,並且勾選了Don’t ask again 這是會有什麼效果呢?試一試

這裡寫圖片描述

從上面的截圖中可以看到,如果使用者點選了DENY 並且勾選了Don’t ask again,那麼申請許可權會一直失敗,也就是說使用者不能再使用相機功能了,那如果使用者這時候又想使用相機功能怎麼辦?
方法1:使用者可以自己去應用設定裡面把相應的許可權開啟,然後再回來使用拍照功能,如下圖所示
這裡寫圖片描述

方法2:如果使用者點選了DENY 並且勾選了Don’t ask again,當用戶再次點選拍照的時候,我們應該主動彈出一個對話方塊引導使用者去應用設定裡面開啟相應的許可權,效果如下
這裡寫圖片描述

接下來就實現這種效果:最重要的一點就是我們怎麼知道使用者拒絕了我們申請額許可權並且勾選了 Don’t ask again。這時候就需要用到ActivityCompat的一個方法。

  public static boolean shouldShowRequestPermissionRationale( Activity activity, String permission)

這個方法的意思是:例如你需要申請WRITE_EXTERNAL_STORAGE的許可權,這個方法的返回值表示是否應該向使用者解釋為什麼你需要你正在申請的許可權。如果這個方法返回true,表示你應該給出使用者一個合理的解釋(海蔘炒麵,海蔘呢?),返回false,你不需要提示使用者。

所以我們必須得知道shouldShowRequestPermissionRationale何時返回true,何時返回false。下面我用文字敘述這個流程。
1.第一次請求許可權的時候,返回false,如果使用者直接ALLOW 了申請的許可權,直接就和諧了,沒什麼可說的。

2.第一次請求許可權的時候,返回false,使用者選擇了DENY,就不能拍照了。使用者第二次申請許可權,返回true,這時候系統的彈出的對話方塊會有一個checkbox,讓你選擇是否Don’t ask again,如果你選擇了ALLOW,那還是比較和諧的,還是可以正常拍照。

3.第一次請求許可權的時候,返回false,使用者選擇了DENY,就不能拍照了。使用者第二次申請許可權,返回true,這時候系統彈出的對話方塊會有一個checkbox,讓你選擇是否Don’t ask again,如果你沒有勾選checkbox並選擇了DENY。第三次請求許可權,返回true,這時候系統彈出的對話方塊會有一個checkbox,讓你選擇是否Don’t ask again,如果你選擇了DENY,並且勾選了Don’t ask again。第四次申請 ,返回false,系統不會再彈出對話方塊詢問你了,而你也不能拍照了。

經過上面的敘述,我們主動彈出對話方塊讓使用者開啟許可權的條件是:上一次請求許可權的時候shouldShowRequestPermissionRationale方法返回true,而本次請求許可權的時候shouldShowRequestPermissionRationale返回false。所以我們得儲存上一次請求許可權的時候shouldShowRequestPermissionRationale的返回值flag和本次請求許可權shouldShowRequestPermissionRationale的返回值nowFlag共同作用,當flag==true&&nowFlag==false的時候主動彈出對話方塊。

我們把上一次請求許可權,shouldShowRequestPermissionRationale返回的結果儲存在SharedPreferences中,

public class SpUtil {

    private static SharedPreferences hmSpref;
    private static SharedPreferences.Editor editor;
    private static SpUtil spUtil;
    private final String FLAG = "flag";

    private SpUtil() {
        hmSpref = App.getInstance().getSharedPreferences("hmSpref", Context.MODE_PRIVATE);
        editor = hmSpref.edit();
    }

    public static SpUtil getInstance() {
        if (spUtil == null) {
            synchronized (SpUtil.class) {
                if (spUtil == null) {
                    spUtil = new SpUtil();
                }
            }
        }
        return spUtil;
    }

    public void putFlag(boolean flag) {
        editor.putBoolean(FLAG, flag);
        editor.commit();
    }

    //第一次請求許可權的時候,返回的flag是false
    public boolean getFlag() {
        return hmSpref.getBoolean(FLAG, false);
    }

}

修改申請許可權的方法

 private void takePhotoRequestPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        //判斷是否需要主動彈出對話方塊
            if (SpUtil.getInstance().getFlag() &&
                    !ActivityCompat.shouldShowRequestPermissionRationale(this,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                           //滿足條件彈出自定義dialog
                if (dialog == null) {
                    dialog = MyDialog.newInstance("相機故障", "需要開啟讀寫資料許可權才可以使用拍照功能");
                    dialog.setOnAllowClickListener(new MyDialog.OnAllowClickListener() {
                        @Override
                        public void onClick() {
                            //使用者點選 GO SETTING 的時候跳轉到應用設定介面
                            startAppSetting();
                        }
                    });
                }
                dialog.show(getSupportFragmentManager(), "dialog");
            } else {
 //儲存 shouldShowRequestPermissionRationale的返回值  
 SpUtil.getInstance().putFlag(ActivityCompat.shouldShowRequestPermissionRationale(this,
 Manifest.permission.WRITE_EXTERNAL_STORAGE));
                //直接申請許可權
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                        REQUEST_TAKE_PHOTO_PERMISSION);
            }
        } else {
            takePhoto();
        }
    }
//使用startActivityForResult的方式啟動應用設定介面
public void startAppSetting() {
        Intent in = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        Uri uri = Uri.fromParts("package", getPackageName(), null);
        in.setData(uri);
        startActivityForResult(in, REQUEST_TAKE_PHOTO_PERMISSION);
    }

重寫onActivityResult 方法

  @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case REQUEST_TAKE_PHOTO_PERMISSION:
                //在這裡再次檢查許可權進行拍照
                takePhotoRequestPermission();
                break;
            default:
                break;
        }
    }

結尾:流程基本就是這樣,本文的目的旨在瞭解申請許可權的流程,如果要是同時申請多個許可權就有有點麻煩。如果要在專案中使用的,可以直接使用github上的一個開源的許可權申請庫,googlesamples/easypermissions

附錄 Normal Permissions
只要在AndroidManifest.xml檔案中聲明瞭正常許可權,在安裝的時候許可權就會開啟,而且使用者不能關閉許可權。

android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.GET_PACKAGE_SIZE
android.permission.INSTALL_SHORTCUT
android.permission.INTERNET
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.READ_SYNC_SETTINGS
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.SET_ALARM
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER
android.permission.SET_WALLPAPER_HINTS
android.permission.TRANSMIT_IR
android.permission..UNINSTALL_SHORTCUT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS