編輯:關於Android編程
Android是一個權限分離的操作系統,每個應用使用不同的系統身份運行(Linux用戶ID和群組ID)。系統的不同部分也被分隔進不同的身份域。Linux以此來實現不同應用與其他應用和系統的獨立。
更細粒度的安全特征由“權限”機制提供,該機制在一個進程能夠執行的特定操作上強制施加了限制,並且每一個URI權限能夠授予對特定數據的臨時訪問。
Android安全框架的一個中心設計點是,默認情況下,沒有應用具有執行任何不利地影響其他應用,操作系統或用戶的操作的權限。這些操作包括,讀寫用戶私有數據,讀寫其他應用程序文件,執行網絡訪問,保持設備喚醒等等。
因為每一個Android程序運行在進程沙盒裡,所以應用間必須嚴格地分享資源和數據。它們能夠通過聲明它們需要的權限來增加基礎沙盒沒有提供的能力。應用聲明並請求它們需要的權限,Android系統提示用戶以獲得用戶贊同。
應用程序沙盒不依賴用來構建應用的技術。實際上,Dalvik VM不是一個安全的邊界,任何應用都能運行本地代碼。所有類型的應用-Java,native,hybrid-是使用相同的方式被沙盒封裝,他們有相同的安全等級。
所有的apk必須使用證書簽名,該證書的私有密鑰由開發者持有。該證書標識了應用的作者。該證書不需要被一個證書頒發機構簽名。並且對於Android應用使用自簽名證書,是非常被允許和支持的。在Android中證書的作用是用來區分應用的作者。
在安裝時,Android會給每一個安裝包一個不同的Linux用戶ID。這個身份在該應用在設備上的期間是恆定不變的。在不同的設備上,相同的安裝包可有有不同的UID。
安全限制發生在進程級別。由於不同應用需要運行作為不同的Linux用戶,所以兩個不同安裝包的代碼通常不能運行在相同的進程。但你可以在AndroidManifest.xml的manifest
標簽中使用sharedUserId
屬性來讓不同的包分配相同的用戶ID。通過這樣做,兩個包是被作為相同的應用,有相同的用戶ID和文件權限。注意為了確保安全,僅兩個使用相同簽名,且請求相同sharedUserId
的應用會被分配相同的用戶ID。
一個應用存儲的任何數據都會被分配應用的用戶ID,並且正常情況下不能被其他的包訪問。當使用getSharedPreferences(String, int)
,openFileOutput(String, int)
或openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)
等方法創建新文件時,你能夠使用MODE_WORLD_READABLE
或MODE_WORLD_WRITEABLE
標簽來允許其他應用讀寫該文件。當設置這些標簽時,該文件仍然被你的應用所擁有,但它的全局讀寫權限已經被適當地設置,所以其它應用可以看見它。
默認情況下,一個基本的Android應用沒有被分配任何權限,這意味著它不能做任何不利地影響用戶體驗和數據的操作。為了使用設備上受保護的特征,你必須在AndroidManifest.xml中使用一個或多個
標簽。
例如,一個應用需要接收SMS信息,則這樣定義:
...
如果你的應用聲明了“正常權限”(不會對用戶隱私或設備造成影響的操作權限),系統會自動授予這些權限。如果你的應用聲明了“危險權限”(會對用戶隱私或設備造成影響的操作權限),系統會詢問用戶嚴格地授予這些權限。請求權限的表現形式具體依賴於設備系統版本和應用的目標系統版本:
如果你的設備運行在Android6.0(API級別23)以上,並且你的應用的targetSdkVersion是23或更高,則該應用在運行時向用戶請求權限。用戶可以在任何時候取消這個權限,所以應用需要在每次運行時檢測是否已有該權限。 如果你的設備運行在Android5.1(API級別22)一下,會你的應用的targetSdkVersion是22或更低,則系統在安裝應用時,詢問用戶授予該權限。如果你在更新的版本中增加了一個新權限,則系統在升級應用時,詢問用戶授予該權限。一旦用戶安裝了該應用,唯一的取消該權限的方式就是卸載應用。通常一個權限失敗會導致拋出一個SecurityException
。然而,這不是絕對的。例如,sendBroadcast(Intent)
方法在數據被傳遞到每一個接收器時檢查權限,當方法調用返回後,如果權限失敗,不會收到異常。在幾乎所有情況下,一個權限失敗會打印一條log。
Android系統提供的所有權限可以在Manifest.permission
中找到。應用也可以定義自己的權限。
通常特定的權限被施加在你的應用的有數的幾個位置上:
隨著時間的推移,新的限制會被增加到平台,為了使用這些APIs,你的應用必須請求它以前不需要的權限。如果已發布的應用訪問了這些API會怎麼呢?Android會讓其自由的訪問,並在新平台版本上將請求的新權限授予應用,以防止已發布的應用掛掉。Android通過targetSdkVersion屬性提供的值來判斷是否需要請求新權限。如果這個值低於新權限被增加的版本,則Android默認授予該權限。
例如,WRITE_EXTERNAL_STORAGE權限是在API級別4增加的用來限制訪問共享存儲空間的權限。如果你的targetSdkVersion設置的值小於等於3,則該權限在新Android版本上默認被增加到你的應用。
注意:被自動增加到App的權限,會被列在Google Play權限列表中,盡管你的應用不需要請求他們。
為了避免這些並移除你不需要的默認權限,一直盡可能高的更新你的targetSdkVersion。你可以在Build.VERSION_CODES
文檔中找到某個權限是在哪個版本中新增的。
系統權限被分為幾個不同的保護級別。兩個最重要的保護級別是正常”和“危險”權限:
“正常權限”覆蓋了你的應用需要訪問的數據或資源是應用沙盒外的所有區域,在這些區域對用戶隱私或其他應用程序的操作所造成的風險是非常小的。例如,設置時間地區的權限是一個 “正常”權限。如果應用聲明了它需要一個“正常”權限,則系統自動授予應用該權限。 “危險權限”覆蓋了所有你的應用想要的數據或資源包含用戶的隱私信息,或可能潛在地影響用戶存儲的數據或其他應用的操作的區域。例如,讀取用戶的聯系人的權限就是一個危險的權限。如果應用聲明了它想要一個“危險”權限,則用戶必須嚴格授予該權限給應用。所有“危險權限”屬於權限組。如果設備正運行在Android6.0(API級別23)之上,並且該應用的targetSdkVersion是23或更高,則當應用請求一個“危險權限”時,下面的系統行為出現。
如果應用請求了一個“危險權限”,且應用當前沒有該權限所在權限組的權限,則系統展示一個對話框去向用戶描述該應用想要訪問的權限組。這個對話框不能描述該權限組中的具體權限。例如,如果應用僅請求了READ_CONTACTS權限,則系統對話框僅會展示該應用需要訪問設備的聯系人。如果用戶授予了該權限,則系統僅會授予該應用請求的權限,同一權限組中的其他權限還需要再去請求。 如果應用請求了一個“危險權限”,並且該應用已經有了該權限組中的另一個“危險權限”,則系統會立刻授予該權限,而不會與用戶有任何的交互。例如,如果應用已經請求並授予了READ_CONTACTS權限,則當它再請求WRITE_CONTACTS權限時,系統會立刻授予該權限。注意:任何權限都屬於一個權限組,包括“正常權限”和用戶自定義的權限。只是“危險權限”的權限組會影響用戶體驗,所以我們特殊對待。
為了施行自己的權限,你首先需要在AndroidManifest.xml中使用
標簽定義權限。
例如,應用想要控制誰能啟動一個Activity,則可以像下面這樣定義權限:
...
. . .
. . .
protectionLevel
:指定了該權限的安全級別,它告訴系統當應用請求該權限時怎樣通知用戶,或誰被允許持有該權限。 permissionGroup
:其僅幫助系統向用戶顯示權限。通常你設置其為一個標准系統權限組(android.Manifest.permission_group
中列舉)或自定義的權限組。 label
:被用來展示給用戶當展示一個權限列表時,要盡量簡短 description
:具體描述一個權限
可以在手機“設置”->“應用程序”中選擇具體的應用來查看其需要的相關權限,也可以使用adb shell pm list permissions
命令查看權限:
$ adb shell pm list permissions -s
All Permissions:
Network communication: view Wi-Fi state, create Bluetooth connections, full
Internet access, view network state
Your location: access extra location provider commands, fine (GPS) location,
mock location sources for testing, coarse (network-based) location
Services that cost you money: send SMS messages, directly call phone numbers
...
從Android6.0(API級別23)開始,用戶在應用運行時授予權限。這種方式簡化了應用的安裝過程,因為用戶不再需要在安裝或升級應用時授予權限。這也給了用戶在使用應用過程中的更多控制。例如,一個用戶能夠選擇給一個相機應用訪問相機的權限而不給獲取設備位置的權限。用戶能夠隨時在應用設置界面取消授予的權限。
如果你的應用需要一個“危險權限”,則在每次執行請求這個權限的操作時,你必須檢測你是否已經被授予該權限。因為用戶可以隨時取消授權。
使用ContextCompat.checkSelfPermission()
方法來檢測你是否被授予一個權限。例如,下面的代碼段展示了怎樣檢測一個Activity是否已被授予寫日歷的權限:
// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR);
如果應用具有該權限,則該方法返回PackageManager.PERMISSION_GRANTED
,則該應用可以執行該操作。如果該應用不具有該權限,則該方法返回PERMISSION_DENIED
,則應用不得不嚴格地向用戶請求該權限。
如果你的應用需要一個“危險權限”,它必須詢問用戶授予該權限。Android提供了幾種用來請求權限的方法。調用這些方法調起一個標准的對話框,它不能被訂制。
有些情況下,你可能想幫用戶理解為什麼你的應用需要一個權限。例如,如果啟動一個拍照應用,則用戶不會驚訝於應用請求使用相機的權限,但用戶不能理解應用為什麼想要訪問用戶的位置和聯系人。在你請求一個權限之前,你應該考慮向用戶做出一個解釋。注意不要讓解釋影響了用戶。如果你提供了太多的解釋,用戶可能會產生厭煩,卸載了你的應用。
一種你能夠使用的方法是,僅當用戶已經拒絕了權限請求時提供解釋。如果用戶持續嘗試使用一個請求權限的功能,但卻保持拒絕權限請求,則這可能表明用戶不理解為什麼應用需要該權限來提供這個功能。像這種情況,可能給出一個解釋是個好的實踐。
為了幫助找出用戶可能需要一個解釋的情況,Android提供了一個實用的方法shouldShowRequestPermissionRationale()
。如果應用之前請求過這個權限並且用戶拒絕了該請求,則該方法返回true。
注意:如果用戶在過去拒絕了這個權限請求並在該權限的請求對話框中選擇了“不再詢問”選項,則該方法返回false。如果設備策略禁止該應用有該權限則該方法也返回false。
如果你的應用還沒有它需要的權限,則該應用必須調用requestPermissions()
方法來請求該權限。該方法需要傳遞你的應用需要的權限和標識該次權限請求的一個整形請求碼。該方法的作用是異步的,當用戶響應該對話框後,系統調用回調方法返回授權結果,結果中會包含請求權限時傳入的相同請求碼。
下面的代碼檢測應用是否具有讀用戶聯系人的權限並在需要時請求該權限:
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}
當你的應用請求權限時,系統呈現一個對話框給用戶。當用戶響應時,系統觸發onRequestPermissionsResult()
方法,給出響應結果。下面給出了請求READ_CONTACTS權限時的響應回調方法:
Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
注意:你的應用仍需要嚴格地請求每一個它需要的權限,盡管用戶已經授予了相同權限組的其他權限。因為權限組的分組可能會在將來的某個Android版本更改。你的代碼不應該依賴該權限是不是在相同權限組的假設。
當系統詢問用戶授予權限時,用戶有一個操作選項用來告訴系統需要再次詢問授予該權限。在這種情況下,每次使用requestPermissions()
方法請求權限,系統都會立即拒絕請求,而不再彈出授權對話框。
Android API中處理運行時權限的方法checkSelfPermission()
,shouldShowRequestPermissionRationale()
,requestPermissions()
等都是在Android6.0版本增加的方法,所以要使用這些方法必須要至少API級別為23,為了調用方法的兼容性可以通過如下檢查運行版本的方式處理:
if (Build.VERSION.SDK_INT >= 23) {
// Marshmallow+
} else {
// Pre-Marshmallow
}
這種方法雖然可以解決兼容性問題,但太過復雜,幸運的是,v4兼容庫已經提供了相應的兼容方法。
ContextCompat.checkSelfPermission()
PERMISSION_GRANTED
,否則返回PERMISSION_DENIED
,在所有版本都是如此。 ActivityCompat.requestPermissions()
OnRequestPermissionsResult
回調方法直接被調用,帶著正確的PERMISSION_GRANTED
或者PERMISSION_DENIED
。 ActivityCompat.shouldShowRequestPermissionRationale()
後兩種方法,在Fragment中也有相應方法,使用v13兼容包的FragmentCompat.requestPermissions()
和FragmentCompat.shouldShowRequestPermissionRationale()
方法。
以拍照功能為例,我們示例請求拍照權限。
在AndroidManifest.xml中聲明權限
在SingleNormalActivity.java中動態請求權限
/**
* 單個權限請求處理
* Created by sunxiaodong on 16/4/26.
*/
public class SingleNormalActivity extends AppCompatActivity implements View.OnClickListener {
private static final int CAMERA_REQUEST_CODE = 1;
private static final int PERMISSION_REQUEST_CODE = 2;
private Button mGoCamera;
private ImageView mPhotoImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera_activity);
initView();
}
private void initView() {
mGoCamera = (Button) findViewById(R.id.go_camera);
mGoCamera.setOnClickListener(this);
mPhotoImageView = (ImageView) findViewById(R.id.photo_imageview);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.go_camera:
getCameraPermission();
break;
}
}
private void getCameraPermission() {
int hasCameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
if (hasCameraPermission != PackageManager.PERMISSION_GRANTED) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
showRationaleDialog(getResources().getString(R.string.permission_camera_rationale),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(SingleNormalActivity.this,
new String[]{Manifest.permission.CAMERA},
PERMISSION_REQUEST_CODE);
}
});
return;
}
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
PERMISSION_REQUEST_CODE);
return;
}
camera();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_CODE:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
camera();
} else {
Toast.makeText(this, getResources().getString(R.string.permission_camera_denied), Toast.LENGTH_SHORT).show();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
private void camera() {
Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(openCameraIntent, CAMERA_REQUEST_CODE);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case CAMERA_REQUEST_CODE:
Bitmap bm = (Bitmap) data.getExtras().get("data");
mPhotoImageView.setImageBitmap(bm);
break;
default:
break;
}
}
}
private void showRationaleDialog(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", null)
.create()
.show();
}
}
下面給出授權過程的一個執行路徑
首次點擊拍照按鈕,彈出自定義授權解釋對話框:
點擊授權解釋對話框的“允許”選項,彈出系統的授權請求對話框:
點擊授權請求對話框的“拒絕”選項後,再次點擊拍照按鈕,彈出有“不再詢問”選項的授權請求對話框:
選擇“不再詢問”選項,點擊授權請求對話框的“拒絕”選項後,再次點擊拍照按鈕,彈出自定義授權解釋對話框:
點擊授權解釋對話框的“允許”選項,不再有任何反應。
分析程序中可知,在運行時權限下,如果想要使用拍照功能,就必須在使用相機功能前,主動請求相機權限,程序中,我們首先調用ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
方法獲取應用是否已經被授予相機權限,如果已被授予該權限,則可直接使用相機功能,否則通過調用ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)
來判斷用戶是否拒絕過該權限的授予或是否用戶已經選擇了“不再詢問”選項予以拒絕授予該權限,如果用戶已經選擇了“不再詢問”選項予以拒絕授予該權限,則系統不再彈出權限請求提示框,所以我們自己彈出需要相機權限的解釋,如果僅是簡單地被拒絕了授予權限,則再次調用ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CODE);
方法請求權限。請求權限後,用戶響應授權提示的結果,通過onRequestPermissionsResult()
回調方法返回,接下來根據用戶授權情況,做出相應處理。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoNSBpZD0="一次請求多個權限">一次請求多個權限
接下來,在請求拍照功能的同時,一起請求聯系人權限。
在AndroidManifest.xml中聲明權限
在MultiNormalActivity.java中動態請求權限
/**
* 常規的一次請求多個權限處理方式
* Created by sunxiaodong on 16/4/26.
*/
public class MultiNormalActivity extends AppCompatActivity implements View.OnClickListener {
private static final int CAMERA_REQUEST_CODE = 1;
private static final int PERMISSION_REQUEST_CODE = 2;
private Button mGoCamera;
private ImageView mPhotoImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera_activity);
initView();
}
private void initView() {
mGoCamera = (Button) findViewById(R.id.go_camera);
mGoCamera.setOnClickListener(this);
mPhotoImageView = (ImageView) findViewById(R.id.photo_imageview);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.go_camera:
getCameraAndContactsPermission();
break;
}
}
private boolean addPermission(List permissionsList, String permission) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
permissionsList.add(permission);
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permission))
return false;
}
return true;
}
private void getCameraAndContactsPermission() {
List permissionsNeeded = new ArrayList();
final List permissionsList = new ArrayList();
if (!addPermission(permissionsList, Manifest.permission.CAMERA)) {
permissionsNeeded.add("相機");
}
if (!addPermission(permissionsList, Manifest.permission.READ_CONTACTS)) {
permissionsNeeded.add("讀聯系人");
}
if (!addPermission(permissionsList, Manifest.permission.WRITE_CONTACTS)) {
permissionsNeeded.add("寫聯系人");
}
if (permissionsList.size() > 0) {
if (permissionsNeeded.size() > 0) {
String message = getResources().getString(R.string.permission_rationale, permissionsNeeded.get(0));
for (int i = 1; i < permissionsNeeded.size(); i++)
message = message + ", " + permissionsNeeded.get(i);
showRationaleDialog(message,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MultiNormalActivity.this, permissionsList.toArray(new String[permissionsList.size()]),
PERMISSION_REQUEST_CODE);
}
});
return;
}
ActivityCompat.requestPermissions(this, permissionsList.toArray(new String[permissionsList.size()]),
PERMISSION_REQUEST_CODE);
return;
}
camera();
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_CODE: {
Map perms = new HashMap();
perms.put(Manifest.permission.CAMERA, PackageManager.PERMISSION_GRANTED);
perms.put(Manifest.permission.READ_CONTACTS, PackageManager.PERMISSION_GRANTED);
perms.put(Manifest.permission.WRITE_CONTACTS, PackageManager.PERMISSION_GRANTED);
for (int i = 0; i < permissions.length; i++)
perms.put(permissions[i], grantResults[i]);
if (perms.get(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
&& perms.get(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED
&& perms.get(Manifest.permission.WRITE_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
camera();
} else {
Toast.makeText(this, getResources().getString(R.string.permission_denied), Toast.LENGTH_SHORT).show();
}
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
private void camera() {
Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(openCameraIntent, CAMERA_REQUEST_CODE);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case CAMERA_REQUEST_CODE:
Bitmap bm = (Bitmap) data.getExtras().get("data");
mPhotoImageView.setImageBitmap(bm);
break;
default:
break;
}
}
}
private void showRationaleDialog(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", null)
.create()
.show();
}
}
從程序中可知,一次請求多個權限的過程同一次請求一個權限的過程,所以不再對其進行分析,讀者可下載示例源碼自行查看結果。
使用Android原生API請求權限有些復雜,所以出現了很多第三方庫來簡化過程,PermissionDispatcher
是其中一個比較好用的庫,接下來對使用其進行權限請求進行介紹。
繼續以拍照為例。
在AndroidManifest.xml中聲明權限
在SinglePermissionDispatcherActivity.java中動態請求權限
/**
* 使用PermissionDispatcher處理一次單個權限請求
* Created by sunxiaodong on 16/4/26.
*/
@RuntimePermissions
public class SinglePermissionDispatcherActivity extends AppCompatActivity implements View.OnClickListener {
private static final int CAMERA_REQUEST_CODE = 1;
private Button mGoCamera;
private ImageView mPhotoImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera_activity);
initView();
}
private void initView() {
mGoCamera = (Button) findViewById(R.id.go_camera);
mGoCamera.setOnClickListener(this);
mPhotoImageView = (ImageView) findViewById(R.id.photo_imageview);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.go_camera:
SinglePermissionDispatcherActivityPermissionsDispatcher.cameraWithCheck(this);
break;
}
}
@NeedsPermission(Manifest.permission.CAMERA)
public void camera() {
Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(openCameraIntent, CAMERA_REQUEST_CODE);
}
@OnShowRationale(Manifest.permission.CAMERA)
void showRationaleForCamera(PermissionRequest request) {
showRationaleDialog(R.string.permission_camera_rationale, request);
}
@OnPermissionDenied(Manifest.permission.CAMERA)
void onCameraDenied() {
Toast.makeText(this, R.string.permission_camera_denied, Toast.LENGTH_SHORT).show();
}
@OnNeverAskAgain(Manifest.permission.CAMERA)
void onCameraNeverAskAgain() {
Toast.makeText(this, R.string.permission_camera_never_askagain, Toast.LENGTH_SHORT).show();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
SinglePermissionDispatcherActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case CAMERA_REQUEST_CODE:
Bitmap bm = (Bitmap) data.getExtras().get("data");
mPhotoImageView.setImageBitmap(bm);
break;
default:
break;
}
}
}
private void showRationaleDialog(@StringRes int messageResId, final PermissionRequest request) {
new AlertDialog.Builder(this)
.setPositiveButton(R.string.button_allow, new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.proceed();
}
})
.setNegativeButton(R.string.button_deny, new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.cancel();
}
})
.setCancelable(false)
.setMessage(messageResId)
.show();
}
}
下面給出授權過程的一個執行路徑
首次點擊拍照按鈕,彈出系統的授權請求對話框:
點擊授權請求對話框的“拒絕”選項後,再次點擊拍照按鈕,彈出自定義授權解釋對話框:
點擊自定義授權解釋對話框的“允許”選項後,彈出有“不再詢問”選項的授權請求對話框:
選擇“不再詢問”選項,點擊授權請求對話框的“拒絕”選項後,再次點擊拍照按鈕,彈出授權不再詢問的Toast提示:
在AndroidManifest.xml中聲明權限
在MultiPermissionDispatcherActivity.java中動態請求權限
/**
* 使用PermissionDispatcher處理一次多個權限請求
* Created by sunxiaodong on 16/4/26.
*/
@RuntimePermissions
public class MultiPermissionDispatcherActivity extends AppCompatActivity implements View.OnClickListener {
private static final int CAMERA_REQUEST_CODE = 1;
private Button mGoCamera;
private ImageView mPhotoImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera_activity);
initView();
}
private void initView() {
mGoCamera = (Button) findViewById(R.id.go_camera);
mGoCamera.setOnClickListener(this);
mPhotoImageView = (ImageView) findViewById(R.id.photo_imageview);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.go_camera:
MultiPermissionDispatcherActivityPermissionsDispatcher.cameraWithCheck(this);
break;
}
}
@NeedsPermission({Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public void camera() {
Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(openCameraIntent, CAMERA_REQUEST_CODE);
}
@OnShowRationale({Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
void showRationaleForCamera(PermissionRequest request) {
showRationaleDialog(R.string.permission_camera_contacts_rationale, request);
}
@OnPermissionDenied({Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
void onCameraDenied() {
Toast.makeText(this, R.string.permission_denied, Toast.LENGTH_SHORT).show();
}
@OnNeverAskAgain({Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
void onCameraNeverAskAgain() {
Toast.makeText(this, R.string.permission_never_askagain, Toast.LENGTH_SHORT).show();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
MultiPermissionDispatcherActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case CAMERA_REQUEST_CODE:
Bitmap bm = (Bitmap) data.getExtras().get("data");
mPhotoImageView.setImageBitmap(bm);
break;
default:
break;
}
}
}
private void showRationaleDialog(@StringRes int messageResId, final PermissionRequest request) {
new AlertDialog.Builder(this)
.setPositiveButton(R.string.button_allow, new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.proceed();
}
})
.setNegativeButton(R.string.button_deny, new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.cancel();
}
})
.setCancelable(false)
.setMessage(messageResId)
.show();
}
}
從程序中可知,使用PermissionDispatcher一次請求多個權限的過程同一次請求一個權限的過程,所以不再對其進行分析,讀者可下載示例源碼自行查看結果。
源碼地址
先看需求,要求這種效果
一、建立測試環境 安裝了Android Developer Tools (ADT) 插件的Eclipse將為你創建,構建,以及運行Android程序提供一個基於圖形界面的
// 表示事件是否攔截, 返回false表示不攔截 @Override public boolean onInterceptTouchEvent(Motion
根據Android官方提供給我們的Sample例子實實在在的分析Bitmap使用時候的注意點。在分析Bitmap的使用之前先簡單的了解下BitmapFactory 類,B