安卓四大组件之内容提供者
內容提供者ContentProvider,是Android 的四大組件之一。內容提供者是應用程序之間共享數據的接口。應用程序創建的數據庫,默認情況下是私有的,別的應用程序訪問不到數據,如果想把數據對外提供,就要用到內容提供。ContentProvider屏蔽了數據存儲的細節,內部實現對用戶完全透明, 用戶只需要關心操作數據的uri就可以了,ContentProvider可以實現不同app之間共享。 Sql也有增刪改查的方法,但是sql只能查詢本應用下的數據庫。 而ContentProvider 還可以去增刪改查本地文件/xml文件的讀取等。Android 系統將這種機制應用到方方面面,比如:聯系人(通訊錄應用程序)Provider 專為不同應用程序提供聯系人數據;短信(短信應用程序)Provider 專為不同應用程序提供系統短信信息。當應用繼承ContentProvider 類,并重寫該類用于提供數據和存儲數據的方法,就可以向其他應用共享其數據。雖然使用其他方法也可以對外共享數據,但數據訪問方式會因數據存儲的方式而不同,如:采用文件方式對外共享數據,需要進行文件操作讀寫數據;采用SharedPreferences 共享數據,需要使用SharedPreferences API 讀寫數據。而使用ContentProvider 共享數據的好處是統一了數據訪問方式。總之,內容提供者管理了對結構化數據最常見的就是數據庫中數據的訪問,操作內容提供者是不同進程之間以數據庫數據形式交互數據的標準方式。
自定義的內容提供者包括內容提供者和訪問者兩個部分。
內容提供者,擁有自己的數據庫,將數據庫暴露出來供訪問者修改。ContenProvider的編寫基本步驟:
1. 寫一個類繼承 ContentProvider;
2. 重寫一系列的方法,包括數據庫操作的空實現;
3. 在內容提供者代碼內部定義UriMatcher -用于判斷uri是否匹配
static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
mUriMatcher.addURI("在清單文件里面定義的authorities", "自定義匹配字符串", 成功返回的標識);
}
4. 在增刪改查執行的時候判斷uri是否合法,在內容提供者內部實現對數據庫的增刪改查;
5. 清單文件的下面聲明provider,這里需要指定主機名,也就是對外提供的Uri,當訪問者在內容解析者中傳入同一個uri時,才可以訪問到數據庫;
<provider
android:name="com.itheima.db.BankDBBackdoor"
android:authorities="自定義主機名" >
</provider>
訪問者,存在于另外一個工程中,可以對內提供者的數據庫進行操作。
1. 創建內容提供者解析器
ContentResolver resolver = 上下文.getContentResolver();
2.定義要訪問的Uri路徑
Uri uri = Uri.parse("content://自定義主機名/自定義匹配字符串") // “content://”是標準寫法
3.利用內容提供者解析器進行增刪改查,實現對數據庫的操作
內容提供者Uri 的書寫模板: content:// 主機名authority/path/id。具體的書寫規范如下所示:
1."content://" 這是個固定寫法,用來說明一個ContentProvider 控制這些數據。
2.主機名或授權Authority:它定義了是哪個ContentProvider 提供這些數據。
3.path:路徑,URI 下的某一個Item。
4.ID:通常定義Uri 時使用”#”號占位符代替, 使用時替換成對應的數字。#表示數據id,#代表任意數字,*用來來匹配任意文本
使用內容提供者操作系統短信和操作系統聯系人是我們開發中經常遇到的需求,而自定義內容提供者對外提供數據反而使用的場景并不多,除非我們開發的短信或者聯系人應用。一個小細節是,由于讀取和插入系統短信數據庫都涉及到可能侵犯用戶隱私,因此創建的工程必須添加相關的權限。下面就分別講解一下使用內容提供者操作系統數據庫和自定義內容提供者。
用內容提供者操作系統短信只需要關注的到系統短信數據庫的一張表,最長用的數據有body,date,type,address,各自的含義也較為直觀。body表示短信的內容,date表示發送短信或收到短信的時間,type表示是受到短信還是發送短信,address表示收到的短信來自于哪個手機號。讀取和插入系統短信數據庫需要添加如下權限:
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.WRITE_SMS"/>
使用內容觀察者操作系統聯系人時需要關注三張張數據表,rawcontact表 ,data表和 mimetype表。其中raw_contacts 表存放的是聯系人id 信息,一個聯系人就是表中的一行記錄。ontact_id在表單里應該是唯一的存在, 所以在插入的時候需要先查詢最后一條id是多少,然后在此基礎上加一。data 表中存放的是raw_contacts中的每一條id 對應的具體信息,一個聯系人可能有電話、郵件、姓名等多條信息,每一條信息在該表中都是一行記錄。為了區分不同信息的類型,因此還有一個mimetypes 表,該表存儲的是常量數據,不同類型的信息由mimetype_id 來標識。現在很多App 都可以對系統聯系人進行操作,這樣就可以直接將號碼添加到系統聯系人中,可以關聯/備份/恢復系統聯系人。讀取聯系人信息的基本步驟是,首先查詢rawcontact表,獲取聯系人的contactid,在rawcontact表中并不是每一個contact_id對應一條信息,而是一個contact_id對應多條信息,這樣可以存儲更多的信息。其次查詢根據contact_id查詢data表,獲取聯系人的數據 data1、mimetype,前者存儲相關數據,后者存儲該數據對應得數據。最后根據mimetype類型確定數據類型。修改聯系人的操作是往raw_contacts 表中插入一個id,值為n+1,作為一條新的記錄,然后往data1 表中插入具體的數據,其中id 必須為n+1。讀取和插入系統聯系人數據庫需要添加如下權限:
<uses-permission android:readPermission="android.permission.READ_CONTACTS"
<uses-permission android:writePermission="android.permission.WRITE_CONTACTS"
那么怎么才能獲取短信和聯系人的Uri呢。這時就需要看源碼了。打開Android 系統源碼,其中TelephonyProvider 就是短信的內容提供者文件。打開TelephonyProvider 下的src 文件,查看java 文件,其中的SmsProvider.java 即短信息內容提供者邏輯代碼。通過查找系統源碼,可以確定短信息內容提供者的Uri 應該為:”content://sms”。用同樣的方法,可以查到聯系人內容提供者的Uri路徑。打開Android 源碼,查看packagesproviders路徑下的文件,其中ContactsProvider 就是聯系人的內容提供者。打開ContactsProvider2.java文件,查看此內容提供者的uri 路徑。根據源碼,確定內容提供者的Uri 信息為:操作raw_contacts 表的Uri:content://com.android.contacts/raw_contacts。操作data 表的Uri:content://com.android.contacts/data。其實,平常寫代碼時不必要這么復雜,直接把Uri路徑拿來就可以用了。另外要注意的是,由于聯系人數據庫使用了視圖,所以操作數據庫表時,看到的表字段名稱和真實操作的有所不同。比如:data 表在查詢的時候沒有mimetype_id 字段,取代的是mimetype 字段。
再來講一講內容觀察者。內容提供者相當于一個監聽。觀察數據庫內容是否發生改變,如果改變,通知觀察者。內容觀察者ContentObserver,目的是觀察(捕捉)特定Uri 引起的數據庫的變化,繼而做一些相應的處理,它類似于數據庫技術中的觸發器(Trigger),當ContentObserver 所觀察的Uri 發生變化時,便會觸發它。觸發器分為表觸發器、行觸發器,相應的ContentObserver 也分為“表ContentObserver”、“行ContentObserver”,當然這是與它所監聽的Uri MIME Type 有關的。
內容觀察者的使用步驟:
1.在內容提供者類增加通知方法
getContext().getContentResolver().notifyChange(uri, null);
2.在觀察者類注冊觀察
//定義觀察的uri ,和內容提供者的Uri一直
Uri uri = Uri.parse("content://...");
//注冊觀察者
getContentResolver().registerContentObserver(uri, true, ContentObserver);
最后通過案例來演示今天的知識點。
案例一: 內容提供者操作短信
java代碼
/*
* 添加權限
* <uses-permission android:name="android.permission.READ_SMS"/>
* <uses-permission android:name="android.permission.WRITE_SMS"/>
*/
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**利用內存提供者添加短信*/
public void add(View view) {
Uri uri = Uri.parse("content://sms");
ContentResolver resolver = getContentResolver();
ContentValues values = new ContentValues();
values.put("address", "00000");
values.put("date", System.currentTimeMillis());
values.put("type", 1); // 表示收短信還是發短信
values.put("body", "看到短信表示利用內容提供者添加短信成功");
resolver.insert(uri, values);
}
/**利用內存提供者刪除短信*/
public void delete(View view) {
Uri uri = Uri.parse("content://sms");
ContentResolver resolver = getContentResolver();
resolver.delete(uri, "address = ?", new String[] {"00000"});
}
}
布局文件中只定義了兩個按鈕
<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"
android:orientation="vertical" >
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="add"
android:text="添加短信" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="delete"
android:text="刪除短信" />
</LinearLayout>
效果展示:
添加短息:
刪除短信:
案例二: 內容提供者操作聯系人
JAVA代碼,主程序:
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.example.contact.domain.ContactInfo;
import com.example.contact.utils.ContactUtils;
//添加權限<uses-permission android:name="android.permission.READ_CONTACTS"/>
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private ListView listview;
private List<ContactInfo> contactlist;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listview = (ListView) findViewById(R.id.listview);
contactlist = ContactUtils.getContact(this);
Log.i(TAG, "info" + contactlist.size());
for (ContactInfo info : contactlist) {
Log.i(TAG, "info" + info.toString());
}
listview.setAdapter(new ContactAdapter());
}
public class ContactAdapter extends BaseAdapter {
private ContactInfo contactInfo;
@Override
public int getCount() {
return contactlist.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
viewHolder holder;
if (convertView == null) {
holder = new viewHolder();
convertView = View.inflate(MainActivity.this,
R.layout.item_contact, null);
holder.tv_text = (TextView) convertView
.findViewById(R.id.tv_text);
convertView.setTag(holder);
} else {
holder = (viewHolder) convertView.getTag();
}
contactInfo = contactlist.get(position);
holder.tv_text.setText(contactInfo.toString());
return convertView;
}
}
public class viewHolder {
TextView tv_text;
}
}
聯系人工具類:
public class ContactInfo {
private String name;
private String phone;
private String email;
private String qq;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getQq() {
return qq;
}
public void setQq(String qq) {
this.qq = qq;
}
@Override
public String toString() {
return "ContactInfo [name=" + name + ", phone=" + phone + ", email="
+ email + ", qq=" + qq + "]";
}
}
獲取系統聯系人:
import java.util.ArrayList;
import java.util.List;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import com.example.contact.domain.ContactInfo;
public class ContactUtils {
public static List<ContactInfo> getContact(Context context) {
List<ContactInfo> list = new ArrayList<ContactInfo>();
ContentResolver resolver = context.getContentResolver();
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
Uri datauri = Uri.parse("content://com.android.contacts/data");
Cursor cursor = resolver.query(uri, new String[] { "contact_id" },
null, null, null);
while (cursor.moveToNext()) {
String id = cursor.getString(0);
System.out.println("00"+id);
if (id != null) {
ContactInfo info = new ContactInfo();
Cursor datacursor = resolver.query(datauri, new String[] {
"data1", "mimetype" }, "raw_contact_id = ?",
new String[] { id }, null);
while (datacursor.moveToNext()) {
String data1 = datacursor.getString(0);
String mimetype = datacursor.getString(1);
if ("vnd.android.cursor.item/name".equals(mimetype)) {
info.setName(data1);
} else if ("vnd.android.cursor.item/im".equals(mimetype)) {
info.setQq(data1);
} else if ("vnd.android.cursor.item/email_v2"
.equals(mimetype)) {
info.setEmail(data1);
} else if ("vnd.android.cursor.item/phone_v2"
.equals(mimetype)) {
info.setPhone(data1);
}
}
datacursor.close();
list.add(info);
}
}
cursor.close();
return list;
}
}
布局文件:
<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=".MainActivity" >
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
成果展示:
案例三: 內容提供者修改數據庫。通過上述知識我們知道當需要對另外一個應用程序的數據庫操作的時候可以用到內容提供者,那到底內容提供者內部是如何工作的呢?在這里給出一個案例來說明內容提供者的工作機制。首先,創建一個工程,在這個工程中新建一個數據庫,單獨寫一個類繼承ContentProvider,我們稱之為后門程序,重寫其中一系列的方法。其中包括對數據增刪改查。但此處的數據庫操作方法是空實現,當我們在另一工程中為內容解析者指定同一Uri路徑時。調用內容解析者的增刪改查方法時,會自動對該數據哭庫操作。同時,還可以在后門程序中設置內容監聽者,這樣可以隨時觀察到數據的變化。
內容提供者的主程序,不用操作任何邏輯:
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
內容提供者中創建數據庫:
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
public class MyDBOpenhelper extends SQLiteOpenHelper {
public MyDBOpenhelper(Context context) {
super(context, "test.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table account (_id integer primary key autoincrement,name varchar(20),number varchar(20))");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
寫一個類ContentProvider:
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
public class BackDoor extends ContentProvider {
private static final int SUCCESS = 1;
/** 判斷Uri規則 */
static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
mUriMatcher.addURI("com.exmple.text", "account", SUCCESS); //uri規則可自己定義,但一定和清單文件一直
}
@Override
public boolean onCreate() {
return false;
}
/** 增刪改查為空實現 */
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
int code = mUriMatcher.match(uri); // 判斷Uri是否合法
if (code == SUCCESS) {
System.out.println("查詢數據");
MyDBOpenhelper helper = new MyDBOpenhelper(getContext());
SQLiteDatabase db = helper.getReadableDatabase();
return db.query("account", projection, selection, selectionArgs,
null, null, sortOrder);
} else {
throw new IllegalArgumentException("路徑不正確");
}
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
int code = mUriMatcher.match(uri);
if (code == SUCCESS) {
System.out.println("添加數據");
MyDBOpenhelper helper = new MyDBOpenhelper(getContext());
SQLiteDatabase db = helper.getWritableDatabase();
db.insert("account", null, values);
getContext().getContentResolver().notifyChange(uri, null); // 內容觀察者檢測數據庫是否更改
} else {
throw new IllegalArgumentException("路徑不正確");
}
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int code = mUriMatcher.match(uri);
if (code == SUCCESS) {
System.out.println("刪除數據");
MyDBOpenhelper helper = new MyDBOpenhelper(getContext());
SQLiteDatabase db = helper.getWritableDatabase();
db.delete("account", selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null); // 內容觀察者檢測數據庫是否更改
} else {
throw new IllegalArgumentException("路徑不正確");
}
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int code = mUriMatcher.match(uri);
if (code == SUCCESS) {
System.out.println("更新數據");
MyDBOpenhelper helper = new MyDBOpenhelper(getContext());
SQLiteDatabase db = helper.getWritableDatabase();
db.update("account", values, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null); // 內容觀察者檢測數據庫是否更改
} else {
throw new IllegalArgumentException("路徑不正確");
}
return 0;
}
}
配置文件中添加provider節點:
<!-- 注冊內容提供者數據 -->
<provider
android:name="com.example.provider.BackDoor"
android:authorities="com.exmple.text" >
</provider>
調用者的主程序:
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 利用后門程序 添加一條數據
*/
public void insert(View view) {
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://com.exmple.text/account");
ContentValues values = new ContentValues();
values.put("name", "zhangsan");
values.put("number", 10000);
resolver.insert(uri, values);
}
/**
* 利用后門程序 刪除一條數據
*/
public void delete(View view) {
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://com.exmple.text/account");
resolver.delete(uri, "name=?", new String[] { "zhangsan" });
}
/**
* 利用后門程序 修改數據
*/
public void update(View view) {
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://com.exmple.text/account");
ContentValues values = new ContentValues();
values.put("number", 20000);
resolver.update(uri, values, "name=?", new String[] { "zhangsan" });
}
/**
* 利用后門程序 查詢數據
*/
public void query(View view) {
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://com.exmple.text/account");
Cursor cursor = resolver.query(uri, new String[] { "name", "number" },
null, null, null);
while (cursor.moveToNext()) {
String name = cursor.getString(0);
float number = cursor.getFloat(1);
System.out.println("name:" + name + "----" + "number:" + number);
}
cursor.close();
}
}
調用者的布局文件:
<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"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="insert"
android:text="增" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="delete"
android:text="刪" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="update"
android:text="改" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="query"
android:text="查" />
</LinearLayout>
運行結果:
總結
以上是生活随笔為你收集整理的安卓四大组件之内容提供者的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MCC码怎么看
- 下一篇: hiredis的安装