Android學習羈絆之Content Provider
Content Provider(內容提供器)主要用于在不同的應用程序之間實現數據共享的功能,它提供了一套完整的機制,允許一個程序訪問另一個程序中的數據,同時還能保證被訪數據的安全性。目前,使用內容提供器是Android實現跨程序共享數據的標準方式。
不同于文件存儲和SharedPreferences存儲中的兩種全局可讀寫操作模式,內容提供器可以選擇只對哪一部分數據進行共享,從而保證程序中的隱私數據不會有泄漏的風險。
運行時權限
Android 6.0以前,Android的權限機制在保護用戶安全和隱私等方面起到的作用比較有限,Android開發團隊在Android 6.0系統中引用了運行時權限這個功能,從而更好地保護了用戶的安全和隱私。
Android權限機制
當應用程序要訪問系統相關信息(例如網絡狀態)或是設置系統設置,這會涉及用戶設備的安全性,必須要在AndroidManifest.xml文件中添加權限聲明,否則程序會崩潰
<!--添加權限-->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
聲明權限之后用戶在兩個方面得到了保護:
- 如果用戶在低于6.0系統的設備上安裝程序,會在安裝界面上提醒用戶程序所要獲取的權限,這樣用戶就可以清楚的知道該程序一共申請了那些權限,從而決定是否要安裝程序。
- 用戶可以隨時在應用程序管理界面查看任意一個程序的權限申請情況
雖然Android系統通過權限保護了用戶的信息安全,但是很多常用軟件普遍存在濫用權限的情況,不管最后到底用不用,先把申請權限了。Android開發團隊意識到了這個問題,于是在6.0系統中加入了運行時權限功能。用戶不需要在安裝軟件的時候一次性授權所有申請的權限,而是可以在軟件的使用過程中再對某一項權限申請進行授權。
Android將所有的權限歸成了兩類:
- 普通權限:指那些不會直接威脅到用戶的安全和隱私的權限,對于這部分權限申請,系統會自動幫用戶進行授權,而不需要用戶再去手動操作。
- 危險權限:指那些可能會觸及用戶隱私或者對設備安全性造成影響的權限,對于這部分權限申請,必須要由用戶手動點擊授權才可以,否則
程序就無法使用相應的功能。
這樣做可以避免用戶繁瑣的授權。
Android中有一共有上百種權限,危險權限總共就幾個,除了危險權限之外,剩余的就都是普通權限。Android中所有的危險權限,一共是9組24個權限,如下圖所示
用戶一旦同意授權了,那么該權限所對應的權限組中所有的其他權限也會同時被授權。每當要使用一個權限時,可以先到這張表中來查一下,如果是屬于這張表中的權限,那么就需要進行運行時權限處理,如果不在這張表中,那么只需要在AndroidManifest.xml文件中添加一下權限聲明就可以。
運行時申請權限
運行時權限的核心就是在程序運行過程中由用戶授權去執行某些危險操作,程序不可以擅自做主去執行這些危險操作的。第一步就是要先判斷用戶是不是已經授權了,通過**ContextCompat.checkSelfPermission()**方法實現。
public static int checkSelfPermission(@NonNull Context context, @NonNull String permission)
checkSelfPermission() 方法接收兩個參數:
- Context context:context對象(Activity等)
- String permission:具體的權限名(打電話的權限:Manifest.permission.CALL_PHONE)
通過checkSelfPermission()方法的返回值與PackageManager.PERMISSION_GRANTED 做比較,相等就說明用戶已經授權,不等就表示
用戶沒有授權。
如果沒有授權的話,則需要調用**ActivityCompat.requestPermissions()**方法來向用戶申請授權。
public static void requestPermissions(final @NonNull Activity activity, final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode)
**requestPermissions()**方法接收3個參數:
- Activity activity:Activity的實例
- String[] permissions:要申請的權限名放在數組中
- int requestCode:請求碼,要求是唯一值
調用完了**requestPermissions()方法之后,系統會彈出一個權限申請的對話框,然后用戶可以選擇同意或拒絕程序的權限申請,不論是哪種結果,最終都會回調到onRequestPermissionsResult()**方法中,而授權的結果則會封裝在grantResults參數當中。
相關代碼如下:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
//沒有授權,先執行授權操作
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
} else {
//若已經授權,執行其他操作
}
//回調方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
//授權與否都會調用這個方法,用回調方法執行程序相應的邏輯
}
若用戶想回收賦予程序的權限,可以在設置中回收程序的權限。
Content Provider
Content Provider的用法一般有兩種:
- 使用現有的內容提供器來讀取和操作相應程序中的數據;
- 創建自己的內容提供器給程序的數據提供外部訪問接口
一個應用程序通過內容提供器對其數據提供了外部訪問接口,那么任何其他的應用程序就都可以對這部分數據進行訪問
使用現有Content Provider
要訪問內容提供器中共享的數據,需要借助ContentResolver類,可以通過Context中的**getContentResolver()**方法獲取到該類的實例。
ContentResolver中提供了一系列的方法用于對數據進行CRUD操作:
- insert():添加數據
- update():更新數據
- delete():刪除數據
- query():查詢數據
這些操作與SQLiteDatabase類似。ContentResolver中的CRUD方法都是不接收表名參數的,而是使用一個Uri參數代替,這個參數被稱為內容URI。
內容URI給內容提供器中的數據建立了唯一標識符,它主要由兩部分組成:
- authority:用于對不同的應用程序做區分的,一般為了避免沖突,都會采用程序包名的方式來進行命名
- path:用于對同一應用程序中不同的表做區分的,通常都會添加到authority的后面
除此之外,還需要在URI字符串前添加協議聲明,使其能夠被識別成內容URI,內容URI最標準的格式寫法如下:
content://cn.chenjianlink.android.contactstest/table
在得到了內容URI字符串之后,需要將它解析成Uri對象才可以作為參數傳入,調用**Uri.parse()**方法,就可以將內容URI字符串解析成Uri 對象:
Uri uri = Uri.parse("content://cn.chenjianlink.android.contactstest/table")
使用Uri 對象來查詢表中的數據:
Cursor cursor = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
**query()**方法中的參數如下表所示,與SQLiteDatabase類似:
查詢完成后返回的是一個Cursor 對象,這時可以將數據從Cursor對象中逐個讀取出來了。通過移動游標的位置來遍歷Cursor 的所有行,然后再取出每一行中相應列的數據,相關代碼如下:
if (cursor != null) {
while (cursor.moveToNext()) {
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
}
對Content Provider增加操作如下所示:
ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);
將待添加的數據組裝到ContentValues中,然后調用ContentResolver的**insert()**方法,將Uri和ContentValues作為參數傳入。
對Content Provider修改操作如下所示:
ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[] {"text", "1"});
對Content Provider刪除操作如下所示:
getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });
自定義Content Provider
過新建一個類去繼承ContentProvider 的方式來創建一個自己的內容提供器。ContentProvider 類中有6個抽象方法,在使用子類繼承它的時候,需要將這6個方法全部重寫:
public class MyProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[]
selectionArgs, String sortOrder) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[]
selectionArgs) {
return 0;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public String getType(Uri uri) {
return null;
}
}
- onCreate():初始化Content Provider的時候調用。返回true 表示內容提供器初始化成功,返回false 則表示失敗。
- query():從Content Provider中查詢數據。使用uri 參數來確定查詢哪張表,projection 參數用于確定查詢哪些列,selection 和selectionArgs 參數用于約束查詢哪些行,sortOrder 參數用于對結果進行排序,查詢的結果存放在Cursor對象中返回。
- insert():向Content Provider中添加一條數據。使用uri 參數來確定要添加到的表,待添加的數據保存在values 參數中。添加完成后,返回一個用于表示這條新記錄的URI
- update():更新Content Provider中已有的數據。使用uri 參數來確定更新哪一張表中的數據,新數據保存在values 參數中,selection 和selectionArgs 參數用于約束更新哪些行,受影響的行數將作為返回值返回
- delete():從Content Provider中刪除數據。使用uri 參數來確定刪除哪一張表中的數據,selection 和selectionArgs 參數用于約束刪除哪些行,被刪除的行數將作為返回值返回。
- getType():根據傳入的內容URI來返回相應的MIME類型。
每一個方法都會帶有Uri 這個參數,這個參數也正是調用Content Resolver的增刪改查方法時傳遞過來的。
一個標準的內容URI寫法如下:
content://cn.chenjianlink.android.contactstest/table
可以在這個內容URI的后面加上一個id,表示調用方期望訪問的是cn.chenjianlink.android.contactstest這個應用的table表中id為1的數據
content://cn.chenjianlink.android.contactstest/table/1
內容URI的格式主要就只有以上兩種,以路徑結尾就表示期望訪問該表中所有的數據,以id結尾就表示期望訪問該表中擁有相應id的數據。可以使用通配符的方式來分別匹配這兩種格式的內容URI,規則如下:
- *:表示匹配任意長度的任意字符
- #:表示匹配任意長度的數字
借助UriMatcher這個類就可以輕松地實現匹配內容URI的功能,UriMatcher中提供了一個addURI() 方法,這個方法接收3個參數:
public void addURI(String authority, String path, int code)
- String authority
- String path
- int code:自定義代碼
調用UriMatcher的**match()**方法時,就可以將一個Uri 對象傳入,返回值是某個能夠匹配這個Uri對象所對應的自定義代碼,利用這個代碼,就可以判斷出調用方期望訪問的是哪張表中的數據了。
對于**getType()**方法。它是所有的內容提供器都必須提供的一個方法,用于獲取Uri 對象所對應的MIME類型。一個內容URI所對應的MIME字符串主要由3部分組成,Android對這3個部分做了如下格式規定:
- 必須以vnd 開頭
- 如果內容URI以路徑結尾,則后接android.cursor.dir/ ,如果內容URI以id結尾,則后接android.cursor.item/
- 最后接上vnd.<authority>.<path>
對于content://cn.chenjianlink.android.contactstest/table 這個內容URI,它所對應的MIME類型就可以寫成:
vnd.android.cursor.dir/vnd.cn.chenjianlink.android.contactstest.table
對于content://cn.chenjianlink.android.contactstest/table/1 這個內容URI,它所對應的MIME類型就可以寫成:
vnd.android.cursor.item/vnd.cn.chenjianlink.android.contactstest.table
自定義Content Resolver需要在AndroidManifest.xml文件中注冊才可以使用,相關代碼如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.chenjianlink.android.contactstest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
<provider
android:name=".MyProvider"
android:authorities="cn.chenjianlink.android.contactstest"
android:enabled="true"
android:exported="true">
</provider>
</application>
</manifest>
在<application>標簽中添加<provider>標簽,<provider>標簽屬性如下:
- android:name:指定MyProvider的類名
- android:authorities:指定MyProvider的authority
- android:enabled:表示是否啟用這個內容提供器
- android:exported:表示是否允許外部程序訪問自定義的內容提供器
智能推薦
Content Provider與SQLite結合使用
前言 雖然推薦使用數據庫保存結構化的復雜數據,但是數據共享是一個挑戰,因為數據庫只允許創建它的應用訪問。 在Android中共享數據 在Android中,推薦使用content provider方法在不同包之間共享數據。content provider可以被視為一個數據倉庫。它如何存儲數據與應用如何使用它無關。然而,應用如何使用一致的編程接口在它的里面的數據非常重要。content pro...
(譯)從Content Provider中獲取數據
原文:https://developer.android.google.cn/guide/topics/providers/content-provider-basics.html Content Provider基礎 Content Provider管理對中央數據倉庫的訪問,它是Android應用的一部分,通常會提供自己的UI來使用數據。然而,Content Provider主要是為了給其他應用...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
猜你喜歡
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...
統計學習方法 - 樸素貝葉斯
引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...
styled-components —— React 中的 CSS 最佳實踐
https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...