本文结构: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); } }
短信内容发生变化后将所有短信显示。(本身能够根据实际须要进行相应处理)