本模塊共有四篇文章,參考郭神的《第一行代碼》,對Content Provider的學(xué)習(xí)做一個詳細(xì)的筆記,大家可以一起交流一下:
- 跨程序共享數(shù)據(jù)——Content Provider 之 運(yùn)行時權(quán)限解析以及申請的實(shí)現(xiàn)(可完美解決java.lang.SecurityException:Permission Denial 問題)(即本文)
- 跨程序共享數(shù)據(jù)——Content Provider 之 ContentResolver基本用法 & 一個讀取系統(tǒng)聯(lián)系人的Demo
- 跨程序共享數(shù)據(jù)——Content Provider 之 創(chuàng)建自己的內(nèi)容提供器
- Content Provider 之 最終彈 實(shí)戰(zhàn)體驗(yàn)跨程序數(shù)據(jù)共享(結(jié)合SQLiteDemo)
關(guān)于內(nèi)容提供器:
內(nèi)容提供器(Content Provider)主要用于在不同的應(yīng)用程序之間實(shí)現(xiàn)數(shù)據(jù)共享的功能,它提供了一套完整的機(jī)制,允許一個程序訪問另一個程序中的數(shù)據(jù),同時還能保證被訪數(shù)據(jù)的安全性。目前,使用內(nèi)容提供器是Android實(shí)現(xiàn)跨程序共享數(shù)據(jù)的標(biāo)準(zhǔn)方式。
不同于文件存儲和SharedPreferences存儲中的兩種全局可讀寫操作模式,內(nèi)容提供器可以選擇只對哪一部分?jǐn)?shù)據(jù)進(jìn)行共享,從而保證我們程序中的隱私數(shù)據(jù)不會有泄漏的風(fēng)險。
在正式開始學(xué)習(xí)內(nèi)容提供器之前,我們需要先掌握待會兒需要用到的運(yùn)行時權(quán)限。
完美解決java.lang.SecurityException:Permission Denial 問題
1.運(yùn)行時權(quán)限
首先,Android所有的權(quán)限可以歸成兩類:一類是普通權(quán)限,一類是危險權(quán)限;
普通權(quán)限是指那些不會直接威脅到用戶的安全和隱私的權(quán)限,對于這部分權(quán)限申請,系統(tǒng)會自動幫我們進(jìn)行授權(quán),而不需要用戶再去手動操作了,比如在BroadcastTest項目中申請的兩個權(quán)限就是普通權(quán)限。
危險權(quán)限則表示那些可能會觸及用戶隱私,或者對設(shè)備安全性造成影響的權(quán)限,如獲取設(shè)備聯(lián)系人信息、定
位設(shè)備的地理位置等,對于這部分權(quán)限,我們當(dāng)在程序中申請,并必須要由用戶手動點(diǎn)擊授權(quán)才可以,否則程序就無法使用相應(yīng)的功能。
Android中有一共上百種權(quán)限,危險權(quán)限主要為以下9組24個權(quán)限,剩余的都是普通權(quán)限:

使用這張表格:
這張表格里面的權(quán)限可能全都是我們沒使用過的。
不過沒事,我們并不需要了解表格中每個權(quán)限的作用,只要把它當(dāng)成一個參照表來查看就行了:
每當(dāng)要使用一個權(quán)限時,可以先到這張表中來查一下:
如果是屬于這張表中的權(quán)限,那么就需要進(jìn)行運(yùn)行時權(quán)限處理;
如果不在這張表中,則在AndroidManifest.xml文件中添加權(quán)限聲明即可;
以及有的時候是關(guān)于特殊權(quán)限的申請,那就當(dāng)查看API了;
另外注意一下,表格中每個危險權(quán)限都屬于一個權(quán)限組,我們在進(jìn)行運(yùn)行時權(quán)限處理時使用的是權(quán)限名,而用戶一旦同意授權(quán)了一個權(quán)限組中的任意一個權(quán)限的話,則該權(quán)限所對應(yīng)的權(quán)限組中所有的其他權(quán)限也會同時被授權(quán)。
訪問https://developer.android.google.cn/reference/android/Manifest.permission可以查看Android系統(tǒng)中完整的權(quán)限列表。
1.1 在程序運(yùn)行時申請權(quán)限
首先新建一個RuntimePermissionTest項目;
本例子將嘗試對CALL_PHONE這個權(quán)限的申請;
CALL_PHONE這個權(quán)限是編寫撥打電話功能的時候需要聲明的,在Android6.0系統(tǒng)出現(xiàn)之前,撥打電話功能的實(shí)現(xiàn)其實(shí)非常簡單,Android6.0之后因?yàn)閾艽螂娫挄婕坝脩羰謾C(jī)的資費(fèi)問題,因而被列為了危險權(quán)限,需要聲明才可使用。
修改activity_main.xml,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.runtimepermissiontest.MainActivity">
<Button
android:id="@+id/make_call"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Make Call"/>
</LinearLayout>
MainActivity,添加監(jiān)聽,觸發(fā)打電話的邏輯:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button makeCall = (Button) findViewById(R.id.make_call);
makeCall.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try{
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}catch (SecurityException e){
e.printStackTrace();
}
}
});
}
}
可以看到,在按鈕的點(diǎn)擊事件中,我們構(gòu)建了一個隱式Intent, Intent 的 action 指定為 Intent.ACTION_CALL ,這是一個系統(tǒng)內(nèi)置的打電話的動作,然后在data部分指定了協(xié)議是 tel , 號碼是10086
接下來還需在AndroidManifest中聲明權(quán)限:

<uses-permission android:name="android.permission.CALL_PHONE" />
當(dāng)然到此為止運(yùn)行的時候,會出現(xiàn)報錯,下面需要最后一步,進(jìn)行權(quán)限申請?。?/strong>
我們把剛剛的打電話邏輯封裝在call()中:
PS:onRequestPermissionsResult所在的包

代碼簡析:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button makeCall = (Button) findViewById(R.id.make_call);
makeCall.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.
permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(MainActivity.this, new
String[]{ Manifest.permission.CALL_PHONE}, 1);
}else {
call();
}
}
});
}
private void call() {
try{
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}catch (SecurityException e){
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case 1:
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
call();
}else{
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
代碼詳細(xì)講解:
上面的代碼是申請運(yùn)行時權(quán)限的完整流程,下面我們來具體解析一下;
運(yùn)行時權(quán)限的核心就是在程序運(yùn)行過程中由用戶授權(quán)我們?nèi)?zhí)行某些危險操作,程序是不可以擅自做主去執(zhí)行這些危險操作的;
因此,第一步就是要先借助于ContextCompa.checkSelfPermission()方法,判斷用戶是不是已經(jīng)給過我們授權(quán)了。
checkSelfPermission()方法接收兩個參數(shù),第一個參數(shù)是Context,第二個參數(shù)是具體的權(quán)限名;
比如打電話的權(quán)限名就是Manifest.permission.CALLPHONE,
然后我們使用方法的返回值和PackageManager.PERMISSION_GRANTED做比較:
相等就說明用戶已經(jīng)授權(quán),
不等就表示用戶沒有授權(quán)。如果已經(jīng)授權(quán),就直接執(zhí)行撥打電話的封裝方法call();
如果沒有授權(quán),則需要調(diào)用ActivityCompat.requestpermissions()方法來向用戶申請授權(quán),
requestpermissions()方法接收3個參數(shù),
第一個參數(shù)要求是Activity的實(shí)例,
第二個參數(shù)是一個String數(shù)組,把要申請的權(quán)限名放在數(shù)組中即可,
第三個參數(shù)是請求碼,只要是唯一值就可以了,這里傳入了1。調(diào)用完了requestpermissions()方法之后,系統(tǒng)會彈出一個權(quán)限申請的對話框,然后用戶可以選擇同意或拒絕我們的權(quán)限申請,
不論是哪種結(jié)果,最終都會回調(diào)到onRequestPermissionsResult()方法中,
而授權(quán)的結(jié)果則會封裝在grantResuIts參數(shù)當(dāng)中。
到這里判斷一下最后的授權(quán)結(jié)果,
如果用戶同意就調(diào)用call()方法來撥打電話,
如果用戶拒絕則放棄操作并彈出一條失敗提示;
下面運(yùn)行程序,點(diǎn)擊按鈕,會彈出對話框:

如果點(diǎn)擊拒絕,則會彈出Toast:

如果點(diǎn)擊允許,則成功進(jìn)入到撥打電話界面:

在這之后,我們就成功進(jìn)入撥打電話界面了,并且至此用戶已經(jīng)完成了授權(quán),之后再點(diǎn)擊MakeCall按鈕就不會再彈出權(quán)限申請對話框了,而是可以直接撥打電話。
所以注意一下這里,用戶隨時都可以手動將授予程序的危險權(quán)限進(jìn)行關(guān)閉,
進(jìn)人Settings→Apps→RuntimePermissionTest→Permissions進(jìn)行操作即可,界面如圖:

在這里便可以手動開關(guān)危險權(quán)限了:
