Android--ContentProvider


前言

  本篇博客講講ContentProvider,內容提供者。前面已經講過了數據持久化,但是除了共享內存(SDCard)的數據外,其他包括SQLite、SharedPreferences都是僅限於被當前所創建的應用訪問,而無法使它們的數據在應用程序之間交換數據,所以Android提供了ContentProvider,ContentProvider是不同應用程序之間進行數據交換的標准API。雖然Android附帶了需要有用的內容提供者,但是本片博客不涉及這方面的內容,而是專注講解如何創建自己的ContentProvider,並在其他應用中如何調用。

 

概述

  ContentProvider可以理解為一個Android應用對外開放的接口,只要是符合它所定義的Uri格式的請求,均可以正常訪問執行操作。其他的Android應用可以使用ContentResolver對象通過與ContentProvider同名的方法請求執行,被執行的就是ContentProvider中的同名方法。所以ContentProvider很多對外可以訪問的方法,在ContentResolver中均有同名的方法,是一一對應的,如圖:

 

 

Uri

  在Android中,Uri是一種比較常見的資源訪問方式。而對於ContentProvider而言,Uri也是有固定格式的:

    <srandard_prefix>://<authority>/<data_path>/<id>

  • <srandard_prefix>:ContentProvider的srandard_prefix始終是content://。
  • <authority>:ContentProvider的名稱。
  • <data_path>:請求的數據類型。
  • <id>:指定請求的特定數據。

 

ContentProvider

  ContentProvider也是Android應用的四大組件之一,所以也需要在AndroidManifest.xml文件中進行配置。而且某個應用程序通過ContentProvider暴露了自己的數據操作接口,那么不管該應用程序是否啟動,其他應用程序都可以通過這個接口來操作它的內部數據。

  Android附帶了許多有用的ContentProvider,但是本篇博客不會涉及到這些內容的,以后有時間會再講解。Android附帶的ContentProvider包括:

  • Browser:存儲如瀏覽器的信息。
  • CallLog:存儲通話記錄等信息。
  • Contacts:存儲聯系人等信息。
  • MediaStore:存儲媒體文件的信息。
  • Settings:存儲設備的設置和首選項信息。

  在Android中,如果要創建自己的內容提供者的時候,需要擴展抽象類ContentProvider,並重寫其中定義的各種方法。然后在AndroidManifest.xml文件中注冊該ContentProvider即可。

  ContentProvider是內容提供者,實現Android應用之間的數據交互,對於數據操作,無非也就是CRUD而已。下面是ContentProvider必須要實現的幾個方法:

  • onCreate():初始化提供者。
  • query(Uri, String[], String, String[], String):查詢數據,返回一個數據Cursor對象。
  • insert(Uri, ContentValues):插入一條數據。
  • update(Uri, ContentValues, String, String[]):根據條件更新數據。
  • delete(Uri, String, String[]):根據條件刪除數據。
  • getType(Uri) 返回MIME類型對應內容的URI。

  除了onCreate()和getType()方法外,其他的均為CRUD操作,這些方法中,Uri參數為與ContentProvider匹配的請求Uri,剩下的參數可以參見SQLite的CRUD操作,基本一致,SQLite的內容在另外一篇博客中有講解:Android--數據持久化之SQLite。、

  Tips:還有兩個非常有意思的方法,必須要提一下,call()和bulkInsert()方法,使用call,理論上可以在ContentResolver中執行ContentProvider暴露出來的任何方法,而bulkInsert()方法用於插入多條數據。

 

  在ContentProvider的CRUD操作,均會傳遞一個Uri對象,通過這個對象來匹配對應的請求。那么如何確定一個Uri執行哪項操作呢?需要用到一個UriMatcher對象,這個對象用來幫助內容提供者匹配Uri。它所提供的方法非常簡單,僅有兩個:

  • void addURI(String authority,String path,int code):添加一個Uri匹配項,authority為AndroidManifest.xml中注冊的ContentProvider中的authority屬性;path為一個路徑,可以設置通配符,#表示任意數字,*表示任意字符;code為自定義的一個Uri代碼。
  • int match(Uri uri):匹配傳遞的Uri,返回addURI()傳遞的code參數。

 

  在創建好一個ContentProvider之后,還需要在AndroidManifest.xml文件中對ContentProvider進行配置,使用一個<provider.../>節點,一般只需要設置兩個屬性即可訪問,一些額外的屬性就是為了設置訪問權限而存在的,后面會詳細講解:

  • android:name:provider的響應類。
  • android:authorities:Provider的唯一標識,用於Uri匹配,一般為ContentProvider類的全名。

 

  下面通過一個示例來講解一下ContentProvider,在這個例子中,需要用到SQLite數據庫來存儲數據,定義了一個StudentDAO類,用於進行對SQLite的CRUD操作,這里就不提供數據訪問的源碼了,有興趣的朋友可以在下載源碼查看:

  ContentProvider實現:

  1 package com.example.contentproviderdemo;
  2 
  3 import com.example.dao.StudentDAO;
  4 import android.content.ContentProvider;
  5 import android.content.ContentUris;
  6 import android.content.ContentValues;
  7 import android.content.UriMatcher;
  8 import android.database.Cursor;
  9 import android.net.Uri;
 10 import android.os.Bundle;
 11 import android.util.Log;
 12 
 13 public class StudentProvider extends ContentProvider {
 14 
 15     private final String TAG = "main";
 16     private StudentDAO studentDao = null;
 17     private static final UriMatcher URI_MATCHER = new UriMatcher(
 18             UriMatcher.NO_MATCH);
 19     private static final int STUDENT = 1;
 20     private static final int STUDENTS = 2;
 21     static {
 22         //添加兩個URI篩選
 23         URI_MATCHER.addURI("com.example.contentproviderdemo.StudentProvider",
 24                 "student", STUDENTS);
 25         //使用通配符#,匹配任意數字
 26         URI_MATCHER.addURI("com.example.contentproviderdemo.StudentProvider",
 27                 "student/#", STUDENT);        
 28     }
 29 
 30     public StudentProvider() {
 31 
 32     }    
 33     
 34     @Override
 35     public boolean onCreate() {
 36         // 初始化一個數據持久層
 37         studentDao = new StudentDAO(getContext());
 38         Log.i(TAG, "---->>onCreate()被調用");
 39         return true;
 40     }
 41 
 42     @Override
 43     public Uri insert(Uri uri, ContentValues values) {
 44         Uri resultUri = null;
 45         //解析Uri,返回Code
 46         int flag = URI_MATCHER.match(uri);
 47         if (flag == STUDENTS) {
 48             long id = studentDao.insertStudent(values);
 49             Log.i(TAG, "---->>插入成功, id="+id);
 50             resultUri = ContentUris.withAppendedId(uri, id);
 51         }
 52         return resultUri;
 53     }
 54 
 55     @Override
 56     public int delete(Uri uri, String selection, String[] selectionArgs) {
 57         int count = -1;
 58         try {
 59             int flag = URI_MATCHER.match(uri);
 60             switch (flag) {
 61             case STUDENT:
 62                 // delete from student where id=?
 63                 //單條數據,使用ContentUris工具類解析出結尾的Id
 64                 long id = ContentUris.parseId(uri);
 65                 String where_value = "id = ?";
 66                 String[] args = { String.valueOf(id) };
 67                 count = studentDao.deleteStudent(where_value, args);
 68                 break;
 69             case STUDENTS:
 70                 count = studentDao.deleteStudent(selection, selectionArgs);                
 71                 break;
 72             }
 73         } catch (Exception e) {
 74             e.printStackTrace();
 75         }
 76         Log.i(TAG, "---->>刪除成功,count="+count);
 77         return count;
 78     }
 79 
 80     @Override
 81     public int update(Uri uri, ContentValues values, String selection,
 82             String[] selectionArgs) {
 83         int count = -1;
 84         try {            
 85             int flag = URI_MATCHER.match(uri);
 86             switch (flag) {
 87             case STUDENT:
 88                 long id = ContentUris.parseId(uri);
 89                 String where_value = " id = ?";
 90                 String[] args = { String.valueOf(id) };
 91                 count = studentDao.updateStudent(values, where_value, args);
 92                 break;
 93             case STUDENTS:
 94                 count = studentDao.updateStudent(values, selection,
 95                         selectionArgs);
 96                 break;
 97             }
 98         } catch (Exception e) {
 99             e.printStackTrace();
100         }
101         Log.i(TAG, "---->>更新成功,count="+count);
102         return count;
103     }
104 
105     @Override
106     public Cursor query(Uri uri, String[] projection, String selection,
107             String[] selectionArgs, String sortOrder) {
108         Cursor cursor = null;
109         try {
110             int flag = URI_MATCHER.match(uri);
111             switch (flag) {
112             case STUDENT:
113                 long id = ContentUris.parseId(uri);
114                 String where_value = " id = ?";
115                 String[] args = { String.valueOf(id) };
116                 cursor = studentDao.queryStudents(where_value, args);
117                 break;
118             case STUDENTS:
119                 cursor = studentDao.queryStudents(selection, selectionArgs);
120                 break;
121             }
122         } catch (Exception e) {
123             e.printStackTrace();
124         }
125         Log.i(TAG, "---->>查詢成功,Count="+cursor.getCount());
126         return cursor;
127     }
128 
129 
130     @Override
131     public String getType(Uri uri) {
132         int flag = URI_MATCHER.match(uri);
133         String type = null;
134         switch (flag) {
135         case STUDENT:
136             type = "vnd.android.cursor.item/student";
137             Log.i(TAG, "----->>getType return item");
138             break;
139         case STUDENTS:
140             type = "vnd.android.cursor.dir/students";
141             Log.i(TAG, "----->>getType return dir");
142             break;
143         }
144         return type;
145     }
146     @Override
147     public Bundle call(String method, String arg, Bundle extras) {
148         Log.i(TAG, "------>>"+method);
149         Bundle bundle=new Bundle();
150         bundle.putString("returnCall", "call被執行了");
151         return bundle;
152     }
153 }

  在AndroidManifest.xml中<application>節點中增加:

1         <provider
2             android:name=".StudentProvider" 
3             android:authorities="com.example.contentproviderdemo.StudentProvider" >
4         </provider>

 

ContentResolver

  ContentResolver,內容訪問者。可以通過ContentResolver來操作ContentProvider所暴露處理的接口。一般使用Content.getContentResolver()方法獲取ContentResolver對象。上面已經提到ContentResolver的很多方法與ContentProvider一一對應,所以它也存在insert、query、update、delete等方法。

  下面就通過一個簡單的Demo來演示ContentResolver的使用,這里就不另外新建一個項目了,在原有項目上使用Android JUnit新建一個測試類,用於測試。在另外一個項目中的操作也是一樣的。關於JUnit,不了解的朋友可以參見另外一篇博客:JUnit單元測試

  JUnit測試類:

  1 package com.example.contentproviderdemo;
  2 
  3 import android.content.ContentResolver;
  4 import android.content.ContentValues;
  5 import android.database.Cursor;
  6 import android.net.Uri;
  7 import android.os.Bundle;
  8 import android.test.AndroidTestCase;
  9 import android.util.Log;
 10 
 11 public class MyTest extends AndroidTestCase {
 12 
 13     public MyTest() {
 14         // TODO Auto-generated constructor stub
 15 
 16     }
 17 
 18     public void insert() {
 19         ContentResolver contentResolver = getContext().getContentResolver();
 20         Uri uri = Uri
 21                 .parse("content://com.example.contentproviderdemo.StudentProvider/student");
 22         ContentValues values = new ContentValues();
 23         values.put("name", "Demo");
 24         values.put("address", "HK");
 25         Uri returnuir = contentResolver.insert(uri, values);
 26         Log.i("main", "-------------->" + returnuir.getPath());
 27     }
 28 
 29     public void delete() {
 30         ContentResolver contentResolver = getContext().getContentResolver();
 31         // 刪除多行:content://com.example.contentproviderdemo.StudentProvider/student
 32         Uri uri = Uri
 33                 .parse("content://com.example.contentproviderdemo.StudentProvider/student/2");
 34         contentResolver.delete(uri, null, null);
 35     }
 36 
 37     public void deletes() {
 38         ContentResolver contentResolver = getContext().getContentResolver();
 39         Uri uri = Uri
 40                 .parse("content://com.example.contentproviderdemo.StudentProvider/student");
 41         String where = "address=?";
 42         String[] where_args = { "HK" };
 43         contentResolver.delete(uri, where, where_args);
 44     }
 45 
 46     public void update() {
 47         ContentResolver contentResolver = getContext().getContentResolver();
 48         Uri uri = Uri
 49                 .parse("content://com.example.contentproviderdemo.StudentProvider/student/2");
 50         ContentValues values = new ContentValues();
 51         values.put("name", "李四");
 52         values.put("address", "上海");
 53         contentResolver.update(uri, values, null, null);
 54     }
 55 
 56     public void updates() {
 57         ContentResolver contentResolver = getContext().getContentResolver();
 58         Uri uri = Uri
 59                 .parse("content://com.example.contentproviderdemo.StudentProvider/student");
 60         ContentValues values = new ContentValues();
 61         values.put("name", "王五");
 62         values.put("address", "深圳");
 63         String where = "address=?";
 64         String[] where_args = { "beijing" };
 65         contentResolver.update(uri, values, where, where_args);
 66     }
 67 
 68     public void query() {
 69         ContentResolver contentResolver = getContext().getContentResolver();
 70         Uri uri = Uri
 71                 .parse("content://com.example.contentproviderdemo.StudentProvider/student/2");
 72         Cursor cursor = contentResolver.query(uri, null, null, null, null);
 73         while (cursor.moveToNext()) {
 74             Log.i("main",
 75                     "-------------->"
 76                             + cursor.getString(cursor.getColumnIndex("name")));
 77         }
 78     }
 79 
 80     public void querys() {
 81         ContentResolver contentResolver = getContext().getContentResolver();
 82         Uri uri = Uri
 83                 .parse("content://com.example.contentproviderdemo.StudentProvider/student");
 84         String where = "address=?";
 85         String[] where_args = { "深圳" };
 86         Cursor cursor = contentResolver.query(uri, null, where, where_args,
 87                 null);
 88         while (cursor.moveToNext()) {
 89             Log.i("main",
 90                     "-------------->"
 91                             + cursor.getString(cursor.getColumnIndex("name")));
 92         }
 93     }
 94 
 95     public void calltest() {
 96         ContentResolver contentResolver = getContext().getContentResolver();
 97         Uri uri = Uri
 98                 .parse("content://com.example.contentproviderdemo.StudentProvider/student");
 99         Bundle bundle = contentResolver.call(uri, "method", null, null);
100         String returnCall = bundle.getString("returnCall");
101         Log.i("main", "-------------->" + returnCall);
102     }
103 
104 }

  效果演示:

  執行insert(),數據庫在創建的時候存在初始數據:

  執行update():

   執行updates():

  執行query()

  執行delete()

 

getType()中的MIME

  MIME類型就是設定某種擴展名的文件用一種應用程序來打開的方式類型。在ContentProvider中的getType方法,返回的就是一個MIME類型的字符串。如果支持需要使用ContentProvider來訪問數據,就上面這個Demo,getType()完全可以只返回一個Null,並不影響效果,但是覆蓋ContentProvider的getType方法對於用new Intent(String action, Uri uri)方法啟動activity是很重要的,如果它返回的MIME type和activity在<intent filter>中定義的data的MIME type不一致,將造成activity無法啟動。這就涉及到Intent和Intent-filter的內容了,以后有機會再說,這里不再詳解。

  從官方文檔了解到,getType返回的字符串,如果URI針對的是單條數據,則返回的字符串以vnd.android.cursor.item/開頭;如果是多條數據,則以vnd.adroid.cursor.dir/開頭。

 

訪問權限

  對於ContentProvider暴露出來的數據,應該是存儲在自己應用內存中的數據,對於一些存儲在外部存儲器上的數據,並不能限制訪問權限,使用ContentProvider就沒有意義了。對於ContentProvider而言,有很多權限控制,可以在AndroidManifest.xml文件中對<provider>節點的屬性進行配置,一般使用如下一些屬性設置:

  • android:grantUriPermssions:臨時許可標志。
  • android:permission:Provider讀寫權限。
  • android:readPermission:Provider的讀權限。
  • android:writePermission:Provider的寫權限。
  • android:enabled:標記允許系統啟動Provider。
  • android:exported:標記允許其他應用程序使用這個Provider。
  • android:multiProcess:標記允許系統啟動Provider相同的進程中調用客戶端。

  源碼下載

總結

  以上就講解了內容提供者的簡單使用,關於內容提供者的內容涉及面很多,一篇博客無法一一說明,這里只是簡單的定義一個內容提供者,並且說明如何操作它。這個博客的示例源碼中,聲明的內容提供者和內容訪問者均在一個項目中,可能不太利於大家理解,但是在另外一個項目中的操作,與JUnit類中的操作一致,所以這里就沒有另外創建一個項目去說明它,不過源碼中有另外一個小項目,只是實現了Insert的操作,有興趣的朋友對照看一下就會發現其實是一樣的。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM