Android 6.0 動(dòng)態(tài)權(quán)限Permission相關(guān)

Andriod 6.0 動(dòng)態(tài)權(quán)限Permission相關(guān)

推薦博文:

隨著Android 6.0發(fā)布以及普及,我們開(kāi)發(fā)者所要應(yīng)對(duì)的主要就是新版本SDK帶來(lái)的一些變化,首先關(guān)注的就是權(quán)限機(jī)制的變化。對(duì)于6.0的幾個(gè)主要的變化,查看查看官網(wǎng)的這篇文章http://developer.android.com/intl/zh-cn/about/versions/marshmallow/android-6.0-changes.html,其中當(dāng)然包含Runtime Permissions。

相關(guān)知識(shí)點(diǎn)

新的權(quán)限機(jī)制更好的保護(hù)了用戶(hù)的隱私,Google將權(quán)限分為兩類(lèi),一類(lèi)是Normal Permissions,這類(lèi)權(quán)限一般不涉及用戶(hù)隱私,是不需要用戶(hù)進(jìn)行授權(quán)的,比如手機(jī)震動(dòng)、訪(fǎng)問(wèn)網(wǎng)絡(luò)等;另一類(lèi)是Dangerous Permission,一般是涉及到用戶(hù)隱私的,需要用戶(hù)進(jìn)行授權(quán),比如讀取sdcard、訪(fǎng)問(wèn)通訊錄等。

targetSdkVersion和minSdkVersion相關(guān)的區(qū)別

關(guān)于taretSdkVersion和minSdkVersion的區(qū)分相信很多人都不是太清楚,在這里推薦Android Min SDK Version vs. Target SDK Version,對(duì)相關(guān)的設(shè)置有點(diǎn)說(shuō)明。這兩者相當(dāng)于一個(gè)區(qū)間,你可以用到targetSDK中最新的API和最酷的新功能,但你又不得不向下兼容到minSDK,保證這個(gè)區(qū)間內(nèi)的設(shè)備都可以正常的運(yùn)行你的app。換句話(huà)說(shuō),你想使用Android剛剛推出的新特性,但這對(duì)于你的app又不是必須的,你就可以將targetSDK設(shè)置為你想使用新特性的SDK版本,minSDK設(shè)置成低版本保證所有人都可以使用你的app。

  • minSdkVersion與maxSdkVersion比較容易理解,就是在安裝程序的時(shí)候,如果目標(biāo)設(shè)備的API版本小于minSdkVersion, 或者大于maxSdkVersion,程序?qū)o(wú)法安裝。一般來(lái)說(shuō)沒(méi)有必要設(shè)置maxSdkVersion。

  • targetSdkVersion相對(duì)復(fù)雜一些,如果設(shè)置了此屬性,那么在程序執(zhí)行時(shí),如果目標(biāo)設(shè)備的API版本正好等于此數(shù)值, 他會(huì)告訴Android平臺(tái):此程序在此版本已經(jīng)經(jīng)過(guò)充分測(cè),沒(méi)有問(wèn)題。不必為此程序開(kāi)啟兼容性檢查判斷的工作了。 也就是說(shuō),如果targetSdkVersion與目標(biāo)設(shè)備的API版本相同時(shí),運(yùn)行效率可能會(huì)高一些。 但是,這個(gè)設(shè)置僅僅是一個(gè)聲明、一個(gè)通知,不會(huì)有太實(shí)質(zhì)的作用, 比如說(shuō),使用了targetSdkVersion這個(gè)SDK版本中的一個(gè)特性,但是這個(gè)特性在低版本中是不支持的 ,那么在低版本的API設(shè)備上運(yùn)行程序時(shí),可能會(huì)報(bào)錯(cuò):Java.lang.VerifyError。也就是說(shuō),此屬性不會(huì)幫你解決兼容性的測(cè)試問(wèn)題。 你至少需要在minSdkVersion這個(gè)版本上將程序完整的跑一遍來(lái)確定兼容性是沒(méi)有問(wèn)題的。(這個(gè)問(wèn)題確實(shí)讓人頭疼)

  • project.properties中的target是指在編譯的時(shí)候使用哪個(gè)版本的API進(jìn)行編譯。 綜上,上面的四個(gè)值其實(shí)是作用于不同的時(shí)期:
    target API level是在編譯的時(shí)候起作用,用于指定使用哪個(gè)API版本(SDK版本)進(jìn)行編譯。 minSdkVersion和maxSdkVersion是在程序安裝的時(shí)候起作用, 用于指定哪些版本的設(shè)備可以安裝此應(yīng)用。 targetSdkVersion是在程序運(yùn)行的時(shí)候起作用,用于提高指定版本的設(shè)備上程序運(yùn)行體驗(yàn)。

  • 知道注意的是:最好將project.properties中target與AndroidManifest中minSdkVersion的APILevel設(shè)置為同一個(gè)等級(jí),達(dá)到編譯兼容最小版本的效果,如果前者比后者等級(jí)高的話(huà),可能出現(xiàn)的一個(gè)問(wèn)題就是,在開(kāi)發(fā)過(guò)程中,一些高等級(jí)的api能夠通過(guò)編譯,但是當(dāng)實(shí)際運(yùn)行在低系統(tǒng)的真機(jī)或者模擬器上時(shí),將會(huì)出現(xiàn)一些NoSuchMethodError的錯(cuò)誤,導(dǎo)致程序奔潰。例如:
    android.view.view.setBackgroundDrawable(Drawable background)方法在APILeven為16時(shí)廢棄(deprecated)掉了,改用setBackground(Drawable background),開(kāi)發(fā)中如果工程project.properties中的target編譯版本大于或等于16,而在AndroidManifest中設(shè)置的minSdkVersion為小于16,則會(huì)出現(xiàn)能夠通過(guò)開(kāi)發(fā)工具的編譯,但運(yùn)行在4.1系統(tǒng)一下的機(jī)器中時(shí),會(huì)出現(xiàn)NoSuchMethodError的錯(cuò)誤!

  • 當(dāng)你的應(yīng)用targetSdkVersion小于23的時(shí)候,就算你運(yùn)行在Android6.0系統(tǒng)上,它也會(huì)默認(rèn)采用以前的權(quán)限管理機(jī)制,也就是一刀切。當(dāng)你的targetSdkVersion大于等于23的時(shí)候且在Andorid6.0(M)系統(tǒng)上,它才會(huì)采用新的這套權(quán)限管理機(jī)制。
    所以如果你想逃開(kāi)這個(gè)“麻煩”,只要把targetSdkVersion的版本設(shè)置為低于23就可以了,不過(guò)不建議采用這種方案,該來(lái)的總是要來(lái)的,隨著國(guó)產(chǎn)手機(jī)ROM的更新,比如小米,華為,魅族等也開(kāi)始有部分機(jī)型進(jìn)行了系統(tǒng)升級(jí),所以這是種趨勢(shì)。
    說(shuō)了這么多,那么來(lái)看下怎么進(jìn)行Android6.0(M)的權(quán)限管理適配吧,其實(shí)很簡(jiǎn)單,只需要記住下面幾個(gè)API方法就可以:(API23之后提供)
  • int checkSelfPermission(String permission) 用來(lái)檢測(cè)應(yīng)用是否已經(jīng)具有權(quán)限
  • void requestPermissions(String[] permissions, int requestCode) 進(jìn)行請(qǐng)求單個(gè)或多個(gè)權(quán)限
  • void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 請(qǐng)求權(quán)限結(jié)果回調(diào)

參考博文鏈接:http://m.itdecent.cn/p/a37f4827079a

下面來(lái)段代碼示例(為了向下兼容,這里我采用了v4包下的ContextCompat和ActivityCompat):

 View.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //判斷當(dāng)前系統(tǒng)是否高于或等于6.0
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                //當(dāng)前系統(tǒng)大于等于6.0
                if (ContextCompat.checkSelfPermission(MineInforActivity.this,Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
                    //具有拍照權(quán)限,直接調(diào)用相機(jī)
                    //具體調(diào)用代碼
                } else {
                    //不具有拍照權(quán)限,需要進(jìn)行權(quán)限申請(qǐng)
                    ActivityCompat.requestPermissions(MineInforActivity.this,new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CAMERA_CODE);
                }
            } else {
                //當(dāng)前系統(tǒng)小于6.0,直接調(diào)用拍照

            }
        }
    });

如果用戶(hù)勾選了不再提醒,然后把你拒絕了,那你的應(yīng)用就GG了,其實(shí)這里還有一個(gè)API方法:

    if(!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)){
                        //如果用戶(hù)勾選了不再提醒,則返回false
                        //給予用戶(hù)提醒,比如Toast或者對(duì)話(huà)框,讓用戶(hù)去系統(tǒng)設(shè)置-應(yīng)用管理里把相關(guān)權(quán)限打開(kāi)    
                    }

ContextCompat.checkSelfPermission無(wú)效的問(wèn)題

在做項(xiàng)目中發(fā)現(xiàn),我在使用ContextCompat.checkSelfPermission時(shí),無(wú)論如何開(kāi)關(guān)權(quán)限返回值都是PackageManager.PERMISSION_GRANTED,而使用PackageManager.checkPermission()的時(shí)候返回值又始終都是PackageManager.PERMISSION_DENIED;
經(jīng)過(guò)查詢(xún)相關(guān)文檔及博客發(fā)現(xiàn):

**If your application is targeting an API level before 23 (Android M) then both:ContextCompat.CheckSelfPermission and Context.checkSelfPermission doesn't work and always returns 0 (PERMISSION_GRANTED). Even if you run the application on Android 6.0 (API 23).
**
在targetSdkVersion小于23(Android M)的時(shí)候,ContextCompat.CheckSelfPermission 和Context.checkSelfPermission方法都不能正常工作并且始終返0(PERMISSION_GRANTED),即使你的應(yīng)用運(yùn)行在Android6.0(API 23)的設(shè)備上。

解決辦法:

As I said in the 1st point, if you targeting an API level before 23 on Android 6.0 then ContextCompat.CheckSelfPermission and Context.checkSelfPermission doesn't work. Fortunately you can use PermissionChecker.checkSelfPermission to check run-time permissions.
使用permissionChecker.checkSelfPermission,來(lái)檢測(cè)權(quán)限是否被授予。

關(guān)于權(quán)限android.permission.READ_PHONE_STATE和 WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE問(wèn)題

參考博文
以上兩個(gè)權(quán)限對(duì)應(yīng)用運(yùn)行時(shí)影響最大,其中READ_PHONE_STATE用來(lái)獲取deviceID,即IMEI號(hào)碼。這是很多統(tǒng)計(jì)依賴(lài)計(jì)算設(shè)備唯一ID的參考。如果新的權(quán)限導(dǎo)致讀取不到,避免導(dǎo)致統(tǒng)計(jì)的異常。建議在完全支持運(yùn)行時(shí)權(quán)限之前,將對(duì)應(yīng)的值寫(xiě)入到App本地?cái)?shù)據(jù)中,對(duì)于新安裝的,可以采取其他策略減少對(duì)統(tǒng)計(jì)的影響。

WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE這兩個(gè)權(quán)限和外置存儲(chǔ)(即sdcard)有關(guān),對(duì)于下載相關(guān)的應(yīng)用這一點(diǎn)還是比較重要的,我們應(yīng)該盡可能的說(shuō)明和引導(dǎo)用戶(hù)授予該權(quán)限。

關(guān)于權(quán)限android.permission.READ_PHONE_STATE,系統(tǒng)會(huì)彈出一個(gè)對(duì)話(huà)框提醒撤銷(xiāo)的危害,如果用戶(hù)執(zhí)意撤銷(xiāo),會(huì)帶來(lái)如下的反應(yīng)
如果你的程序正在運(yùn)行,則會(huì)被殺掉。
當(dāng)你的應(yīng)用再次運(yùn)行時(shí),可能出現(xiàn)崩潰
為什么會(huì)可能崩潰的,比如下面這段代碼

    TelephonyManager telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
    String deviceId = telephonyManager.getDeviceId();
    if (deviceId.equals(mLastDeviceId)) {//This may cause NPE
      //do something
    }

如果用戶(hù)撤消了獲取DeviceId的權(quán)限,那么再次運(yùn)行時(shí),deviceId就是null,如果程序后續(xù)處理不當(dāng),就會(huì)出現(xiàn)崩潰。
目前我在項(xiàng)目中做了版本控制,當(dāng)版本較低是還是用老的方法,做了向下兼容。

service與動(dòng)態(tài)權(quán)限管理兼容問(wèn)題

requestPermission()can only be called from an Activity and not a Service (unlike checkPermission() that only requires PackageManager). So you need to do some extra work to get around that; you do need to provide an Activity in your app and, for example, your Service can check for permissions it needs and if they have not been granted yet, it can create a notification and that can inform user with a descriptive short message as to why there is a notification and what needs to happen when they click on the notification, etc.

requestPermission()需要用戶(hù)提供Activity,在service里使用存在問(wèn)題,可以在service里面先執(zhí)行checkPermission方法,判斷是否授予權(quán)限,鄙人技術(shù)有限沒(méi)能解決這個(gè)問(wèn)題,不過(guò)有技術(shù)大牛實(shí)現(xiàn)相關(guān)問(wèn)題GitHub傳送門(mén)有興趣的可以下載先來(lái)研究研究源碼,這里我先做下相關(guān)備注以便后面學(xué)習(xí)。

部分手機(jī)兼容存在禁止權(quán)限卻始終返回PERMISSION_GRANTED

這種情況部分手機(jī)解決方案跟上述ContextCompat.checkSelfPermission無(wú)效問(wèn)題類(lèi)似,先設(shè)置targetSdkVersion>=23,再設(shè)置ContextCompat.checkSelfPermission()改為permissionChecker.checkSelfPermission()方法,來(lái)檢測(cè)是否授予權(quán)限。
但是針對(duì)魅族、小米手機(jī),還是存在無(wú)效問(wèn)題,這就需要對(duì)應(yīng)的處理,還去大佬指教……

BaseActivity部分核心代碼

1、先檢測(cè)權(quán)限是否授予

 * 檢測(cè)所有的權(quán)限是否都已授權(quán)
 * 
 * @param permissions
 * @return
 */
private boolean checkPermissions(String[] permissions) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        return true;
    }
    for (String permission : permissions) {
        if (PermissionChecker.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
            return false;
        }
    }
    return true;

}

2、獲取權(quán)限集合中權(quán)限列表

 * 獲取權(quán)限集中需要申請(qǐng)權(quán)限的列表
 * 
 * @param permissions
 * @return
 */
private List<String> getDeniedPermissions(String[] permissions) {
    List<String> needRequestPermissionList = new ArrayList<>();
    for (String permission : permissions) {
        if (PermissionChecker.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED
                || ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
            needRequestPermissionList.add(permission);
        }
    }
    return needRequestPermissionList;
}

3、請(qǐng)求所需權(quán)限

 * 請(qǐng)求權(quán)限
 * 
 * @param permissions
 *            請(qǐng)求的權(quán)限
 * @param requestCode
 *            請(qǐng)求權(quán)限的請(qǐng)求碼
 */
public void requestPermission(String[] permissions, int requestCode) {
    this.REQUEST_CODE_PERMISSION = requestCode;
    if (checkPermissions(permissions)) {
        permissionSuccess(REQUEST_CODE_PERMISSION);
    } else {
        List<String> needPermissions = getDeniedPermissions(permissions);
        ActivityCompat.requestPermissions(this, needPermissions.toArray(new String[needPermissions.size()]),
                REQUEST_CODE_PERMISSION);
    }
}

4、確定所有權(quán)限是否都已授權(quán)

 * 確認(rèn)所有的權(quán)限是否都已授權(quán)
 * 
 * @param grantResults
 * @return
 */
private boolean verifyPermissions(int[] grantResults) {
    for (int grantResult : grantResults) {
        if (grantResult != PackageManager.PERMISSION_GRANTED) {
            return false;
        }
    }
    return true;
}

5、系統(tǒng)請(qǐng)求權(quán)限回調(diào)執(zhí)行對(duì)應(yīng)的操作

 * 系統(tǒng)請(qǐng)求權(quán)限回調(diào)
 * 
 * @param requestCode
 * @param permissions
 * @param grantResults
 */
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
        @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_CODE_PERMISSION) {
        if (verifyPermissions(grantResults)) {
            permissionSuccess(REQUEST_CODE_PERMISSION);
        } else {
            permissionFail(REQUEST_CODE_PERMISSION);
        }
    }
}

6、子類(lèi)繼承實(shí)現(xiàn)方法執(zhí)行相應(yīng)的操作

 * 獲取權(quán)限成功
 * 
 * @param requestCode
 */
public void permissionSuccess(int requestCode) {
    Log.e("TAG", "獲取權(quán)限成功=" + requestCode);

}

/**
 * 權(quán)限獲取失敗
 * 
 * @param requestCode
 */
public void permissionFail(int requestCode) {
    Log.e("TAG", "獲取權(quán)限失敗=" + requestCode);
}

歡迎各位大佬給予寶貴意見(jiàn)……

最后編輯于
?著作權(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)容