android短彩信数据库设计(三)

本文详细解析了Android系统中短彩信ContentProvider的工作原理,包括MmsSmsProvider、SmsProvider和MmsProvider的实现细节。重点介绍了如何通过ContentProvider组件实现短彩信的联合查询,以及构建SQL查询语句的具体步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载请注明出处:http://blog.youkuaiyun.com/droyon/article/details/8738873

在Android短彩信中,有这么三个ContentProvider组建,MmsSmsPrivider.java,SmsPrivider.java,MmsPrivider.java,我们发送的数据库请求会到这里。

ContentPrivider组建是android四大组件之一,我们来看看短彩信如何利用这个组件。

MmsSmsPrivider.java

1、

private static final UriMatcher URI_MATCHER =
            new UriMatcher(UriMatcher.NO_MATCH);
初始化一个UriMatcher,默认不匹配任何东西。

2、

// These constants are used to construct union queries across the
    // MMS and SMS base tables.

    // These are the columns that appear in both the MMS ("pdu") and
    // SMS ("sms") message tables.
    private static final String[] MMS_SMS_COLUMNS =
            { BaseColumns._ID, Mms.DATE, Mms.DATE_SENT, Mms.READ, Mms.THREAD_ID, Mms.LOCKED };
我翻译一下英文注释:

这些常量用于在彩信和短信以来的数据表内组建union查询语句。这些列是彩信数据表pdu以及短信数据表sms公共的列。

// These are the columns that appear only in the MMS message
    // table.
    private static final String[] MMS_ONLY_COLUMNS = {
        Mms.CONTENT_CLASS, Mms.CONTENT_LOCATION, Mms.CONTENT_TYPE,
        Mms.DELIVERY_REPORT, Mms.EXPIRY, Mms.MESSAGE_CLASS, Mms.MESSAGE_ID,
        Mms.MESSAGE_SIZE, Mms.MESSAGE_TYPE, Mms.MESSAGE_BOX, Mms.PRIORITY,
        Mms.READ_STATUS, Mms.RESPONSE_STATUS, Mms.RESPONSE_TEXT,
        Mms.RETRIEVE_STATUS, Mms.RETRIEVE_TEXT_CHARSET, Mms.REPORT_ALLOWED,
        Mms.READ_REPORT, Mms.STATUS, Mms.SUBJECT, Mms.SUBJECT_CHARSET,
        Mms.TRANSACTION_ID, Mms.MMS_VERSION };

仅仅在彩信数据表内会定义的数据项。

// These are the columns that appear only in the SMS message
    // table.
    private static final String[] SMS_ONLY_COLUMNS =
            { "address", "body", "person", "reply_path_present",
              "service_center", "status", "subject", "type", "error_code" };
仅仅在短信数据表内定义的数据项。

实例化一个能够包含短信和彩信所有项的数组

// These are all the columns that appear in the MMS and SMS
    // message tables.
    private static final String[] UNION_COLUMNS =
            new String[MMS_SMS_COLUMNS.length
                       + MMS_ONLY_COLUMNS.length
                       + SMS_ONLY_COLUMNS.length];

定义了上述三个常量数组,我们在后面用他们构建union查询等等。

3、

private static final String SMS_CONVERSATION_CONSTRAINT = "(" +
            Sms.TYPE + " != " + Sms.MESSAGE_TYPE_DRAFT + ")";

private static final String MMS_CONVERSATION_CONSTRAINT = "(" +
            Mms.MESSAGE_BOX + " != " + Mms.MESSAGE_BOX_DRAFTS + " AND (" +
            Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_SEND_REQ + " OR " +
            Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF + " OR " +
            Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + "))";
两个常量,前者代表短信不为草稿,后者代表彩信不为草稿,也就是说彩信数据仅仅是这三种类型。
4、

// Search on the words table but return the rows from the corresponding sms table
    private static final String SMS_QUERY =
            "SELECT sms._id AS _id,thread_id,address,body,date,date_sent,index_text,words._id " +
            "FROM sms,words WHERE (index_text MATCH ? " +
            "AND sms._id=words.source_id AND words.table_to_use=1)";

    // Search on the words table but return the rows from the corresponding parts table
    private static final String MMS_QUERY =
            "SELECT pdu._id,thread_id,addr.address,part.text " +
            "AS body,pdu.date,pdu.date_sent,index_text,words._id " +
            "FROM pdu,part,addr,words WHERE ((part.mid=pdu._id) AND " +
            "(addr.msg_id=pdu._id) AND " +
            "(addr.type=" + PduHeaders.TO + ") AND " +
            "(part.ct='text/plain') AND " +
            "(index_text MATCH ?) AND " +
            "(part._id = words.source_id) AND " +
            "(words.table_to_use=2))";

    // This code queries the sms and mms tables and returns a unified result set
    // of text matches.  We query the sms table which is pretty simple.  We also
    // query the pdu, part and addr table to get the mms result.  Notet we're
    // using a UNION so we have to have the same number of result columns from
    // both queries.
    private static final String SMS_MMS_QUERY =
            SMS_QUERY + " UNION " + MMS_QUERY +
            " GROUP BY thread_id ORDER BY thread_id ASC, date DESC";
在信息中搜索匹配的字符串。分别对短信和彩信构建查询语句,然后使用union将他们联合起来。使用union的两边要保证对应位置的数据类型一样。

5、

private static final String AUTHORITY = "mms-sms";

    static {
        URI_MATCHER.addURI(AUTHORITY, "conversations", URI_CONVERSATIONS);
        URI_MATCHER.addURI(AUTHORITY, "complete-conversations", URI_COMPLETE_CONVERSATIONS);

        // In these patterns, "#" is the thread ID.
        URI_MATCHER.addURI(
                AUTHORITY, "conversations/#", URI_CONVERSATIONS_MESSAGES);
.....
......
URI_MATCHER.addURI(AUTHORITY, "locked", URI_FIRST_LOCKED_MESSAGE_ALL);

        URI_MATCHER.addURI(AUTHORITY, "locked/#", URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID);

        URI_MATCHER.addURI(AUTHORITY, "messageIdToThread", URI_MESSAGE_ID_TO_THREAD);
        initializeColumnSets();
    }
构建UriMatcher,最后调用initializeColumnSets()方法,我们想来介绍一下这个方法,这个方法做的事情也挺简单的。就是给我们前面提到的UNION_COLUMNS数组,以及下面两项
// These are all the columns that appear in the MMS table.
    private static final Set<String> MMS_COLUMNS = new HashSet<String>();

    // These are all the columns that appear in the SMS table.
    private static final Set<String> SMS_COLUMNS = new HashSet<String>();
填充数据
/**
     * Construct Sets of Strings containing exactly the columns
     * present in each table.  We will use this when constructing
     * UNION queries across the MMS and SMS tables.
     */
    private static void initializeColumnSets() {
        int commonColumnCount = MMS_SMS_COLUMNS.length;
        int mmsOnlyColumnCount = MMS_ONLY_COLUMNS.length;
        int smsOnlyColumnCount = SMS_ONLY_COLUMNS.length;
        Set<String> unionColumns = new HashSet<String>();

        for (int i = 0; i < commonColumnCount; i++) {
            MMS_COLUMNS.add(MMS_SMS_COLUMNS[i]);
            SMS_COLUMNS.add(MMS_SMS_COLUMNS[i]);
            unionColumns.add(MMS_SMS_COLUMNS[i]);
        }
        for (int i = 0; i < mmsOnlyColumnCount; i++) {
            MMS_COLUMNS.add(MMS_ONLY_COLUMNS[i]);
            unionColumns.add(MMS_ONLY_COLUMNS[i]);
        }
        for (int i = 0; i < smsOnlyColumnCount; i++) {
            SMS_COLUMNS.add(SMS_ONLY_COLUMNS[i]);
            unionColumns.add(SMS_ONLY_COLUMNS[i]);
        }

        int i = 0;
        for (String columnName : unionColumns) {
            UNION_COLUMNS[i++] = columnName;
        }
    }
6、

public boolean onCreate() {
        mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext());
        mUseStrictPhoneNumberComparation =
            getContext().getResources().getBoolean(
                    com.android.internal.R.bool.config_use_strict_phone_number_comparation);
        return true;
    }
再往下就是利用getInstance方法得到SQLiteDatabaseHelper.java的对象。

7、在然后是query方法,这个方法首先通过6中得到mOpernHelper得到可读数据库,然后根据uri的匹配结果,进入到不同的case中进行数据的查询。我们一步一步的看看过程。

SQLiteDatabase db = mOpenHelper.getReadableDatabase();
        Cursor cursor = null;
switch(URI_MATCHER.match(uri)) {
            case URI_COMPLETE_CONVERSATIONS:
                cursor = getCompleteConversations(projection, selection, sortOrder);
                break;
首先调用getCompleteConversations方法去数据库获取数据,将数据包装成cursor,在后面返回。

我们看看这个方法的内部逻辑。

7.1、

/**
     * Return every message in each conversation in both MMS
     * and SMS.
     */
    private Cursor getCompleteConversations(String[] projection,
            String selection, String sortOrder) {
        String unionQuery = buildConversationQuery(projection, selection, sortOrder);//方法介绍见7.2

        return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY);//利用上面得到的sql,进行数据检索,将数据包装到cursor中并返回。
    }
这个方法调用buildConversationQuery方法,我们进入到这个方法中,

7.2、

private static String buildConversationQuery(String[] projection,
            String selection, String sortOrder) {
        String[] mmsProjection = createMmsProjection(projection);

        SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();//首先初始化一个SQLiteQueryBuilder对象,作为彩信sql的构建对象
        SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();//和上面雷同

        mmsQueryBuilder.setDistinct(true);//设置去掉重复的数据
        smsQueryBuilder.setDistinct(true);//同上
        mmsQueryBuilder.setTables(joinPduAndPendingMsgTables());//见7.3
        smsQueryBuilder.setTables(SmsProvider.TABLE_SMS);//为sql设置目标数据表。例如:select * from 【table】。这里就是用来设置table的。

        String[] smsColumns = handleNullMessageProjection(projection);//处理空projection,见7.4
        String[] mmsColumns = handleNullMessageProjection(mmsProjection);//同上
        String[] innerMmsProjection = makeProjectionWithNormalizedDate(mmsColumns, 1000);//见7.5,转换date的秒单位为毫秒
        String[] innerSmsProjection = makeProjectionWithNormalizedDate(smsColumns, 1);//同上

        Set<String> columnsPresentInTable = new HashSet<String>(MMS_COLUMNS);//构建一个set,其中含有短信以及彩信的数据库表的所有字段。
        columnsPresentInTable.add("pdu._id");
        columnsPresentInTable.add(PendingMessages.ERROR_TYPE);//增加两项数据表项

        String mmsSelection = concatSelections(selection,
                                Mms.MESSAGE_BOX + " != " + Mms.MESSAGE_BOX_DRAFTS);//见7.6,拼接两个selection
        String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(//调用buildUnionSubQuery进行构建sql子句。想了解参数的意义,见7.7
                MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerMmsProjection,
                columnsPresentInTable, 0, "mms",
                concatSelections(mmsSelection, MMS_CONVERSATION_CONSTRAINT),
                null, null);
        String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
                MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection, SMS_COLUMNS,
                0, "sms", concatSelections(selection, SMS_CONVERSATION_CONSTRAINT),
                null, null);
        SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();//构建一个新的sql语句包装对象,用于将sms以及mms的包装对象加进来。

        unionQueryBuilder.setDistinct(true);//设置清楚重复数据项。设置了这一项,使用的是union不设置则使用union all

        String unionQuery = unionQueryBuilder.buildUnionQuery(//将短信和彩信的sql加进来。
                new String[] { smsSubQuery, mmsSubQuery },
                handleNullSortOrder(sortOrder), null);

        SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder();

        outerQueryBuilder.setTables("(" + unionQuery + ")");//将上述sql搜索的字符串作为一个数据表

        return outerQueryBuilder.buildQuery(//构建最终的sql。
                smsColumns, null, null, null, sortOrder, null);
//执行完毕会得到类似这样的数据:

 }

//select a,b,c from (select aa,bb,null as cc,null as dd from sms where aa = 1union selectnull as aa,null as bb,cc,dd from pdu where aaa = 1) where (_id = 1) groupBy xxx date DESC.

7.3、

private static String joinPduAndPendingMsgTables() {
        return MmsProvider.TABLE_PDU + " LEFT JOIN " + TABLE_PENDING_MSG
                + " ON pdu._id = pending_msgs.msg_id";
    }
pdu表和pending_msgs表进行左连接
7.4、

/**
     * If a null projection is given, return the union of all columns
     * in both the MMS and SMS messages tables.  Otherwise, return the
     * given projection.
     */
    private static String[] handleNullMessageProjection(
            String[] projection) {
        return projection == null ? UNION_COLUMNS : projection;
    }
检查projection是否为null,如果不为null,返回自身,如果为null,则返回所有UNION_COLUMNS,这个UNION_COLUMNS的初始化在5中介绍了。

7.5、

 /**
     * Add normalized date to the list of columns for an inner
     * projection.
     */
    private static String[] makeProjectionWithNormalizedDate(
            String[] projection, int dateMultiple) {
        int projectionSize = projection.length;
        String[] result = new String[projectionSize + 1];

        result[0] = "date * " + dateMultiple + " AS normalized_date";
        System.arraycopy(projection, 0, result, 1, projectionSize);
        return result;
    }
为projection添加normalized_date,由于sms表内的date存储的是当前的时间,是毫秒。而在彩信数据库中,存储的是秒,因而转换时间时,彩信需要乘以1000,转换为毫秒。

7.6、

private static String concatSelections(String selection1, String selection2) {
        if (TextUtils.isEmpty(selection1)) {
            return selection2;
        } else if (TextUtils.isEmpty(selection2)) {
            return selection1;
        } else {
            return selection1 + " AND " + selection2;
        }
    }
拼接selection1以及selection2.

7.7、

public String buildUnionSubQuery(
            String typeDiscriminatorColumn,
            String[] unionColumns,
            Set<String> columnsPresentInTable,
            int computedColumnsOffset,
            String typeDiscriminatorValue,
            String selection,
            String groupBy,
            String having) {
        int unionColumnsCount = unionColumns.length;
        String[] projectionIn = new String[unionColumnsCount];

        for (int i = 0; i < unionColumnsCount; i++) {
            String unionColumn = unionColumns[i];

            if (unionColumn.equals(typeDiscriminatorColumn)) {
                projectionIn[i] = "'" + typeDiscriminatorValue + "' AS "
                        + typeDiscriminatorColumn;
            } else if (i <= computedColumnsOffset
                       || columnsPresentInTable.contains(unionColumn)) {
                projectionIn[i] = unionColumn;
            } else {
                projectionIn[i] = "NULL AS " + unionColumn;
            }
        }
        return buildQuery(
                projectionIn, selection, groupBy, having,
                null /* sortOrder */,
                null /* limit */);
    }
贴出源码比较容易理解,这个函数的设计思想。
一切的重点在于columnsPresentInTable中。把属于此set集合的项放进sql中,如果不再此set集合中,则把null as xx放进sql字符串。

这个函数就是把不属于set集合的列在搜索结果中现实为null.


剩下的case项基本雷同。

public Cursor query(Uri uri, String[] projection,
            String selection, String[] selectionArgs, String sortOrder) {
.......
.....
....
        cursor.setNotificationUri(getContext().getContentResolver(), MmsSms.CONTENT_URI);
        return cursor;
    }
最后的一项很重要,它为自身注册了一个观察者,监听了URI,当数据库发生变动,

if (affectedRows > 0) {
            context.getContentResolver().notifyChange(MmsSms.CONTENT_URI, null);
        }
如果数据库发生变动,并执行 notifyChanged方法,那么注册相应uri的Cursor会受到通知,并做出反应。

ContentProvider类大部分为相应请求,构建sql进行数据库的查询。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hailushijie

您的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值