天天看點

Android 6.0 運作時權限

1、運作時權限定義

android6.0 運作時權限是指抛棄以前我們在manifest.xml檔案中一次性賦予app權限,轉而在程式真正運作時授予權限(需要使用者同意),如果沒有授予權限,則出現異常的一種機制。

6.0之前的權限模型

  • 在AndroidManifest中聲明可能用到的所有權限
  • 使用者在安裝時,系統展示所有權限,使用者安裝即授予所有權限,取消則拒絕安裝

6.0新的運作時權限

  • 将權限分為一般權限和危險權限兩種,一般權限跟以前一樣在AndroidManifest聲明,危險權限需要開發者在代碼中手動的動态申請
  • 動态申請權限,系統彈出對話框,使用者可點選确定或拒絕,系統提供使用者的選擇回調,進而開發者處理相應的邏輯
  • 使用者可以選擇拒絕并不再提醒
    Android 6.0 運作時權限

2、對比傳統權限聲明和運作時權限請求的差別

傳統權限聲明針對 Android 6.0 及其以下版本使用,Android 6.0對應的API版本23,聲明的方式直接将所有應用程式用到的權限統一在 manifest 清單檔案中定義,使用标簽 ,應用程式點選安裝的過程,羅列清單檔案聲明的所有權限,安裝完成後使用者可以選擇是否授予應用程式某個隐私的權限,Android系統提供:允許、提示和禁止三種選擇,下面看一組示範:

build.gradle 選擇編譯版本、目标版本都是 API 19,運作在Android 4.4.2系統(華為)效果:

Android 6.0 運作時權限

build.gradle 選擇編譯版本、目标版本都是 API 19,運作在Android 6.0.1系統(小米)效果

Android 6.0 運作時權限

build.gradle選擇編譯版本、目标版本都是API 23,運作在Android 4.4.2系統(華為)效果:

Android 6.0 運作時權限

build.gradle選擇編譯版本、目标版本都是API 23,運作在Android 6.0.1系統(小米)效果:

Android 6.0 運作時權限

圖1示範傳統權限授予過程,在完成安裝的過程中可以選擇某個權限是否允許、提示和禁止狀态;

圖2示範低版本應用程式在Android 6.0以上系統安裝過程,預設授予應用程式清單檔案聲明的所有權限,小米手機測試無法修改權限狀态;

圖3和圖4示範API版本23開發的應用程式分别安裝在低版本和高版本系統權限授予過程,安裝在低版本時授予權限過程和傳統的方式一樣,使用者可以修改權限的狀态;安裝到高版本時授予權限過程發生了很大變化,使用者安裝過程無法修改權限狀态,最後運作應用程式的錄音功能,出現閃退、崩潰現象。

3 權限分類

android6.0以後将權限分為兩類,分别是危險權限與普通權限

危險權限

危險權限即需要動态申請的權限,一共9組,取得一組中某一個權限的授權,則自動擁有該組的所有授權

Android 6.0 運作時權限

一般權限

除上面的危險權限之外的權限即為一般權限(普通權限)

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

繼續閱讀