1、運作時權限定義
android6.0 運作時權限是指抛棄以前我們在manifest.xml檔案中一次性賦予app權限,轉而在程式真正運作時授予權限(需要使用者同意),如果沒有授予權限,則出現異常的一種機制。
6.0之前的權限模型
- 在AndroidManifest中聲明可能用到的所有權限
- 使用者在安裝時,系統展示所有權限,使用者安裝即授予所有權限,取消則拒絕安裝
6.0新的運作時權限
- 将權限分為一般權限和危險權限兩種,一般權限跟以前一樣在AndroidManifest聲明,危險權限需要開發者在代碼中手動的動态申請
- 動态申請權限,系統彈出對話框,使用者可點選确定或拒絕,系統提供使用者的選擇回調,進而開發者處理相應的邏輯
- 使用者可以選擇拒絕并不再提醒
2、對比傳統權限聲明和運作時權限請求的差別
傳統權限聲明針對 Android 6.0 及其以下版本使用,Android 6.0對應的API版本23,聲明的方式直接将所有應用程式用到的權限統一在 manifest 清單檔案中定義,使用标簽 ,應用程式點選安裝的過程,羅列清單檔案聲明的所有權限,安裝完成後使用者可以選擇是否授予應用程式某個隐私的權限,Android系統提供:允許、提示和禁止三種選擇,下面看一組示範:
build.gradle 選擇編譯版本、目标版本都是 API 19,運作在Android 4.4.2系統(華為)效果:
build.gradle 選擇編譯版本、目标版本都是 API 19,運作在Android 6.0.1系統(小米)效果
build.gradle選擇編譯版本、目标版本都是API 23,運作在Android 4.4.2系統(華為)效果:
build.gradle選擇編譯版本、目标版本都是API 23,運作在Android 6.0.1系統(小米)效果:
圖1示範傳統權限授予過程,在完成安裝的過程中可以選擇某個權限是否允許、提示和禁止狀态;
圖2示範低版本應用程式在Android 6.0以上系統安裝過程,預設授予應用程式清單檔案聲明的所有權限,小米手機測試無法修改權限狀态;
圖3和圖4示範API版本23開發的應用程式分别安裝在低版本和高版本系統權限授予過程,安裝在低版本時授予權限過程和傳統的方式一樣,使用者可以修改權限的狀态;安裝到高版本時授予權限過程發生了很大變化,使用者安裝過程無法修改權限狀态,最後運作應用程式的錄音功能,出現閃退、崩潰現象。
3 權限分類
android6.0以後将權限分為兩類,分别是危險權限與普通權限
危險權限
危險權限即需要動态申請的權限,一共9組,取得一組中某一個權限的授權,則自動擁有該組的所有授權
一般權限
除上面的危險權限之外的權限即為一般權限(普通權限)
android6.0 運作時權限機制
當編譯版本、目标版本都是 API 23及其以上時,安裝在Android 6.0系統以上的手機預設授予所有普通權限,不授予危險權限,而危險權限隻在程式将要使用這個功能時才進行授予(将會彈出對話框提示使用者,使用者同意,使用者已同意則直接授予)。如果這時沒有授予響應權限,系統将會抛出異常。
4 相關API及運作時權限例子
運作時權限主要的API就是以下幾個
ContextCompat
//檢查是否授予某個權限
static int checkSelfPermission(Context context, String permission)
主要用于檢測某個權限是否已經被授予,方法傳回值為PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED。當傳回DENIED就需要進行申請授權了
ActivityCompat
//申請授權
static void requestPermissions(Activity activity, String[] permissions, int requestCode)
該方法是異步的,第一個參數是Context;第二個參數是需要申請的權限的字元串數組;第三個參數為requestCode,主要用于回調的時候檢測。可以從方法名requestPermissions以及第二個參數看出,是支援一次性申請多個權限的,系統會通過對話框逐一詢問使用者是否授權。
Activity
//處理權限申請回調
public void onRequestPermissionsResult(int requestCode,String permissions[], int[] grantResults)
對于權限的申請結果,首先驗證requestCode定位到你的申請,然後驗證grantResults對應于申請的結果,這裡的數組對應于申請時的第二個權限字元串數組。如果你同時申請兩個權限,那麼grantResults的length就為2,分别記錄你兩個權限的申請結果。如果申請成功,就可以做你的事情了~
static boolean shouldShowRequestPermissionRationale(Activity activity, String permission)
這個API主要用于給使用者一個申請權限的解釋,該方法隻有在使用者在上一次已經拒絕過你的這個權限申請。也就是說,使用者已經拒絕一次了,你又彈個授權框,你需要給使用者一個解釋,為什麼要授權,則使用該方法。
一般來說:視訊運作時權限步驟如下:
1 在AndroidManifest檔案中添加需要的權限
2 在代碼中判斷是否授予某個權限
3 沒有授予權限進行權限的申請
4 對申請權限使用者處理後的結果進行處理
例子:
一個打電話的小例子
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity
{
private static final int MY_PERMISSIONS_REQUEST_CALL_PHONE = ;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void testCall(View view)
{
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CALL_PHONE},
MY_PERMISSIONS_REQUEST_CALL_PHONE);
} else
{
callPhone();
}
}
public void callPhone()
{
Intent intent = new Intent(Intent.ACTION_CALL);
Uri data = Uri.parse("tel:" + "10086");
intent.setData(data);
startActivity(intent);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
{
if (requestCode == MY_PERMISSIONS_REQUEST_CALL_PHONE)
{
if (grantResults[] == PackageManager.PERMISSION_GRANTED)
{
callPhone();
} else
{
// Permission Denied
Toast.makeText(MainActivity.this, "Permission Denied", Toast.LENGTH_SHORT).show();
}
return;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
在Android 6.x上運作是,點選testCall,即會彈出授權視窗,如何你Allow則直接撥打電話,如果Denied則Toast彈出”Permission Denied”。
例子很簡單,不過需要注意的是,對于Intent這種方式,很多情況下是不需要授權的甚至權限都不需要的,比如:你是到撥号界面而不是直接撥打電話,就不需要去申請權限;打開系統圖庫去選擇照片;調用系統相機app去牌照等
5 運作時權限封裝
一般來說我們申請權限的代碼如下:
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.XXXX)
!= PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.XXXX},
MY_PERMISSIONS_REQUEST_CODE_XX);
} else
{
xxx();
}
雖然權限處理并不複雜,但是需要編寫很多重複的代碼,是以目前也有很多庫對用法進行了封裝,大家可以在github首頁搜尋:android permission,對比了幾個庫的使用方式,發現https://github.com/lovedise/PermissionGen這個庫還不錯,很多大神也在推薦
對于申請權限,我們需要3個參數:Activity|Fragment、權限字元串數組、int型申請碼。
也就是說,我們隻需要寫個方法,接受這幾個參數即可,然後邏輯是統一的。
public static void needPermission(Fragment fragment, int requestCode, String[] permissions)
{
requestPermissions(fragment, requestCode, permissions);
}
@TargetApi(value = Build.VERSION_CODES.M)
private static void requestPermissions(Object object, int requestCode, String[] permissions)
{
if (!Utils.isOverMarshmallow())
{
doExecuteSuccess(object, requestCode);
return;
}
List<String> deniedPermissions = Utils.findDeniedPermissions(getActivity(object), permissions);
if (deniedPermissions.size() > )
{
if (object instanceof Activity)
{
((Activity) object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
} else if (object instanceof Fragment)
{
((Fragment) object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
} else
{
throw new IllegalArgumentException(object.getClass().getName() + " is not supported");
}
} else
{
doExecuteSuccess(object, requestCode);
}
}
Utils.findDeniedPermissions其實就是check沒有授權的權限。
#Utils
@TargetApi(value = Build.VERSION_CODES.M)
public static List<String> findDeniedPermissions(Activity activity, String... permission)
{
List<String> denyPermissions = new ArrayList<>();
for (String value : permission)
{
if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED)
{
denyPermissions.add(value);
}
}
return denyPermissions;
}
那麼上述的邏輯就很清晰了,需要的3種參數傳入,先去除已經申請的權限,然後開始申請權限。
ok,我相信上面代碼,大家掃一眼就可以了解了。
處理權限回調
對于回調,因為要根據是否授權去執行不同的事情,是以很多架構也需要些一連串的代碼,或者和前面的申請代碼耦合。不過這個架構還是比較友善的,也是我選擇它來講解的原因。
回調主要做的事情:
了解是否授權成功。
根據授權情況進行回調。
對于第一條邏輯都一樣;對于第二條,因為涉及到兩個分支,每個分支執行不同的方法。
對于第二條,很多架構選擇将兩個分支的方法在申請權限的時候進行注冊,然後在回調中根據requestCode進行比對執行,不過這樣需要在針對每次申請進行對象管理。
不過這個架構采取了一種很有意思的做法,它利用注解去确定需要執行的方法,存在兩個注解:
@PermissionSuccess(requestCode = )
@PermissionFail(requestCode = )
利用反射根據授權情況+requestCode即可找到注解标注的方法,然後直接執行即可。
大緻的代碼為:
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
PermissionGen.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
private static void requestResult(Object obj, int requestCode, String[] permissions,
int[] grantResults)
{
List<String> deniedPermissions = new ArrayList<>();
for (int i = ; i < grantResults.length; i++)
{
if (grantResults[i] != PackageManager.PERMISSION_GRANTED)
{
deniedPermissions.add(permissions[i]);
}
}
if (deniedPermissions.size() > )
{
doExecuteFail(obj, requestCode);
} else
{
doExecuteSuccess(obj, requestCode);
}
}
首先根據grantResults進行判斷成功還是失敗,對于成功則:
private static void doExecuteSuccess(Object activity, int requestCode)
{
Method executeMethod = Utils.findMethodWithRequestCode(activity.getClass(),
PermissionSuccess.class, requestCode);
executeMethod(activity, executeMethod);
}
#Utils
public static <A extends Annotation> Method findMethodWithRequestCode(Class clazz, Class<A> annotation, int requestCode)
{
for (Method method : clazz.getDeclaredMethods())
{
if (method.isAnnotationPresent(annotation))
{
if (isEqualRequestCodeFromAnntation(method, annotation, requestCode))
{
return method;
}
}
}
return null;
}
根據注解和requestCode找到方法,然後反射執行即可。失敗的邏輯類似,不貼代碼了。
ok,到此我們的運作時權限相對于早起版本的變化、特點、以及如何處理和封裝都介紹完了。
不過這個庫有個缺點,就是運作時注解,對性能有影響
參考:
http://blog.csdn.net/lmj623565791/article/details/50709663
http://www.jianshu.com/p/d4a9855e92d3
https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650238900&idx=1&sn=6ec8ef71058d5bf81e8fc03a0a4af1d5&chksm=88639edbbf1417cd66d93c549c5537e32e54cf2e068eb6bc8cd0476cd8873c865bf245abe208&mpshare=1&scene=23&srcid=0501hrGGRxXdPlcZQOeUR60i#rd