1炕横、問題
運(yùn)行在android 6.0上恩脂,出現(xiàn)了這樣的錯(cuò)誤:
java.lang.SecurityException: getDeviceId: Neither user xxxxx nor current process has Android.permission.READ_PHONE_STATE
在API 23 以上召噩,如果沒有給應(yīng)用加上動(dòng)態(tài)權(quán)限請(qǐng)求儡湾,應(yīng)用不會(huì)獲得任何權(quán)限8廊荨H嘌帧庄撮!
2、解決方案
在應(yīng)用沒有切換到運(yùn)行時(shí)權(quán)限模型Runtime Permissions之前毙籽,以下兩種方式可以保證應(yīng)用不出錯(cuò):
1洞斯、compileSdkVersion 24、buildToolsVersion "24.0.1"坑赡,targetSdkVersion 22烙如,采用的是 API 23以下的權(quán)限管理模型。如果寫成24會(huì)報(bào)錯(cuò)毅否,因?yàn)闆]有權(quán)限亚铁。
2、compileSdkVersion 22螟加、buildToolsVersion "22.0.1"徘溢、targetSdkVersion 22,同時(shí)注意所有l(wèi)ibrary引用的是22這個(gè)版本號(hào)捆探。
在...XXX.../merged/debug/values-v23/values-v23.xml下然爆,會(huì)報(bào)錯(cuò):Error retrieving parent for item
在它引用的library中,引用了版本號(hào)為23的庫(kù),如:
dependencies {
compile 'com.android.support:appcompat-v7:23.1.1'
}
所以,去除 com.hqgj:myvideo:0.3.5的appcompat-v7的模塊
compile ('com.hqgj:myvideo:0.3.5'){
exclude module: 'appcompat-v7'
}
關(guān)于compileSdkVersion黍图、targetSdkVersion
compileSdkVersion
compileSdkVersion 告訴 Gradle 用哪個(gè) Android SDK 版本編譯你的應(yīng)用曾雕。修改 compileSdkVersion 不會(huì)改變運(yùn)行時(shí)的行為。當(dāng)你修改了 compileSdkVersion 的時(shí)候助被,可能會(huì)出現(xiàn)新的編譯警告剖张、編譯錯(cuò)誤,但新的 compileSdkVersion 不會(huì)被包含到 APK 中:它純粹只是在編譯的時(shí)候使用揩环。
targetSdkVersion
targetSdkVersion 是 Android 提供向前兼容的主要依據(jù)修械,在應(yīng)用的 targetSdkVersion 沒有更新之前系統(tǒng)不會(huì)應(yīng)用最新的行為變化。這允許你在適應(yīng)新的行為變化之前就可以使用新的 API (因?yàn)槟阋呀?jīng)更新了 compileSdkVersion 不是嗎检盼?)肯污。
3、Runtime Permissions,動(dòng)態(tài)權(quán)限管理
3蹦渣、動(dòng)態(tài)權(quán)限管理
Runtime Permissions哄芜,即,應(yīng)用在運(yùn)行的時(shí)候柬唯,獲得權(quán)限认臊,而不是在安裝的時(shí)候得到權(quán)限,但是這個(gè)行為锄奢,取決于系統(tǒng)的版本和targetSdkVersion:
- 設(shè)備的版本號(hào)<=android 5.1 或者 targetSdkVersion<=22 失晴,是在安裝的時(shí)候得到權(quán)限
- 如果>=6.0 同時(shí) targetSdkVersion>=23,運(yùn)行的時(shí)候獲得權(quán)限
但是拘央,即便targetSdkVersion<=22涂屁,只要設(shè)備的版本號(hào)>=android 6.0,用戶依然可以撤銷授權(quán)(設(shè)置--應(yīng)用)灰伟。
Google將權(quán)限分為兩類:
Normal Permissions拆又,這類權(quán)限一般不涉及用戶隱私,是不需要用戶進(jìn)行授權(quán)的栏账,比如手機(jī)震動(dòng)帖族、訪問網(wǎng)絡(luò)等:
ACCESS_NETWORK_STATE、ACCESS_WIFI_STATE挡爵、CHANGE_NETWORK_STATE竖般、CHANGE_WIFI_STATE、INTERNET茶鹃、MODIFY_AUDIO_SETTINGS生巡、RECEIVE_BOOT_COMPLETED(開機(jī)廣播)批钠、VIBRATE缆八、WAKE_LOCK
Dangerous Permissions恨溜,一般是涉及到用戶隱私的垃杖,需要用戶進(jìn)行授權(quán)男杈,比如讀取sdcard、訪問通訊錄调俘、電話伶棒、日歷、攝像頭彩库、位置肤无、錄音、SMS等:
WRITE_EXTERNAL_STORAGE骇钦、CAMERA宛渐、READ_PHONE_STATE、ACCESS_COARSE_LOCATION
幾個(gè)方法:
ContextCompat.checkSelfPermission(context,permission)
主要用于檢測(cè)某個(gè)權(quán)限是否已經(jīng)被授予,方法返回值為PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED窥翩。當(dāng)返回DENIED就需要進(jìn)行申請(qǐng)授權(quán)了业岁。ActivityCompat.requestPermissions(context,permissions,PERMISSION_REQUEST_CODE)
請(qǐng)求權(quán)限onRequestPermissionsResult
回調(diào)shouldShowRequestPermissionRationale()
如果用戶拒絕某授權(quán)。下一次彈框寇蚊,用戶會(huì)有一個(gè)“不再提醒”的選項(xiàng)的來防止app以后繼續(xù)請(qǐng)求授權(quán)笔时。
如果這個(gè)選項(xiàng)在拒絕授權(quán)前被用戶勾選了,下次為這個(gè)權(quán)限請(qǐng)求requestPermissions時(shí)仗岸,對(duì)話框就不彈出來了允耿,系統(tǒng)會(huì)直接回調(diào)onRequestPermissionsResult函數(shù),回調(diào)結(jié)果為最后一次用戶的選擇扒怖。所以為了應(yīng)對(duì)這種情況较锡,系統(tǒng)提供了一個(gè)shouldShowRequestPermissionRationale()函數(shù),這個(gè)函數(shù)的作用是幫助開發(fā)者找到需要向用戶額外解釋權(quán)限的情況姚垃,這個(gè)函數(shù):
** 應(yīng)用安裝后第一次訪問念链,直接返回false;第一次請(qǐng)求權(quán)限時(shí)积糯,用戶拒絕了掂墓,下一次shouldShowRequestPermissionRationale()返回 true,這時(shí)候可以顯示一些為什么需要這個(gè)權(quán)限的說明看成;第二次請(qǐng)求權(quán)限時(shí)君编,用戶拒絕了,并選擇了“不再提醒”的選項(xiàng)時(shí):shouldShowRequestPermissionRationale()返回 false川慌;設(shè)備的系統(tǒng)設(shè)置中禁止當(dāng)前應(yīng)用獲取這個(gè)權(quán)限的授權(quán)吃嘿,shouldShowRequestPermissionRationale()返回false **;
注意:第二次請(qǐng)求權(quán)限時(shí)梦重,才會(huì)有“不再提醒”的選項(xiàng)兑燥,如果用戶一直拒絕,并沒有選擇“不再提醒”的選項(xiàng)琴拧,下次請(qǐng)求權(quán)限時(shí)降瞳,會(huì)繼續(xù)有“不再提醒”的選項(xiàng),并且shouldShowRequestPermissionRationale()也會(huì)一直返回true蚓胸。
代碼:
public class PermissionsChecker {
private Context context;
public PermissionsChecker(Context context) {
this.context = context.getApplicationContext();
}
public boolean lacksPermissions(String...permissions){
Log.i("permission","length:"+permissions.length);
for(String permission:permissions){
if(lacksPermission(permission)){
return true;
}
}
return false;
}
private boolean lacksPermission(String permission) {
return ContextCompat.checkSelfPermission(context,permission)== PackageManager.PERMISSION_DENIED;
}
/** * 是否授予權(quán)限 */
public boolean verifyPermissions(int[] grantResults) {
// At least one result must be checked.
if (grantResults.length < 1) {
return false;
}
// Verify that each required permission has been granted, otherwise return false.
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
}
private PermissionsChecker permissionsChecker;
private boolean flag;private String[] permissions=new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.READ_PHONE_STATE};
private static final int PERMISSION_REQUEST_CODE = 1;
private static final int SEETING_PERMISSION_REQUEST_CODE = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
permissionsChecker=new PermissionsChecker(this);
if(savedInstanceState==null){
flag=true;
}else{
flag=savedInstanceState.getBoolean("flag");
}
}
//在權(quán)限被回收之后挣饥,activity會(huì)被重啟
@Override public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
outState.putBoolean("flag", flag);
}
@Override protected void onResume() {
super.onResume();
if(permissionsChecker.lacksPermissions(permissions)){
requestMainPermissions();
}
}
private void requestMainPermissions() {
if(flag){
flag=false;
ActivityCompat.requestPermissions(MainActivity.this, permissions, PERMISSION_REQUEST_CODE);
}
}
@Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if(requestCode==PERMISSION_REQUEST_CODE && permissionsChecker.verifyPermissions(grantResults)){
}
else {
AlertDialog.Builder builder=new AlertDialog.Builder(this);
builder.setCancelable(false);
builder.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialogInterface, int i, KeyEvent keyEvent) {
if (keyEvent.getAction() == KeyEvent.KEYCODE_BACK) {
finish();
}
return false;
}
}
);
builder.setTitle("提示");
builder.setMessage("缺少必要權(quán)限");
builder.setNegativeButton("退出", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
finish();
}
});
builder.setPositiveButton("設(shè)置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, SEETING_PERMISSION_REQUEST_CODE);
}
});
builder.show();
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode==SEETING_PERMISSION_REQUEST_CODE){
if(permissionsChecker.lacksPermissions(permissions)){
finish();
}
}
}
代碼:
https://github.com/my-sunshine/MyPermission
參考:
如何選擇 compileSdkVersion, minSdkVersion 和 targetSdkVersion
Android 6.0: 動(dòng)態(tài)權(quán)限管理的解決方案
http://www.2cto.com/kf/201512/455888.html