本文結構:java
1.Content Provider簡介,Uri簡介,使用ContentResolver進行操做
2.開發本身的ContentProvider繼承類
3.系統的ContentProvider
4.監聽ContentProvider相關的數據變化(ContentObserver類)android
ContentProvider是容許不一樣應用進行數據交換的標準的API,ContentProvider以Uri的形式對外提供數據的訪問操做接口,而其餘應用則經過ContentResolver根據Uri去訪問指定的數據。數據庫
一旦某個應用經過ContentProvider暴露了本身的數據接口,那麼無論該應用程序是否啓動,其餘程序均可以經過該接口來操做本身的數據接口來操做其內部的數據,包括增長數據,刪除數據,修改數據,查詢數據等.安全
URI是統一資源標識符,是一個用於標識某一互聯網資源名稱的字符串。 該種標識容許用戶對任何(包括本地和互聯網)的資源經過特定的協議進行交互操做。URI由包括肯定語法和相關協議的方案所定義。由是三個組成部分:訪問資源的命名機制、存放資源的主機名、資源自身的名稱,由路徑表示。
好比:content://edu.Android.demos/t_apps 中:app
content:// 使用的是content協議,屬於默認規定ssh
edu.android.demos屬於本身定義的主機名,惟一標識並區分不一樣的ContentProvider繼承類ide
t_apps資源部分,當訪問不一樣的資源的時候,這部分會動態改變ui
ContentProvider的使用離不開Uri類的支持,在本身的繼承類中使用UriMatcher,根據UriMatcher.match(Uri uri)返回的表示符,進行不一樣範圍,不一樣數據集的操做。this
例如:本身的繼承類中的一個繼承方法 @Override public String getType(Uri uri) { int code = matcher.match(uri); String type = null; if (code == ALL_APP) { type = "vnd.android.cursor.dir/t_apps";// dir表明多行數據 } else if (code == 2) { type = "vnd.android.cursor.item/t_apps";// item單行 } return type; }
一旦定義好本身的ContentProvider類,就可使用ContentResolver進行訪問操做了。
ContentResolver類的方法都會在其內部調用URI主機部分肯定的ContentProviderspa
ContentResolver的內部方法的實現:
以ContentResolver.query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)爲例。
public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { IContentProvider provider = acquireProvider(uri); if (provider == null) { return null; } try { long startTime = SystemClock.uptimeMillis(); Cursor qCursor = provider.query(uri, projection, selection, selectionArgs, sortOrder); if (qCursor == null) { releaseProvider(provider); return null; } // force query execution qCursor.getCount(); long durationMillis = SystemClock.uptimeMillis() - startTime; maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder); // Wrap the cursor object into CursorWrapperInner object return new CursorWrapperInner(qCursor, provider);
能夠發現,其內部檢查了provider的存在,若是存在就調用該ContentProvider的query方法。
說了這麼多原理結論,是時候動手寫寫完成本身的ContentProvider類來完成對數據的操做。
1.首先定義一個本身的ContentProvider類,並在manifest文件中配置相關主機名和讀寫權限
package edu.android.demos.chap14; import edu.android.demos.chap13.AppService; import edu.android.demos.chap13.DBHelper; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.CursorWrapper; import android.database.MatrixCursor; import android.database.MergeCursor; import android.net.Uri; import android.test.mock.MockCursor; import android.util.Log; public class AppContentProvider extends ContentProvider { public static final int ALL_APP = 1; UriMatcher matcher; DBHelper help; @Override public boolean onCreate() { help = new DBHelper(getContext()); matcher = new UriMatcher(UriMatcher.NO_MATCH); matcher.addURI("edu.android.demos", "t_apps", ALL_APP); matcher.addURI("edu.android.demos", "t_apps/#", 2);// #匹配全部數字,*匹配全部字符 return false; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { int code = matcher.match(uri); Log.e("query", "getInquery"); Cursor cursor = null; // MatrixCursor mc=new MatrixCursor(new String[]{"id","name"}); // mc.addRow(new Object[]{new String[]{"101","andy"}}); if (code == ALL_APP) { cursor = help.getReadableDatabase().query( "t_apps", new String[] { "id", "name", "package_name", "activity_name", "icon", "cate_id" }, selection, selectionArgs, null, null, sortOrder); } else if (code == 2) { long id = ContentUris.parseId(uri);// 從uri中取出id cursor = help.getReadableDatabase().query( "t_apps", new String[] { "id", "name", "package_name", "acitivty_name", "icon", "cate_id" }, "id=?", new String[] { String.valueOf(id) }, null, null, sortOrder); } return cursor; } @Override public String getType(Uri uri) { int code = matcher.match(uri); String type = null; if (code == ALL_APP) { type = "vnd.android.cursor.dir/t_apps";// dir表明多行數據 } else if (code == 2) { type = "vnd.android.cursor.item/t_apps";// item單行 } return type; } @Override public Uri insert(Uri uri, ContentValues values) { int code = matcher.match(uri); if (code != ALL_APP && code != 2) { throw new RuntimeException("地址不能匹配"); } long id = help.getWritableDatabase().insert("t_apps", null, values); return ContentUris.withAppendedId(uri, id);// 返回值表明訪問新添加數據的uri } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int code = matcher.match(uri); if (code == ALL_APP) { throw new RuntimeException("不能刪除全部數據"); } else if (code == 2) { long id = ContentUris.parseId(uri); help.getWritableDatabase().delete("t_apps", "id=?", new String[] { id + "" }); } return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int code = matcher.match(uri); int row = 0; if (code == 2) { long id = ContentUris.parseId(uri); row = help.getWritableDatabase().update("t_apps", values, "id=?", new String[] { id + "" }); } return row; } }
在這以前已經建立好相關的數據庫,這是一個存儲一臺手機上有多少個應用的數據庫
for (ResolveInfo info : infos) { appInfo = new ApplicationInfo();// 建立應用程序信息對象 appInfo.setId(i++); appInfo.setName(info.loadLabel(pm).toString()); appInfo.setPackageName(info.activityInfo.packageName); appInfo.setActivityName(info.activityInfo.name); appInfo.setIcon(info.activityInfo.loadIcon(pm)); appInfo.setCateId(101); appService.save(appInfo);// appService是我以前寫好的一個數據庫操做者,save用於保存對象到數據庫中。 }
manifest文件中配置一下
<provider android:name=".chap14.AppContentProvider" android:authorities="edu.android.demos" android:readPermission="edu.android.demos.permission.READ_APPS" > </provider> <!--authorities:主機名,用於區分ContentProvider--> <!--readPermission:這是訪問權限,其餘應用想要訪問必須設置該權限!-->
2.在別的程序中進行數據訪問
<uses-permission android:name="edu.android.demos.permission.READ_APPS" />
package com.sshhsun.contentprovidertest; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import com.sshhsun.utils.LogUtils; public class MainActivity extends ActionBarActivity { private Button start; private Button pics; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); start = (Button) findViewById(R.id.button1); pics=(Button) findViewById(R.id.button2); start.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Uri uri = Uri.parse("content://edu.android.demos/t_apps"); Cursor cursor = getContentResolver().query(uri, null, null, null, null); while (cursor.moveToNext()) { String name = cursor.getString(cursor .getColumnIndex("name")); String package_name = cursor.getString(cursor .getColumnIndex("package_name")); String activity_name = cursor.getString(cursor .getColumnIndex("activity_name")); int id = cursor.getInt(cursor.getColumnIndex("id")); LogUtils.i(cursor, id + ":" + name + ":" + package_name + ":" + activity_name); LogUtils.i(cursor, "================================="); } } }); pics.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent=new Intent(); intent.setClass(MainActivity.this, PicActivity.class); startActivity(intent); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
在代碼中我將查詢到應用信息所有Log出來,已驗證數據能夠訪問
結果以下 :
驗證成功!
操做系統自己也提供了不少很是實用Provider,咱們能夠好好利用一下,已達到事半功倍。
這裏以系統的」Media.EXTERNAL_CONTENT_URI」來訪問一下系統中全部SD卡上圖片信息。
首先查詢一下系統中」com.android.provider.media」中的數據庫文件,熟悉一下大體內容:
好的,以後使用這個URI查詢全部的圖片信息,獲得名稱,修改時間,已經絕對路徑。並以listView展現,點擊item顯示該圖片。
代碼以下:
package com.sshhsun.contentprovidertest; import java.io.ByteArrayInputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; import android.app.AlertDialog; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.provider.MediaStore.Images.Media; import android.support.v7.app.ActionBarActivity; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import com.sshhsun.utils.LogUtils; import com.sshhsun.utils.PicBean; import com.sshhsun.utils.ToastUtils; public class PicActivity extends ActionBarActivity implements OnClickListener { private Button start; private Button clear; private ListView mlv; private BaseAdapter adapter; private List<PicBean> pics; private LayoutInflater inflater; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.pic_activity); pics = new ArrayList<PicBean>(); inflater = LayoutInflater.from(this); mlv = (ListView) findViewById(R.id.lv_pic); start = (Button) findViewById(R.id.btn_start); start.setOnClickListener(this); clear = (Button) findViewById(R.id.btn_clear); clear.setOnClickListener(this); mlv.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String data = pics.get(position).getData(); Bitmap map2 = BitmapFactory.decodeFile(data); View view2 = getLayoutInflater().inflate(R.layout.show, null); ((ImageView) view2.findViewById(R.id.image)) .setImageBitmap(map2); new AlertDialog.Builder(PicActivity.this).setView(view2) .setPositiveButton("肯定", null).show(); } }); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_start: PicBean pic; // Cursor cursor = getContentResolver().query( // Media.INTERNAL_CONTENT_URI, null, null, null, null); Cursor cursor = getContentResolver().query( Media.EXTERNAL_CONTENT_URI, null, null, null, "date_modified desc"); while (cursor.moveToNext()) { String name = cursor.getString(cursor .getColumnIndex(Media.DISPLAY_NAME)); String desc = cursor.getString(cursor .getColumnIndex(Media.DATE_TAKEN)); String data = cursor.getString(cursor .getColumnIndex(Media.DATA)); pic = new PicBean(name, desc, data); pics.add(pic); } if (pics == null) { ToastUtils.ShowToast(PicActivity.this, "沒有圖片"); return; } adapter = new BaseAdapter() { @Override public View getView(int position, View convertView, ViewGroup parent) { PicBean picc = pics.get(position); convertView = inflater .inflate(R.layout.item_listview, null); ((TextView) convertView.findViewById(R.id.name)) .setText(picc.getName()); SimpleDateFormat sDateFormat = new SimpleDateFormat( "yyyy-MM-dd hh:mm:ss"); Long ldate = Long.valueOf(picc.getDesc()); String date = sDateFormat.format(ldate); ((TextView) convertView.findViewById(R.id.desc)) .setText(date); return convertView; } @Override public long getItemId(int position) { return position; } @Override public Object getItem(int position) { return pics.get(position); } @Override public int getCount() { return pics.size(); } }; mlv.setAdapter(adapter); break; case R.id.btn_clear: pics.clear(); adapter.notifyDataSetChanged(); default: break; } } }
效果以下:
點擊START按鈕後,以下:
選擇某一項
使用ContentObserver能夠監測某一數據項的變化,當其中的內容發生變化是會自動調用其中的 public void onChange(boolean selfChange) {}方法。
好比能夠監聽手機中的短信變化,在變化後進行處理:
package edu.android.demos.chap14; import android.app.Activity; import android.app.Service; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.provider.CallLog.Calls; import android.util.Log; import android.widget.TextView; import edu.android.demos.R; public class TestObserverActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.chap14_observer); //建立內容handler,用於內容觀察者向activity發送消息 Handler handler = new Handler() { @Override public void handleMessage(Message msg) { StringBuilder s = (StringBuilder) msg.obj; ((TextView) findViewById(R.id.observer_show_sms)).setText(s .toString()); } }; //註冊內容觀察者,監聽短信數據變化 getContentResolver().registerContentObserver( Uri.parse("content://sms"), true, new SMSObserver(handler, this)); } } //實現內容觀察者須要繼承ContentObserver class SMSObserver extends ContentObserver { Context context; Handler handler; public SMSObserver(Handler handler, Context context) { super(handler); this.handler = handler; this.context = context; } //當觀察的內容發生變化是會觸發該方法,內容發生變化是在該方法中作相應處理 @Override public void onChange(boolean selfChange) { ContentResolver resolver = context.getContentResolver(); Cursor c = resolver.query(Uri.parse("content://sms"), null, null, null, null); StringBuilder sb = new StringBuilder(); while (c.moveToNext()) { sb.append("發件人手機號碼: " + c.getString(c.getColumnIndex("address"))) .append("信息內容: " + c.getString(c.getColumnIndex("body"))) .append("是否查看: " + c.getString(c.getColumnIndex("read"))) .append("發送時間:" + String.format("%tF %<tT", c.getLong(c.getColumnIndex("date")))) .append("\n"); } Log.e("SMSObserver", sb.toString()); Message msg = new Message(); msg.obj = sb;//將讀到的信息使用msg,傳遞給activity handler.sendMessage(msg); } }
短信內容發生變化後將所有短信顯示。(本身能夠根據實際須要進行相應處理)