Android点将台:济世儒侠[-ContentProvider-]

3,677 阅读15分钟

个人所有文章整理在此篇,将陆续更新收录:知无涯,行者之路莫言终(我的编程之路)

零、前言

本文聚焦
[1]通过短信认识ContentProvider的查询功能
[2]通过图片查询了解ContentProvider插入、修改、更新、查找等操作
[3]查询联系人看一下两个表之间该怎么办
[4]简单看一下Android系统如何实现短信的ContentProvider
[5]如何自定义一个ContentProvider,来给别的应用使用

一、ContentProvider的查询功能(短信为例)

权限自理:<uses-permission android:name="android.permission.READ_SMS"/>

展示.png


1:实体类
/**
 * 作者:张风捷特烈
 * 时间:2018/4/12:16:46
 * 邮箱:1981462002@qq.com
 * 说明:短信实体类
 */
class SMSBean {
    var address: String? = null//短信发送方
    var name: String? = null//号码在通讯录中的姓名:无为null
    var date: String? = null//短信时间
    var body: String? = null//短信内容
    var type: Int = 0//1 接收短信 2 发送短信
    var thread_id: Int = 0//同一个手机号互发的短信,其序号是相同的
}

2.查询处理

ContentProvider查询操作.png

/**
 * 获取短信:SMSBean:address发信人  date时间  body信息内容
 *
 * @param ctx 上下文
 * @return 短信bean集合 注意添加读取短信权限
 */
public static List<SMSBean> getSMS(Context ctx) {
    List<SMSBean> smsBeans = new ArrayList<>();
    //[1.]获得ContentResolver对象
    ContentResolver resolver = ctx.getContentResolver();
    //[2.1]得到Uri :访问raw_contacts的url
    Uri uri = Uri.parse("content://sms");
    //[3]查询表,获得sms表游标结果集
    String[] projection = {"address", "date", "body", "type", "person", "thread_id"};//访问表的字段
    Cursor cursor = resolver.query(
            uri, projection, null, null, null);
    while (cursor.moveToNext()) {//遍历游标,获取数据,储存在bean中
        SMSBean smsBean = new SMSBean();
        smsBean.setAddress(cursor.getString(cursor.getColumnIndex("address")));
        smsBean.setDate(cursor.getString(cursor.getColumnIndex("date")));
        smsBean.setBody(cursor.getString(cursor.getColumnIndex("body")));
        smsBean.setType(cursor.getInt(cursor.getColumnIndex("type")));
        smsBean.setName(cursor.getString(cursor.getColumnIndex("person")));
        smsBean.setThread_id(cursor.getInt(cursor.getColumnIndex("thread_id")));
        smsBeans.add(smsBean);
    }
    //[4] 关闭cursor
    cursor.close();
    return smsBeans;
}

读取短信.png


3.关于查询的五个参数

sql的基础知识

* @param uri 资源地址
The URI, using the content:// scheme, for the content to retrieve.

* @param projection 想要查询的列, null查询所有列,最低效
A list of which columns to return. Passing null will return all columns, which is inefficient.

* @param selection 查询过滤语句(不用加where)
A filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE itself).
Passing null will return all rows for the given URI.

* @param selectionArgs 查询过滤语句中的参数
You may include ?s in selection, which will be replaced by the values from selectionArgs, 
in the order that they appear in the selection. The values will be bound as Strings.

* @param sortOrder 排序
How to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself).
Passing null will use the default sort order, which may be unordered.

下面亲测可用,就补贴图了

|-- 查询10086发来的信息
Cursor cursor = resolver.query(
                uri, projection, "address=10086", null, null);

|-- 查询指定号码发来的信息
public static List<SMSBean> getSMSByPhone(Context ctx, String phone) {
    ...
    Cursor cursor = resolver.query(
            uri, projection, "address=?", new String[]{phone}, null);
    ...
}

|-- 按时间倒序排序
Cursor cursor = resolver.query(
        uri, projection, "address=?", new String[]{phone}, "date desc");


|-- 按时间倒序排序 + 取前八条数据
Cursor cursor = resolver.query(
        uri, projection, "address=?", new String[]{phone}, "date desc  limit 0,8");

二、关于多媒体的ContentProvider操作(图片为例)

media作为手机的三鼎之一,自然是少不了内容提供者来向外界暴漏信息
主要储存在external.db(外部)internal.db(内部)两个数据库中
数据库中图片的主要字段有:

_id:id标识             _data: 图片绝对路径         _size: 图片大小            mime_type: 类型
data_added:添加的时间   data_modifide:最后修改时间  _display_name:显示名称     description:描述
width:宽                height:高

media的内容提供者数据库.png


1.获取内容提供者并添加一条自定义信息的图片
private void insertImg() {
    //1.创建ContentValues对象,记录插入照片信息
    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.DISPLAY_NAME, "张风捷特烈");
    values.put(MediaStore.Images.Media.DESCRIPTION, "天下无双");
    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
    //2.获取内容提供者,插入(外部图片存储Uri,values),返回插入图片的Uri
    Uri imgFileUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
    L.d(imgFileUri + L.l());//content://media/external/images/media/1064830
    //3.通过打开图片的意图添加额外信息将imgFileUri发送给系统相机
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imgFileUri);
    startActivity(intent);
}

2.通过内容提供者读取刚才插入的图片
 private void readImg() {
     try {
         //1.获取内容提供者,通过刚才的Uri打开输入流
         Uri imgUri = Uri.parse("content://media/external/images/media/1064830");
         InputStream is = getContentResolver().openInputStream(imgUri);
         //2.将图片解码展示
         Bitmap bitmap = BitmapFactory.decodeStream(is);
         mImageView.setImageBitmap(bitmap);
     } catch (FileNotFoundException e) {
         e.printStackTrace();
     }
 }

3.更新描述信息
private void updateImg() {
    ContentValues values = new ContentValues(2);
    values.put(MediaStore.Images.Media.DISPLAY_NAME, "Toly");
    values.put(MediaStore.Images.Media.DESCRIPTION, "Only Me");
    //1.获取内容提供者,通过刚才的Uri打开输入流
    Uri imgUri = Uri.parse("content://media/external/images/media/1064830");
    getContentResolver().update(imgUri, values, null, null);
}

4.查表

既然是内容提供者,玩个表再所难免,验证上面的修改方法是否成功

private void queryImg() {
    //1.查询获得游标
    Cursor cursor = getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, "_id=1064830",
            null, null, null);
    //2.让游标移动,到尾为止
    while (cursor.moveToNext()) {
        String filePath = cursor.getString(cursor.getColumnIndex("_data"));
        String name = cursor.getString(cursor.getColumnIndex("_display_name"));
        String description = cursor.getString(cursor.getColumnIndex("description"));
        L.d(filePath + L.l());//storage/emulated/0/DCIM/Camera/1539834068417.jpg
        L.d(name + L.l());//Toly   
        L.d(description + L.l());//Only Me   
    }

5.删除
private void deleteImg() {
    Uri imgUri = Uri.parse("content://media/external/images/media/1064830");
    int delete = getContentResolver().delete(imgUri, "_id=1064830", null);
    L.d(delete + L.l());//1 表示删除了1行
}

6.获取所有图片的路径

一共12540张图片,方法耗时:1.289秒,属于耗时操作应该放在子线程
可以获取数据库中的字段,封装一个图片的实体类,以便使用

private ArrayList<String> queryAllImg() {
    ArrayList<String> imgPaths = new ArrayList<>();
    //1.查询获得游标
    Cursor cursor = getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, "",
            null, null, null);
    //2.让游标移动,到尾为止
    while (cursor.moveToNext()) {
        String filePath = cursor.getString(cursor.getColumnIndex("_data"));
        imgPaths.add(filePath);
    }
    return imgPaths;
}

查询所有图片.png

7.显示最近100张图片

为了简便,使用Picasso来加载图片--详情可见:开源框架之[-Picasso-]应用篇

查询最近100张图片.png


7.1.获取最近100条数据库记录

排序条件:"date_added desc"表示根据date_added字段倒序查询
将数据盛放在List中,并根据列表元素个数来决定跳出while循环

private ArrayList<String> queryPic100() {
    ArrayList<String> imgPaths = new ArrayList<>();
    //1.查询获得游标
    String queryCol = MediaStore.Images.Media.DATE_ADDED;
    Cursor cursor = getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, "",
            null, "date_added desc", null);
    //2.让游标移动,到尾为止
    while (cursor.moveToNext()) {
        if (imgPaths.size() >= 100) {
            break;
        }
        String filePath = cursor.getString(cursor.getColumnIndex("_data"));
        imgPaths.add(filePath);
    }
    return imgPaths;
}

7.2.RecyclerView的简单使用(布局很简单就免了)

1).创建适配器类和ViewHolder
2).设置RecyclerView样式

/**
 * 适配器
 */
class PicRVAdapter extends RecyclerView.Adapter<PicViewHolder> {
    @NonNull
    @Override
    public PicViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(Pic100Activity.this).inflate(R.layout.item_img, null);
        return new PicViewHolder(view);
    }
    @Override
    public void onBindViewHolder(@NonNull PicViewHolder holder, int position) {
        //使用Picasso加载文件图片
        Picasso.get().setIndicatorsEnabled(true);
        Picasso.get()
                .load(new File(mList.get(position)))//文件
                .resize(200,200)//重设尺寸
                .into(holder.mIv_icon);
    }
    @Override
    public int getItemCount() {
        return mList.size();
    }
}

/**
 * ViewHolder
 */
public class PicViewHolder extends RecyclerView.ViewHolder {
    public final ImageView mIv_icon;
    /**
     * itemView为MyViewHolder中onCreateViewHolder加载的布局
     *
     * @param itemView 条目
     */
    public PicViewHolder(View itemView) {
        super(itemView);
        mIv_icon = itemView.findViewById(R.id.id_iv_item);
    }
}
//1.设置适配器
mIdRvPic100.setAdapter(new PicRVAdapter());
//2.!!创建布局管理器
mGLM = new GridLayoutManager(this, 4, GridLayoutManager.VERTICAL, false);
//3.!!!设置布局管理器
mIdRvPic100.setLayoutManager(mGLM);

MediaStore是一个为例方便操作媒体ContentProvider而给出的类,
图片、音频、视频三大顶梁柱都涉及了,所以图片会操作,那两个也不在话下

MediaStore.png


三、通讯录的查询

权限自理:<uses-permission android:name="android.permission.READ_CONTACTS"/>

1.实现分析:

raw_contacts表中查到contact_id字段,在每个contact_id下,根据contact_id查询data表字段,
然后判断mimetype的值,新建实体类,将数据设置到实体中,将实体放入实体集合,查完返回集合。

联系人数据库

联系人.png

raw_contacts表关注字段contact_id

raw_contacts.png

data表mimetype表:关注字段:mimetype_id 、raw_contact_id 、data1

data.png


2.联系人实体类
/**
 * 作者:张风捷特烈<br></br>
 * 时间:2019/2/27/027:19:48<br></br>
 * 邮箱:1981462002@qq.com<br></br>
 * 说明:联系人
 */
class ContactBean {
    var name: String? = null//联系人姓名
    var address: String? = null//联系人地址
    var email: String? = null//联系人邮箱
    var phone: String? = null//联系人电话
    var photo: Bitmap? = null//联系人头像
}

3.查询到ContactBean集合
/**
 * 获取联系人:ContactBean字段:name姓名  address地址  email邮箱 phone手机号
 *
 * @param ctx 上下文
 * @return ContactBean集合
 */
public static List<ContactBean> getContact(Context ctx) {
    //创建一个容器放结果
    List<ContactBean> contactBeans = new ArrayList<>();
    //[1.]获得ContentResolver对象
    ContentResolver resolver = ctx.getContentResolver();
    //[2.1]得到Uri :访问raw_contacts的url
    Uri raw_contactsUri = Uri.parse("content://com.android.contacts/raw_contacts");
    //[2.2]得到Uri ://访问data的url
    Uri dataUri = Uri.parse("content://com.android.contacts/data");
    //[3]查询表,获得raw_contact表游标结果集
    Cursor raw_contactsCursor = resolver.query(
            raw_contactsUri, new String[]{"contact_id"}, null, null, null);
    //[4]遍历游标,获取数据,储存在bean中
    while (raw_contactsCursor.moveToNext()) {
        //[4.1]查询到contact_id
        String contact_id = raw_contactsCursor.getString(0);
        if (contact_id != null) {
            //[4.2]查询表,获得data表游标结果集
            Cursor dataCursor = resolver.query(dataUri,
                    new String[]{"data1", "mimetype"},//注意不是mimetype_id
                    "raw_contact_id=?",
                    new String[]{contact_id}, null);
            ContactBean contactBean = new ContactBean();
            while (dataCursor.moveToNext()) {
                String result = dataCursor.getString(0);
                //[4.4]根据实体类判断数据,放入实体类中
                String mimetype = dataCursor.getString(1);
                if (mimetype != null) {
                    //[4.3]新建实体类
                    switch (mimetype) {
                        case "vnd.android.cursor.item/phone_v2"://手机号
                            contactBean.setPhone(result);
                            break;
                        case "vnd.android.cursor.item/email_v2"://email
                            contactBean.setEmail(result);
                            break;
                        case "vnd.android.cursor.item/name"://姓名
                            contactBean.setName(result);
                            break;
                        case "vnd.android.cursor.item/postal-address_v2"://地址
                            contactBean.setAddress(result);
                            break;
                    }
                }
            }
            if (contactBean.getPhone() != null) {
                contactBeans.add(contactBean);//加入集合
            }
            //[5.1]关闭data表Cursor
            dataCursor.close();
        }
    }
    //[5.2]关闭raw_contacts表Cursor
    raw_contactsCursor.close();
    return contactBeans;
}

4.额外说一下获取联系人头像
/**
 * 根据号码获得联系人头像
 *
 * @param ctx    上下文
 * @param number 号码
 * @return 图片
 */
public static Bitmap getContactPhoto(Context ctx, String number) {
    Bitmap bmpHead = null;
    ContentResolver resolver = ctx.getContentResolver();//获得ContentResolver对象
    // 获得Uri
    Uri uriNumber2Contacts = Uri.parse("content://com.android.contacts/"
            + "data/phones/filter/" + number);
    // 查询Uri,返回数据集
    Cursor cursorCantacts = resolver.query(uriNumber2Contacts, null, null, null, null);
    // 如果该联系人存在
    if (cursorCantacts.getCount() > 0) {
        // 移动到第一条数据
        cursorCantacts.moveToFirst();
        // 获得该联系人的contact_id
        Long contactID = cursorCantacts.getLong(cursorCantacts.getColumnIndex("contact_id"));
        // 获得contact_id的Uri
        Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactID);
        // 打开头像图片的InputStream
        InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(resolver, uri);
        // 从InputStream获得bitmap
        bmpHead = BitmapFactory.decodeStream(input);
    }
    return bmpHead;

下面是以前做的小项目,用到的联系人获取

相信到这里,你应道知道ContentProvider有什么用了吧。
接下来看一下短信的ContentProvider在Android系统中是怎样实现的。


四、短信的ContentProvider在Android中

看了一下,就这个还hold住,其他几个都要命的长。如果你下载了Android源码,可见:
源码目录\packages\providers\TelephonyProvider\src\com\android\providers\telephony\SmsProvider.java
一共不到900行

1.成员变量

从这里可以看到uri和数据库表的相关字段

public class SmsProvider extends ContentProvider {
    private static final Uri NOTIFICATION_URI = Uri.parse("content://sms");
    private static final Uri ICC_URI = Uri.parse("content://sms/icc");
    static final String TABLE_SMS = "sms";
    static final String TABLE_RAW = "raw";
    private static final String TABLE_SR_PENDING = "sr_pending";
    private static final String TABLE_WORDS = "words";
    static final String VIEW_SMS_RESTRICTED = "sms_restricted";

    private static final Integer ONE = Integer.valueOf(1);

    private static final String[] CONTACT_QUERY_PROJECTION =
            new String[] { Contacts.Phones.PERSON_ID };
    private static final int PERSON_ID_COLUMN = 0;

    /**
     * These are the columns that are available when reading SMS
     * messages from the ICC.  Columns whose names begin with "is_"
     * have either "true" or "false" as their values.
       这些列在从ICC读取SMS消息时可用。名称以“is_”开头的列的值要么为“true”,要么为“false”。
     */
    private final static String[] ICC_COLUMNS = new String[] {
        // N.B.: 这些列的出现顺序必须与要添加的调用在convertIccToSms中出现的顺序相同。
        "service_center_address",       // getServiceCenterAddress
        "address",                      // getDisplayOriginatingAddress
        "message_class",                // getMessageClass
        "body",                         // getDisplayMessageBody
        "date",                         // getTimestampMillis
        "status",                       // getStatusOnIcc
        "index_on_icc",                 // getIndexOnIcc
        "is_status_report",             // isStatusReportMessage
        "transport_type",               // Always "sms".
        "type",                         // Always MESSAGE_TYPE_ALL.
        "locked",                       // Always 0 (false).
        "error_code",                   // Always 0
        "_id"
    };
    
--->private SQLiteOpenHelper mOpenHelper;//数据库操作类

    private final static String TAG = "SmsProvider";
    private final static String VND_ANDROID_SMS = "vnd.android.cursor.item/sms";
    private final static String VND_ANDROID_SMSCHAT =
            "vnd.android.cursor.item/sms-chat";
    private final static String VND_ANDROID_DIR_SMS =
            "vnd.android.cursor.dir/sms";

    private static final String[] sIDProjection = new String[] { "_id" };
---->操作标识符--------------------------------------------
    private static final int SMS_ALL = 0;
    private static final int SMS_ALL_ID = 1;
    private static final int SMS_INBOX = 2;
    private static final int SMS_INBOX_ID = 3;
    private static final int SMS_SENT = 4;
    private static final int SMS_SENT_ID = 5;
    private static final int SMS_DRAFT = 6;
    private static final int SMS_DRAFT_ID = 7;
    private static final int SMS_OUTBOX = 8;
    private static final int SMS_OUTBOX_ID = 9;
    private static final int SMS_CONVERSATIONS = 10;
    private static final int SMS_CONVERSATIONS_ID = 11;
    private static final int SMS_RAW_MESSAGE = 15;
    private static final int SMS_ATTACHMENT = 16;
    private static final int SMS_ATTACHMENT_ID = 17;
    private static final int SMS_NEW_THREAD_ID = 18;
    private static final int SMS_QUERY_THREAD_ID = 19;
    private static final int SMS_STATUS_ID = 20;
    private static final int SMS_STATUS_PENDING = 21;
    private static final int SMS_ALL_ICC = 22;
    private static final int SMS_ICC = 23;
    private static final int SMS_FAILED = 24;
    private static final int SMS_FAILED_ID = 25;
    private static final int SMS_QUEUED = 26;
    private static final int SMS_UNDELIVERED = 27;
--------------------------------------------------------
    private static final UriMatcher sURLMatcher =
            new UriMatcher(UriMatcher.NO_MATCH);

--->static {//静态代码块定义可操作项
--->    sURLMatcher.addURI("sms", null, SMS_ALL);//null是,表示全部
        sURLMatcher.addURI("sms", "#", SMS_ALL_ID);
        sURLMatcher.addURI("sms", "inbox", SMS_INBOX);
        sURLMatcher.addURI("sms", "inbox/#", SMS_INBOX_ID);
        sURLMatcher.addURI("sms", "sent", SMS_SENT);
        sURLMatcher.addURI("sms", "sent/#", SMS_SENT_ID);
        sURLMatcher.addURI("sms", "draft", SMS_DRAFT);
        sURLMatcher.addURI("sms", "draft/#", SMS_DRAFT_ID);
        sURLMatcher.addURI("sms", "outbox", SMS_OUTBOX);
        sURLMatcher.addURI("sms", "outbox/#", SMS_OUTBOX_ID);
        sURLMatcher.addURI("sms", "undelivered", SMS_UNDELIVERED);
        sURLMatcher.addURI("sms", "failed", SMS_FAILED);
        sURLMatcher.addURI("sms", "failed/#", SMS_FAILED_ID);
        sURLMatcher.addURI("sms", "queued", SMS_QUEUED);
        sURLMatcher.addURI("sms", "conversations", SMS_CONVERSATIONS);
        sURLMatcher.addURI("sms", "conversations/*", SMS_CONVERSATIONS_ID);
        sURLMatcher.addURI("sms", "raw", SMS_RAW_MESSAGE);
        sURLMatcher.addURI("sms", "attachments", SMS_ATTACHMENT);
        sURLMatcher.addURI("sms", "attachments/#", SMS_ATTACHMENT_ID);
        sURLMatcher.addURI("sms", "threadID", SMS_NEW_THREAD_ID);
        sURLMatcher.addURI("sms", "threadID/*", SMS_QUERY_THREAD_ID);
        sURLMatcher.addURI("sms", "status/#", SMS_STATUS_ID);
        sURLMatcher.addURI("sms", "sr_pending", SMS_STATUS_PENDING);
        sURLMatcher.addURI("sms", "icc", SMS_ALL_ICC);
        sURLMatcher.addURI("sms", "icc/#", SMS_ICC);
        //we keep these for not breaking old applications
        sURLMatcher.addURI("sms", "sim", SMS_ALL_ICC);
        sURLMatcher.addURI("sms", "sim/#", SMS_ICC);
    }

2. onCreate()方法

这里获取了一个MmsSmsDatabaseHelper的数据库操作类

@Override
    public boolean onCreate() {
        setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS);
        mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext());
        return true;
    }

3.查询方法
@Override
    public Cursor query(Uri url, String[] projectionIn, String selection,
            String[] selectionArgs, String sort) {

        final boolean accessRestricted = ProviderUtil.isAccessRestricted(
                getContext(), getCallingPackage(), Binder.getCallingUid());
        final String smsTable = getSmsTable(accessRestricted);
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        // Generate the body of the query.---生成查询的主体。
        int match = sURLMatcher.match(url);//获取操作标识符
        switch (match) {//下面根据情况进行查询
            case SMS_ALL:
                constructQueryForBox(qb, Sms.MESSAGE_TYPE_ALL, smsTable);
                break;
            case SMS_UNDELIVERED:
                constructQueryForUndelivered(qb, smsTable);
                break;
            case SMS_FAILED:
                constructQueryForBox(qb, Sms.MESSAGE_TYPE_FAILED, smsTable);
                break;
            case SMS_QUEUED:
                constructQueryForBox(qb, Sms.MESSAGE_TYPE_QUEUED, smsTable);
                break;
            case SMS_INBOX:
                constructQueryForBox(qb, Sms.MESSAGE_TYPE_INBOX, smsTable);
                break;
            case SMS_SENT:
                constructQueryForBox(qb, Sms.MESSAGE_TYPE_SENT, smsTable);
                break;
            ...略
        }

        String orderBy = null;

        if (!TextUtils.isEmpty(sort)) {//如果排序非空
            orderBy = sort;//赋值
        } else if (qb.getTables().equals(smsTable)) {
            orderBy = Sms.DEFAULT_SORT_ORDER;//使用默认的排序
        }

--->    SQLiteDatabase db = mOpenHelper.getReadableDatabase();//获得SQLiteDatabase
--->    Cursor ret = qb.query(db, projectionIn, selection, selectionArgs,
                              null, null, orderBy);//数据库的查询操作

        ret.setNotificationUri(getContext().getContentResolver(),
                NOTIFICATION_URI);
        return ret;//将查询到的Cursor返回出去
    }
    
private void constructQueryForBox(SQLiteQueryBuilder qb, int type, String smsTable) {
    qb.setTables(smsTable);

    if (type != Sms.MESSAGE_TYPE_ALL) {//如果不是All,就根据传入的类型查询
        qb.appendWhere("type=" + type);
    }
}

4.删除方法
    @Override
    public int delete(Uri url, String where, String[] whereArgs) {
        int count;
        int match = sURLMatcher.match(url);
--->    SQLiteDatabase db = mOpenHelper.getWritableDatabase();//获取SQLiteDatabase
        switch (match) {//根据分支处理
            case SMS_ALL:
                count = db.delete(TABLE_SMS, where, whereArgs);//执行删除语句
                if (count != 0) {
                    // Don't update threads unless something changed.
                    MmsSmsDatabaseHelper.updateAllThreads(db, where, whereArgs);
                }
                break;

            case SMS_ALL_ID:
                ...
                break;

            case SMS_CONVERSATIONS_ID:
                ...
                break;

            case SMS_RAW_MESSAGE:
                count = db.delete("raw", where, whereArgs);//执行删除语句
                break;

            case SMS_STATUS_PENDING:
                count = db.delete("sr_pending", where, whereArgs);//执行删除语句
                break;

            case SMS_ICC:
                String messageIndexString = url.getPathSegments().get(1);
                return deleteMessageFromIcc(messageIndexString);

            default:
                throw new IllegalArgumentException("Unknown URL");
        }
        if (count > 0) {
            notifyChange(url);
        }
        return count;
    }

就分析这两个吧,可见,就是根据Uri对应不同的操作, 核心还是SQLiteDatabase的数据库的操作,ContentProvider只是封装一下,并暴露给所有人


5.最最重要的一点不要忘记要配置一下

配置.png


五、自定义ContentProvider:SwordProvider

这个应该很少用,不是系统级的应用提供的数据你敢用?这里稍作了解
下面进入Sqlite数据库相关,非战斗人员火速备瓜...还是拿《万界神兵录》的表来看吧

|--- 表分析:
数据库名:  weapon
表名:      sworld
字段: 
id      id      INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
名称    name    VARCHAR(32) NOT NULL
攻击力  atk     SMALLINT UNSIGNED DEFAULT 1000
持有人  user    VARCHAR(32) NOT NULL

|--- 建表语句
CREATE TABLE sword (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
name VARCHAR(32) NOT NULL,
atk SMALLINT UNSIGNED NOT NULL DEFAULT 1000,
user VARCHAR(32) NOT NULL,
); 


1.创建类继承自ContentProvider

ContentProvider是一个抽象类,需要实现下面几个方法

/**
 * 作者:张风捷特烈<br/>
 * 时间:2019/2/28/028:11:42<br/>
 * 邮箱:1981462002@qq.com<br/>
 * 说明:万界神兵录
 */
public class SwordProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return false;
    }
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        return null;
    }
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
}

2.数据库相关
2.1--生成数据库

我是根据MmsSmsDatabaseHelper源码来写的,毕竟是大佬写的,参考一下书写风格

生成数据库.png

public class SwordDatabaseHelper extends SQLiteOpenHelper {
    private static String DATABASE_NAME = "weapon.db";//数据库名
    private static int DATABASE_VERSION = 1;//数据库版本
    private static SwordDatabaseHelper sInstance;
    //双检锁单例
    public static synchronized SwordDatabaseHelper getInstance(Context context) {
        if (sInstance == null) {
            synchronized (SwordDatabaseHelper.class) {
                if (sInstance == null) {
                    sInstance = new SwordDatabaseHelper(context);
                }
            }
        }
        return sInstance;
    }
    public SwordDatabaseHelper(@Nullable Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        createSwordTable(db);
    }
    /**
     * 创建sword表
     *
     * @param db SQLiteDatabase
     */
    private void createSwordTable(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE sword (" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
                "name VARCHAR(32) NOT NULL," +
                "atk INTEGER  DEFAULT 1000," +
                "user VARCHAR(32) NOT NULL" +
                "); ");
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

2.CURD测试--增删改查

也就是sql的基础语法

sql操作测试.png

SwordDatabaseHelper helper = new SwordDatabaseHelper(this);
SQLiteDatabase db = helper.getWritableDatabase();
|-- 插入测试
db.execSQL("INSERT INTO sword(name,atk,user) VALUES" +
                    "('绝世好剑',7000,'步惊云')");
|-- 修改测试                 
db.execSQL("UPDATE sword SET atk=3500 WHERE id=1");

|-- 查询测试
Cursor cursor = db.rawQuery("SELECT * FROM sword", null);
while (cursor.moveToNext()) {
    String id = cursor.getString(cursor.getColumnIndex("id"));
    String name = cursor.getString(cursor.getColumnIndex("name"));
    String atk = cursor.getString(cursor.getColumnIndex("atk"));
    String user = cursor.getString(cursor.getColumnIndex("user"));
    Log.e(TAG, "rawQuery: "+id + "---" + name + "---" + atk + "---" + user );
    //rawQuery: 1---绝世好剑---3500---步惊云
}
cursor.close();//关闭游标

|-- 删除测试
db.execSQL("DELETE FROM sword WHERE id=1");

3.SwordDatabaseHelper在ContentProvider中的使用

既然测试ok,那就去实现一下ContentProvider的几个方法,uri就设定为增删改查四个...
规则可以自己设定,在方法里都可以根据sUriMatcher.match(uri)获取对应码来区别处理

/**
 * 作者:张风捷特烈<br/>
 * 时间:2019/2/28/028:11:42<br/>
 * 邮箱:1981462002@qq.com<br/>
 * 说明:万界神兵录
 */
public class SwordProvider extends ContentProvider {
    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private static final int SWORD_QUERY = 0;
    private static final int SWORD_INSERT = 1;
    private static final int SWORD_UPDATE = 2;
    private static final int SWORD_DELETE = 3;

    private static final String TABLE_NAME = "sword";

    static {
        //给当前sUriMatcher添加匹配规则
        sUriMatcher.addURI("toly1994.com.sword", "query", SWORD_QUERY);
        sUriMatcher.addURI("toly1994.com.sword", "insert", SWORD_INSERT);
        sUriMatcher.addURI("toly1994.com.sword", "update", SWORD_UPDATE);
        sUriMatcher.addURI("toly1994.com.sword", "delete", SWORD_DELETE);
    }

    private SQLiteOpenHelper mOpenHelper;

    @Override
    public boolean onCreate() {
        mOpenHelper = SwordDatabaseHelper.getInstance(getContext());
        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        //进行uri匹配
        int result = sUriMatcher.match(uri);
        if (result == SWORD_QUERY) {
            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
            return db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);

        } else {
            throw new IllegalStateException(" query Uri 错误");
        }
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        //进行uri匹配
        int result = sUriMatcher.match(uri);
        if (result == SWORD_INSERT) {
            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
            Long insert = db.insert(TABLE_NAME, null, values);
            //uri:数据发送变化,通过uri判断调用哪个内容观察者
            //第二个参数:内容观察者对象  如果传null 则注册了整个uri的内容观察者皆可以收到通知
            getContext().getContentResolver().notifyChange(uri, null);
            db.close();
            return Uri.parse(String.valueOf(insert));
        } else {
            throw new IllegalStateException("insert Uri 错误");
        }
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        //进行uri匹配
        int result = sUriMatcher.match(uri);
        if (result == SWORD_DELETE) {
            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
            int delete = db.delete(TABLE_NAME, selection, selectionArgs);
            db.close();
            return delete;
        } else {
            throw new IllegalStateException("delete Uri  错误");
        }
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        //进行uri匹配
        int result = sUriMatcher.match(uri);
        if (result == SWORD_UPDATE) {
            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
            int update = db.update(TABLE_NAME, values, selection, selectionArgs);
            db.close();
            return update;
        } else {
            throw new IllegalStateException("update Uri 错误");
        }
    }
}

4.配置
<provider
        android:name=".provider.swordProvider.SwordProvider"
        android:authorities="toly1994.com.sword"
        android:exported="true"/>

你应该清楚,上面就相当于系统的SmsProvider,用于提供一个可供全局操作的数据库


5.现在到==另一个app==里进行测试

经测试,是可用的,也就是另一个app可以操作刚才应用中的数据库

测试.png

/**
 * 作者:张风捷特烈<br/>
 * 时间:2019/2/27/027:19:52<br/>
 * 邮箱:1981462002@qq.com<br/>
 * 说明:另一个app测试SwordProvider
 */
public class SwordProviderActivity extends AppCompatActivity {
    private static final String TAG = "SwordProviderActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ContentResolver resolver = getContentResolver();
//        insert(resolver); //插入测试
//        query(resolver);//查询测试
        update(resolver);//更新测试
        query(resolver);
        delete(resolver);//删除测试
        query(resolver);
    }
    /**
     * 删除测试
     *
     * @param resolver
     */
    private void delete(ContentResolver resolver) {
        Uri uri = Uri.parse("content://toly1994.com.sword/delete");
        resolver.delete(uri, "name=?", new String[]{"屠龙刀"});
    }
    /**
     * 插入测试
     *
     * @param resolver
     */
    private void insert(ContentResolver resolver) {
        Uri uri = Uri.parse("content://toly1994.com.sword/insert");
        ContentValues values = new ContentValues();
        values.put("name", "屠龙刀");
        values.put("atk", "3000");
        values.put("user", "张无忌");
        resolver.insert(uri, values);
    }
    /**
     * 更新测试
     *
     * @param resolver
     */
    private void update(ContentResolver resolver) {
        Uri uri = Uri.parse("content://toly1994.com.sword/update");
        ContentValues values = new ContentValues();
        values.put("name", "屠龙刀");
        values.put("atk", "3500");
        values.put("user", "张无忌");
        resolver.update(uri, values, "name=?", new String[]{"屠龙刀"});
    }
    /**
     * 查询测试
     *
     * @param resolver
     */
    private void query(ContentResolver resolver) {
        Uri uri = Uri.parse("content://toly1994.com.sword/query");
        Cursor cursor = resolver.query(uri, null, null, null, null);
        while (cursor.moveToNext()) {//遍历游标,获取数据,储存在bean中
            int id = cursor.getInt(cursor.getColumnIndex("id"));
            String name = cursor.getString(cursor.getColumnIndex("name"));
            int atk = cursor.getInt(cursor.getColumnIndex("atk"));
            String user = cursor.getString(cursor.getColumnIndex("user"));
            Log.e(TAG, "query: " + id + "---" + name + "---" + atk + "---" + user);
            //query: 2---屠龙刀---3000---张无忌
        }
    }
}

至于ContentProvider的内部实现原理暂时还没有兴趣,未提上日程
好了,到此为止,安卓的四大组件就重新总结了一遍,这是第二次终结
四大组件的几篇文章都用一个工程测试的,Github地址:欢迎star