編輯:Android開發實例
前言
本文講講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>
ContentProvider
ContentProvider也是Android應用的四大組件之一,所以也需要在AndroidManifest.xml文件中進行配置。而且某個應用程序通過ContentProvider暴露了自己的數據操作接口,那麼不管該應用程序是否啟動,其他應用程序都可以通過這個接口來操作它的內部數據。
Android附帶了許多有用的ContentProvider,但是本篇博客不會涉及到這些內容的,以後有時間會再講解。Android附帶的ContentProvider包括:
在Android中,如果要創建自己的內容提供者的時候,需要擴展抽象類ContentProvider,並重寫其中定義的各種方法。然後在AndroidManifest.xml文件中注冊該ContentProvider即可。
ContentProvider是內容提供者,實現Android應用之間的數據交互,對於數據操作,無非也就是CRUD而已。下面是ContentProvider必須要實現的幾個方法:
除了onCreate()和getType()方法外,其他的均為CRUD操作,這些方法中,Uri參數為與ContentProvider匹配的請求Uri,剩下的參數可以參見SQLite的CRUD操作,基本一致,SQLite的內容在另外一篇中有講解:http://www.fengfly.com/plus/view-213507-1.html。
Tips:還有兩個非常有意思的方法,必須要提一下,call()和bulkInsert()方法,使用call,理論上可以在ContentResolver中執行ContentProvider暴露出來的任何方法,而bulkInsert()方法用於插入多條數據。
在ContentProvider的CRUD操作,均會傳遞一個Uri對象,通過這個對象來匹配對應的請求。那麼如何確定一個Uri執行哪項操作呢?需要用到一個UriMatcher對象,這個對象用來幫助內容提供者匹配Uri。它所提供的方法非常簡單,僅有兩個:
在創建好一個ContentProvider之後,還需要在AndroidManifest.xml文件中對ContentProvider進行配置,使用一個<provider.../>節點,一般只需要設置兩個屬性即可訪問,一些額外的屬性就是為了設置訪問權限而存在的,後面會詳細講解:
下面通過一個示例來講解一下ContentProvider,在這個例子中,需要用到SQLite數據庫來存儲數據,定義了一個StudentDAO類,用於進行對SQLite的CRUD操作,這裡就不提供數據訪問的源碼了,有興趣的朋友可以在下載源碼查看:
ContentProvider實現:
- package com.example.contentproviderdemo;
- import com.example.dao.StudentDAO;
- import android.content.ContentProvider;
- import android.content.ContentUris;
- import android.content.ContentValues;
- import android.content.UriMatcher;
- import android.database.Cursor;
- import android.net.Uri;
- import android.os.Bundle;
- import android.util.Log;
- public class StudentProvider extends ContentProvider {
- private final String TAG = "main";
- private StudentDAO studentDao = null;
- private static final UriMatcher URI_MATCHER = new UriMatcher(
- UriMatcher.NO_MATCH);
- private static final int STUDENT = 1;
- private static final int STUDENTS = 2;
- static {
- //添加兩個URI篩選
- URI_MATCHER.addURI("com.example.contentproviderdemo.StudentProvider",
- "student", STUDENTS);
- //使用通配符#,匹配任意數字
- URI_MATCHER.addURI("com.example.contentproviderdemo.StudentProvider",
- "student/#", STUDENT);
- }
- public StudentProvider() {
- }
- @Override
- public boolean onCreate() {
- // 初始化一個數據持久層
- studentDao = new StudentDAO(getContext());
- Log.i(TAG, "---->>onCreate()被調用");
- return true;
- }
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- Uri resultUri = null;
- //解析Uri,返回Code
- int flag = URI_MATCHER.match(uri);
- if (flag == STUDENTS) {
- long id = studentDao.insertStudent(values);
- Log.i(TAG, "---->>插入成功, id="+id);
- resultUri = ContentUris.withAppendedId(uri, id);
- }
- return resultUri;
- }
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- int count = -1;
- try {
- int flag = URI_MATCHER.match(uri);
- switch (flag) {
- case STUDENT:
- // delete from student where id=?
- //單條數據,使用ContentUris工具類解析出結尾的Id
- long id = ContentUris.parseId(uri);
- String where_value = "id = ?";
- String[] args = { String.valueOf(id) };
- count = studentDao.deleteStudent(where_value, args);
- break;
- case STUDENTS:
- count = studentDao.deleteStudent(selection, selectionArgs);
- break;
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- Log.i(TAG, "---->>刪除成功,count="+count);
- return count;
- }
- @Override
- public int update(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- int count = -1;
- try {
- int flag = URI_MATCHER.match(uri);
- switch (flag) {
- case STUDENT:
- long id = ContentUris.parseId(uri);
- String where_value = " id = ?";
- String[] args = { String.valueOf(id) };
- count = studentDao.updateStudent(values, where_value, args);
- break;
- case STUDENTS:
- count = studentDao.updateStudent(values, selection,
- selectionArgs);
- break;
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- Log.i(TAG, "---->>更新成功,count="+count);
- return count;
- }
- @Override
- public Cursor query(Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
- Cursor cursor = null;
- try {
- int flag = URI_MATCHER.match(uri);
- switch (flag) {
- case STUDENT:
- long id = ContentUris.parseId(uri);
- String where_value = " id = ?";
- String[] args = { String.valueOf(id) };
- cursor = studentDao.queryStudents(where_value, args);
- break;
- case STUDENTS:
- cursor = studentDao.queryStudents(selection, selectionArgs);
- break;
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- Log.i(TAG, "---->>查詢成功,Count="+cursor.getCount());
- return cursor;
- }
- @Override
- public String getType(Uri uri) {
- int flag = URI_MATCHER.match(uri);
- String type = null;
- switch (flag) {
- case STUDENT:
- type = "vnd.android.cursor.item/student";
- Log.i(TAG, "----->>getType return item");
- break;
- case STUDENTS:
- type = "vnd.android.cursor.dir/students";
- Log.i(TAG, "----->>getType return dir");
- break;
- }
- return type;
- }
- @Override
- public Bundle call(String method, String arg, Bundle extras) {
- Log.i(TAG, "------>>"+method);
- Bundle bundle=new Bundle();
- bundle.putString("returnCall", "call被執行了");
- return bundle;
- }
- }
在AndroidManifest.xml中<application>節點中增加:
- <provider
- android:name=".StudentProvider"
- android:authorities="com.example.contentproviderdemo.StudentProvider" >
- </provider>
ContentResolver
ContentResolver,內容訪問者。可以通過ContentResolver來操作ContentProvider所暴露處理的接口。一般使用Content.getContentResolver()方法獲取ContentResolver對象。上面已經提到ContentResolver的很多方法與ContentProvider一一對應,所以它也存在insert、query、update、delete等方法。
下面就通過一個簡單的Demo來演示ContentResolver的使用,這裡就不另外新建一個項目了,在原有項目上使用Android JUnit新建一個測試類,用於測試。在另外一個項目中的操作也是一樣的。關於JUnit,不了解的朋友可以參見另外一篇:http://www.fengfly.com/plus/view-213360-1.html。
JUnit測試類:
- package com.example.contentproviderdemo;
- import android.content.ContentResolver;
- import android.content.ContentValues;
- import android.database.Cursor;
- import android.net.Uri;
- import android.os.Bundle;
- import android.test.AndroidTestCase;
- import android.util.Log;
- public class MyTest extends AndroidTestCase {
- public MyTest() {
- // TODO Auto-generated constructor stub
- }
- public void insert() {
- ContentResolver contentResolver = getContext().getContentResolver();
- Uri uri = Uri
- .parse("content://com.example.contentproviderdemo.StudentProvider/student");
- ContentValues values = new ContentValues();
- values.put("name", "Demo");
- values.put("address", "HK");
- Uri returnuir = contentResolver.insert(uri, values);
- Log.i("main", "-------------->" + returnuir.getPath());
- }
- public void delete() {
- ContentResolver contentResolver = getContext().getContentResolver();
- // 刪除多行:content://com.example.contentproviderdemo.StudentProvider/student
- Uri uri = Uri
- .parse("content://com.example.contentproviderdemo.StudentProvider/student/2");
- contentResolver.delete(uri, null, null);
- }
- public void deletes() {
- ContentResolver contentResolver = getContext().getContentResolver();
- Uri uri = Uri
- .parse("content://com.example.contentproviderdemo.StudentProvider/student");
- String where = "address=?";
- String[] where_args = { "HK" };
- contentResolver.delete(uri, where, where_args);
- }
- public void update() {
- ContentResolver contentResolver = getContext().getContentResolver();
- Uri uri = Uri
- .parse("content://com.example.contentproviderdemo.StudentProvider/student/2");
- ContentValues values = new ContentValues();
- values.put("name", "李四");
- values.put("address", "上海");
- contentResolver.update(uri, values, null, null);
- }
- public void updates() {
- ContentResolver contentResolver = getContext().getContentResolver();
- Uri uri = Uri
- .parse("content://com.example.contentproviderdemo.StudentProvider/student");
- ContentValues values = new ContentValues();
- values.put("name", "王五");
- values.put("address", "深圳");
- String where = "address=?";
- String[] where_args = { "beijing" };
- contentResolver.update(uri, values, where, where_args);
- }
- public void query() {
- ContentResolver contentResolver = getContext().getContentResolver();
- Uri uri = Uri
- .parse("content://com.example.contentproviderdemo.StudentProvider/student/2");
- Cursor cursor = contentResolver.query(uri, null, null, null, null);
- while (cursor.moveToNext()) {
- Log.i("main",
- "-------------->"
- + cursor.getString(cursor.getColumnIndex("name")));
- }
- }
- public void querys() {
- ContentResolver contentResolver = getContext().getContentResolver();
- Uri uri = Uri
- .parse("content://com.example.contentproviderdemo.StudentProvider/student");
- String where = "address=?";
- String[] where_args = { "深圳" };
- Cursor cursor = contentResolver.query(uri, null, where, where_args,
- null);
- while (cursor.moveToNext()) {
- Log.i("main",
- "-------------->"
- + cursor.getString(cursor.getColumnIndex("name")));
- }
- }
- public void calltest() {
- ContentResolver contentResolver = getContext().getContentResolver();
- Uri uri = Uri
- .parse("content://com.example.contentproviderdemo.StudentProvider/student");
- Bundle bundle = contentResolver.call(uri, "method", null, null);
- String returnCall = bundle.getString("returnCall");
- Log.i("main", "-------------->" + returnCall);
- }
- }
效果演示:
執行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>節點的屬性進行配置,一般使用如下一些屬性設置:
源碼下載
總結
以上就講解了內容提供者的簡單使用,關於內容提供者的內容涉及面很多,一篇文章無法一一說明,這裡只是簡單的定義一個內容提供者,並且說明如何操作它。這個文章的示例源碼中,聲明的內容提供者和內容訪問者均在一個項目中,可能不太利於大家理解,但是在另外一個項目中的操作,與JUnit類中的操作一致,所以這裡就沒有另外創建一個項目去說明它,不過源碼中有另外一個小項目,只是實現了Insert的操作,有興趣的朋友對照看一下就會發現其實是一樣的。
在前面的一篇文章中,簡單的介紹了一下如何實現軟鍵盤不自動彈出,使用的方法是設置android:windowSoftInput
前文簡單介紹了Android中SurfaceView的基本使用,本文就來介紹一下SurfaceView與多線程的混搭。SurfaceView與多線程混搭,是為了防
具體代碼如下: main.xml 代碼如下:<LinearLayout xmlns:android=http://schemas.android.com/
注冊很多app或者網絡賬戶的時候,經常需要手機獲取驗證碼,來完成注冊,那時年少,只是覺得手機獲取驗證碼這件事兒很好玩,並沒有關心太多,她是如何實現的,以及她背後的