ContentProvider的理解与使用

Posted sshhsun-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ContentProvider的理解与使用相关的知识,希望对你有一定的参考价值。

ContentProvider管理android以结构化方式存放的数据。他以相对安全的方式封装数据并且提供简易的处理机制。Content provider提供不同进程间数据交互的标准化接口。

本文结构

1.Content Provider简介,Uri简介,使用ContentResolver进行操作
2.开发自己的ContentProvider继承类
3.系统的ContentProvider
4.监听ContentProvider相关的数据变化(ContentObserver类)


ContentProvider简介,Uri简介,使用ContentResolver进行操作

ContentProvider是允许不同应用进行数据交换的标准的API,ContentProvider以Uri的形式对外提供数据的访问操作接口,而其他应用则通过ContentResolver根据Uri去访问指定的数据。

一旦某个应用通过ContentProvider暴露了自己的数据接口,那么不管该应用程序是否启动,其他程序都可以通过该接口来操作自己的数据接口来操作其内部的数据,包括增加数据,删除数据,修改数据,查询数据等.

URI是统一资源标识符,是一个用于标识某一互联网资源名称的字符串。 该种标识允许用户对任何(包括本地和互联网)的资源通过特定的协议进行交互操作。URI由包括确定语法和相关协议的方案所定义。由是三个组成部分:访问资源的命名机制、存放资源的主机名、资源自身的名称,由路径表示。
比如:content://edu.android.demos/t_apps 中:

content:// 使用的是content协议,属于默认规定

edu.android.demos属于自己定义的主机名,唯一标识并区分不同的ContentProvider继承类

t_apps资源部分,当访问不同的资源的时候,这部分会动态改变


ContentProvider的使用离不开Uri类的支持,在自己的继承类中使用UriMatcher,根据UriMatcher.match(Uri uri)返回的表示符,进行不同范围,不同数据集的操作。

例如:自己的继承类中的一个继承方法

@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主机部分确定的ContentProvider

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实现了底层数据的增,删,查,改的功能.

开发自己的ContentProvider继承类

说了这么多原理结论,是时候动手写写完成自己的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出来,已验证数据可以访问
结果如下 :
手机上的app
验证成功!

系统的ContentProvider

操作系统本身也提供了很多非常实用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按钮后,如下:

这里写图片描述

选择某一项
这里写图片描述

监听ContentProvider相关的数据变化(ContentObserver类)

使用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);
    }
}

短信内容发生变化后将全部短信显示。(自己可以根据实际需要进行相应处理)

以上是关于ContentProvider的理解与使用的主要内容,如果未能解决你的问题,请参考以下文章

精心收集的 48 个 JavaScript 代码片段,仅需 30 秒就可理解!(转载)

安卓 ContentProvider

ContentProvider学习笔记

ContentProvider原理分析

ContentProvider

SQLite数据库与Contentprovider