zoukankan      html  css  js  c++  java
  • 基础总结篇之七:ContentProvider之读写短消息

    转自:http://blog.csdn.net/wellsoho/article/details/49494167

    今天我们来讲一下如何利用ContentProvider读写短消息。

    上次我们讲了如何通过ContentProvider机制读写联系人,通过读取联系人信息和添加联系人这两种方式对联系人进行操作,相信大家对ContentProvider的基本使用方法也有所了解了。在Android中ContentProvider应用场合还很多,读写短消息就是其中一个,今天我们就来探讨一下利用ContentProvider操作短消息的问题。

    相对于联系人来说,短消息不是公开的,所以没有专门的API供我们调用,这就要求我们根据源代码进行分析研究,制定出一定的操作方案。

    我们需要先找到短消息的数据源,打开/data/data/com.android.providers.telephony可以看到:

    其中的mmssms.db就是短消息的数据源,朋友们可以导出一下这个文件,用专业工具软件查看一下表结构。为了方便大家理解,我简单介绍一下今天涉及到的两张表以及表中的常用字段:

    如图所示,两张表分别是threads表和sms表,前者代表所有会话信息,每个会话代表和一个联系人之间短信的群组;后者代表短信的具体信息。在sms表中的thread_id指向了threads表中的_id,指定每条短信的会话id,以便对短信进行分组。下面介绍一下表中的每个字段的意义:

    threads表:_id字段表示该会话id;date表示该会话最后一条短信的日期,一般用来对多个会话排序显示;message_count表示该会话所包含的短信数量;snippet表示该会话中最后一条短信的内容;read表示该会话是否已读(0:未读,1:已读),一般来说该会话中有了新短信但没查看时,该会话read变为未读状态,当查看过新短信后read就变为已读状态。

    sms表:_id表示该短信的id;thread_id表示该短信所属的会话的id;date表示该短信的日期;read表示该短信是否已读;type表示该短信的类型,例如1表示接收类型,2表示发送类型,3表示草稿类型;body表示短信的内容。

    下面我们会通过单元测试的方式演示一下读取会话信息和短信内容。在写代码之前,我们先初始化一些数据,具体过程是启动三个模拟器5554、5556、5558,让5554分别与5556和5558互发短信,如下:

    我们看到5554这小子名叫Jack;5556名叫Lucy,可能认识有几天了,手机上存了她的号码;5558名叫Lisa,可能刚认识,还没来得及存号码。Jack这小子真狠啊,想同时泡两个妞,难道名字叫Jack的长得都很帅?下面是以上的两个会话信息:

    可以看到,因为在联系人里存了Lucy,所以显示时并不再直接显示陌生的数字,而是其名字;括号内显示了该会话的短信数;下面文字显示了最后一条短信的内容和日期。

    下面我们创建一个名为SMSTest的单元测试类,用于读取会话信息和短信内容,代码如下:

    [java] view plaincopy
     
     
     
    1. package com.scott.provider;  
    2.   
    3. import java.text.SimpleDateFormat;  
    4.   
    5. import android.content.ContentResolver;  
    6. import android.database.Cursor;  
    7. import android.database.CursorWrapper;  
    8. import android.net.Uri;  
    9. import android.test.AndroidTestCase;  
    10. import android.util.Log;  
    11.   
    12. public class SMSTest extends AndroidTestCase {  
    13.       
    14.     private static final String TAG = "SMSTest";  
    15.       
    16.     //会话  
    17.     private static final String CONVERSATIONS = "content://sms/conversations/";  
    18.     //查询联系人  
    19.     private static final String CONTACTS_LOOKUP = "content://com.android.contacts/phone_lookup/";  
    20.     //全部短信  
    21.     private static final String SMS_ALL   = "content://sms/";  
    22.     //收件箱  
    23. //  private static final String SMS_INBOX = "content://sms/inbox";  
    24.     //已发送  
    25. //  private static final String SMS_SENT  = "content://sms/sent";  
    26.     //草稿箱  
    27. //  private static final String SMS_DRAFT = "content://sms/draft";  
    28.       
    29.     private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
    30.       
    31.     /** 
    32.      * 读取会话信息 
    33.      */  
    34.     public void testReadConversation() {  
    35.         ContentResolver resolver = getContext().getContentResolver();  
    36.         Uri uri = Uri.parse(CONVERSATIONS);  
    37.         String[] projection = new String[]{"groups.group_thread_id AS group_id", "groups.msg_count AS msg_count",  
    38.                         "groups.group_date AS last_date", "sms.body AS last_msg", "sms.address AS contact"};  
    39.         Cursor thinc = resolver.query(uri, projection, null, null, "groups.group_date DESC");   //查询并按日期倒序  
    40.         Cursor richc = new CursorWrapper(thinc) {   //对Cursor进行处理,遇到号码后获取对应的联系人名称  
    41.             @Override  
    42.             public String getString(int columnIndex) {  
    43.                 if(super.getColumnIndex("contact") == columnIndex){  
    44.                     String contact = super.getString(columnIndex);  
    45.                     //读取联系人,查询对应的名称  
    46.                     Uri uri = Uri.parse(CONTACTS_LOOKUP + contact);  
    47.                     Cursor cursor = getContext().getContentResolver().query(uri, null, null, null, null);  
    48.                     if(cursor.moveToFirst()){  
    49.                         String contactName = cursor.getString(cursor.getColumnIndex("display_name"));  
    50.                         return contactName;  
    51.                     }  
    52.                     return contact;  
    53.                 }  
    54.                 return super.getString(columnIndex);  
    55.             }  
    56.         };  
    57.         while (richc.moveToNext()) {  
    58.             String groupId = "groupId: " + richc.getInt(richc.getColumnIndex("group_id"));  
    59.             String msgCount = "msgCount: " + richc.getLong(richc.getColumnIndex("msg_count"));  
    60.             String lastMsg = "lastMsg: " + richc.getString(richc.getColumnIndex("last_msg"));  
    61.             String contact = "contact: " + richc.getString(richc.getColumnIndex("contact"));  
    62.             String lastDate = "lastDate: " + dateFormat.format(richc.getLong(richc.getColumnIndex("last_date")));  
    63.   
    64.             printLog(groupId, contact, msgCount, lastMsg, lastDate, "---------------END---------------");  
    65.         }  
    66.         richc.close();  
    67.     }  
    68.       
    69.     /** 
    70.      * 读取短信 
    71.      */  
    72.     public void testReadSMS() {  
    73.         ContentResolver resolver = getContext().getContentResolver();  
    74.         Uri uri = Uri.parse(SMS_ALL);  
    75.         String[] projection = {"thread_id AS group_id", "address AS contact", "body AS msg_content", "date", "type"};  
    76.         Cursor c = resolver.query(uri, projection, null, null, "date DESC");    //查询并按日期倒序  
    77.         while (c.moveToNext()) {  
    78.             String groupId = "groupId: " + c.getInt(c.getColumnIndex("group_id"));  
    79.             String contact = "contact: " + c.getString(c.getColumnIndex("contact"));  
    80.             String msgContent = "msgContent: " + c.getString(c.getColumnIndex("msg_content"));  
    81.             String date = "date: " + dateFormat.format(c.getLong(c.getColumnIndex("date")));  
    82.             String type = "type: " + getTypeById(c.getInt(c.getColumnIndex("type")));  
    83.   
    84.             printLog(groupId, contact, msgContent, date, type, "---------------END---------------");  
    85.         }  
    86.         c.close();  
    87.     }  
    88.       
    89.     private String getTypeById(int typeId) {  
    90.         switch (typeId) {  
    91.         case 1: return "receive";  
    92.         case 2: return "send";  
    93.         case 3: return "draft";  
    94.         default: return "none";  
    95.         }  
    96.     }  
    97.       
    98.     private void printLog(String...strings) {  
    99.         for (String s : strings) {  
    100.             Log.i(TAG, s == null ? "NULL" : s);  
    101.         }  
    102.     }  
    103. }  

    我们先分析一下testReadConversation()方法,它是用来读取所有的会话信息的,根据“content://sms/conversations/”这个URI进行会话数据的读取操作,当取到数据后,对数据进一步的包装,具体做法是遇到号码时再根据“content://com.android.contacts/phone_lookup/”到联系人中查找对应的名称,如果存在则显示名称而不是号码。我们注意到在查询会话时使用到了projection,这些都是根据什么制定的呢?这就需要我们去看一下源代码了。

    我们找到TelephonyProvider中的com/android/providers/telephony/SmsProvider.java文件,看一看究竟:

    [java] view plaincopy
     
     
     
    1. @Override  
    2. public Cursor query(Uri url, String[] projectionIn, String selection,  
    3.         String[] selectionArgs, String sort) {  
    4.     SQLiteQueryBuilder qb = new SQLiteQueryBuilder();  
    5.   
    6.     // Generate the body of the query.  
    7.     int match = sURLMatcher.match(url);  
    8.     switch (match) {  
    9.     ...  
    10.     case SMS_CONVERSATIONS:  
    11.         qb.setTables("sms, (SELECT thread_id AS group_thread_id, MAX(date)AS group_date,"  
    12.                + "COUNT(*) AS msg_count FROM sms GROUP BY thread_id) AS groups");  
    13.         qb.appendWhere("sms.thread_id = groups.group_thread_id AND sms.date ="  
    14.                + "groups.group_date");  
    15.         qb.setProjectionMap(sConversationProjectionMap);  
    16.         break;  
    17.         ...  
    18.     }  
    19.   
    20.     String orderBy = null;  
    21.   
    22.     if (!TextUtils.isEmpty(sort)) {  
    23.         orderBy = sort;  
    24.     } else if (qb.getTables().equals(TABLE_SMS)) {  
    25.         orderBy = Sms.DEFAULT_SORT_ORDER;  
    26.     }  
    27.   
    28.     SQLiteDatabase db = mOpenHelper.getReadableDatabase();  
    29.     Cursor ret = qb.query(db, projectionIn, selection, selectionArgs,  
    30.                           null, null, orderBy);  
    31.   
    32.     // TODO: Since the URLs are a mess, always use content://sms  
    33.     ret.setNotificationUri(getContext().getContentResolver(),  
    34.             NOTIFICATION_URI);  
    35.     return ret;  
    36. }  

    我们看到,在query方法的case语句中,如果是SMS_CONVERSATIONS类型的话,就为SQLiteQueryBuilder实例对象qb设置对应的查询表和where语句,另外还会为其设置一个基本的查询映射map即sConversationProjectionMap,这个变量在下面代码中体现:

    [java] view plaincopy
     
     
     
    1. static {  
    2.     ...  
    3.     sURLMatcher.addURI("sms", "conversations", SMS_CONVERSATIONS);  
    4.     sURLMatcher.addURI("sms", "conversations/*", SMS_CONVERSATIONS_ID);  
    5.     ...  
    6.   
    7.     sConversationProjectionMap.put(Sms.Conversations.SNIPPET,  
    8.         "sms.body AS snippet");  
    9.     sConversationProjectionMap.put(Sms.Conversations.THREAD_ID,  
    10.         "sms.thread_id AS thread_id");  
    11.     sConversationProjectionMap.put(Sms.Conversations.MESSAGE_COUNT,  
    12.         "groups.msg_count AS msg_count");  
    13.     sConversationProjectionMap.put("delta", null);  
    14. }  

    这几对数据有什么用处呢?如果我们查询时的projection为null的话,sConversationProjectionMap就将转换为默认的projection,最后查询结果中仅包含这三个最基本的字段:snippet、thread_id、msg_count,可以代表一个会话的最简明的信息,朋友们可以亲自试一试。

    当然,如果想运行上面的测试用例,需要配置两个权限:读取短消息权限和读取联系人权限,如下:

    [html] view plaincopy
     
     
     
    1. <!-- 读取短消息 -->  
    2. <uses-permission android:name="android.permission.READ_SMS" />  
    3. <!-- 读取联系人 -->  
    4. <uses-permission android:name="android.permission.READ_CONTACTS"/>  

    然后我们运行一下测试用例,结果如下:

    以上就是读取会话的全部内容,下面我们再介绍其中的testReadSMS()方法。在这个方法中我们试图将所有的短消息都获取到,使用了“content://sms/”进行查询,这个查询相对简单了许多。另外代码中也有几个没使用到的URI,他们分别是收件箱、已发送和草稿箱,这几个查询是“content://sms/”的子集,分别用了不同的选择条件对短信表进行查询,我们看一下具体的源代码:

    [java] view plaincopy
     
     
     
    1.   @Override  
    2.   public Cursor query(Uri url, String[] projectionIn, String selection,  
    3.           String[] selectionArgs, String sort) {  
    4.       SQLiteQueryBuilder qb = new SQLiteQueryBuilder();  
    5.   
    6.       // Generate the body of the query.  
    7.       int match = sURLMatcher.match(url);  
    8.       switch (match) {  
    9.         
    10.       case SMS_ALL:  
    11.           constructQueryForBox(qb, Sms.MESSAGE_TYPE_ALL);  
    12.           break;  
    13.       case SMS_INBOX:  
    14.           constructQueryForBox(qb, Sms.MESSAGE_TYPE_INBOX);  
    15.           break;  
    16.   
    17.       case SMS_SENT:  
    18.           constructQueryForBox(qb, Sms.MESSAGE_TYPE_SENT);  
    19.           break;  
    20.   
    21.       case SMS_DRAFT:  
    22.           constructQueryForBox(qb, Sms.MESSAGE_TYPE_DRAFT);  
    23.           break;  
    24.       }  
    25. ...  
    26.   }  

    可以看到,他们都调用了constructQueryForBox方法,这个方法是干什么的呢?

    [java] view plaincopy
     
     
     
    1. private void constructQueryForBox(SQLiteQueryBuilder qb, int type) {  
    2.     qb.setTables(TABLE_SMS);  
    3.   
    4.     if (type != Sms.MESSAGE_TYPE_ALL) {  
    5.         qb.appendWhere("type=" + type);  
    6.     }  
    7. }  

    我们发现它其实是添加过滤条件的,如果不是查询全部,则添加类型过滤信息,因此查询出不同的短信集合。朋友们也可以亲自试一试不同类型的查询。

    另外,如果我们想根据会话来查询对应的短信集合的话,我们可以用以下两种方式来完成:

    1.“content://sms/”(selection:“thread_id=3”)

    2.“content://sms/conversations/3”

    第一种比较容易想到查询的过程,即在上面的基础上加上“thread_id=3”这条where语句即可;第二种是在会话path后面跟上会话id即可,具体的逻辑如下:

    [java] view plaincopy
     
     
     
    1. case SMS_CONVERSATIONS_ID:  
    2. int threadID;  
    3. try {  
    4.     threadID = Integer.parseInt(url.getPathSegments().get(1));  
    5.     if (Log.isLoggable(TAG, Log.VERBOSE)) {  
    6.         Log.d(TAG, "query conversations: threadID=" + threadID);  
    7.     }  
    8. }  
    9. catch (Exception ex) {  
    10.     Log.e(TAG,  
    11.           "Bad conversation thread id: "  
    12.           + url.getPathSegments().get(1));  
    13.     return null;  
    14. }  
    15. qb.setTables(TABLE_SMS);  
    16. qb.appendWhere("thread_id = " + threadID);  
    17. break;  

    我们可以看到,它最终还是和第一种方式走上了相同的道儿,两者没什么本质上的区别。但是从简单易用性上来讲,这一种方式是比较好的,朋友们可以比较一下。

    以上就是获取会话内容和短信内容的全部信息,下面我们介绍一下短信的写入操作。

    发送和写入短信

    在某些场合,我们需要发送短信,并将短信写入数据源中,这时我们就需要了解一下发送短信机制和写入短信机制。

    我们将试图发送一条短信到指定的地址,同时将短信的内容写入到短信数据源中,待短信发送成功后,我们告知用户发送成功,待对方接收到短信后,我们告知用户对方接收成功。

    要实现这些功能,我们需要了解以下几个重点内容:

    1.使用android.telephony.SmsManager的API发送短信

    2.使用ContentProvider机制对“content://sms/sent”这个URI进行写入操作

    3.注册“SENT_SMS_ACTION”这个广播地址,待短信发送成功后接收到这条广播

    4.注册“DELIVERED_SMS_ACTION”这个广播地址,待对方接收到短信后接收到这条广播

    下面我们就用代码实现这些功能,创建一个名为SMSActivity的Activity,如下:

    [java] view plaincopy
     
     
     
    1. package com.scott.provider;  
    2.   
    3. import java.util.List;  
    4.   
    5. import android.app.Activity;  
    6. import android.app.PendingIntent;  
    7. import android.content.BroadcastReceiver;  
    8. import android.content.ContentValues;  
    9. import android.content.Context;  
    10. import android.content.Intent;  
    11. import android.content.IntentFilter;  
    12. import android.net.Uri;  
    13. import android.os.Bundle;  
    14. import android.telephony.SmsManager;  
    15. import android.view.View;  
    16. import android.widget.EditText;  
    17. import android.widget.Toast;  
    18.   
    19. public class SMSActivity extends Activity {  
    20.       
    21.     private SendReceiver sendReceiver = new SendReceiver();  
    22.     private DeliverReceiver deliverReceiver = new DeliverReceiver();  
    23.       
    24.     private EditText address;  
    25.     private EditText body;  
    26.       
    27.     @Override  
    28.     protected void onCreate(Bundle savedInstanceState) {  
    29.         super.onCreate(savedInstanceState);  
    30.         setContentView(R.layout.sms);  
    31.           
    32.         address = (EditText) findViewById(R.id.address);  
    33.         body = (EditText) findViewById(R.id.body);  
    34.           
    35.         //注册发送成功的广播  
    36.         registerReceiver(sendReceiver, new IntentFilter("SENT_SMS_ACTION"));  
    37.         //注册接收成功的广播  
    38.         registerReceiver(deliverReceiver, new IntentFilter("DELIVERED_SMS_ACTION"));  
    39.     }  
    40.   
    41.     @Override  
    42.     protected void onDestroy() {  
    43.         super.onDestroy();  
    44.           
    45.         unregisterReceiver(sendReceiver);  
    46.         unregisterReceiver(deliverReceiver);  
    47.     }  
    48.       
    49.     public void sendSMS(View view) {  
    50.         String address = this.address.getText().toString();  
    51.         String body = this.body.getText().toString();  
    52.         //android.telephony.SmsManager, not [android.telephony.gsm.SmsManager]  
    53.         SmsManager smsManager = SmsManager.getDefault();  
    54.         //短信发送成功或失败后会产生一条SENT_SMS_ACTION的广播  
    55.         PendingIntent sendIntent = PendingIntent.getBroadcast(this, 0, new Intent("SENT_SMS_ACTION"), 0);  
    56.         //接收方成功收到短信后,发送方会产生一条DELIVERED_SMS_ACTION广播  
    57.         PendingIntent deliveryIntent = PendingIntent.getBroadcast(this, 0, new Intent("DELIVERED_SMS_ACTION"), 0);  
    58.         if (body.length() > 70) {    //如果字数超过70,需拆分成多条短信发送  
    59.             List<String> msgs = smsManager.divideMessage(body);  
    60.             for (String msg : msgs) {  
    61.                 smsManager.sendTextMessage(address, null, msg, sendIntent, deliveryIntent);                          
    62.             }  
    63.         } else {  
    64.             smsManager.sendTextMessage(address, null, body, sendIntent, deliveryIntent);  
    65.         }  
    66.           
    67.         //写入到短信数据源  
    68.         ContentValues values = new ContentValues();  
    69.         values.put("address",address);  //发送地址  
    70.         values.put("body", body);   //消息内容  
    71.         values.put("date", System.currentTimeMillis()); //创建时间  
    72.         values.put("read", 0);  //0:未读;1:已读  
    73.         values.put("type", 2);  //1:接收;2:发送  
    74.         getContentResolver().insert(Uri.parse("content://sms/sent"), values);   //插入数据  
    75.     }  
    76.       
    77.     private class SendReceiver extends BroadcastReceiver {  
    78.   
    79.         @Override  
    80.         public void onReceive(Context context, Intent intent) {  
    81.             switch (getResultCode()) {  
    82.             case Activity.RESULT_OK:  
    83.                 Toast.makeText(context, "Sent Successfully.", Toast.LENGTH_SHORT).show();  
    84.                 break;  
    85.             default:  
    86.                 Toast.makeText(context, "Failed to Send.", Toast.LENGTH_SHORT).show();  
    87.             }  
    88.         }  
    89.     }  
    90.   
    91.     /** 
    92.      * 发送方的短信发送到对方手机上之后,对方手机会返回给运营商一个信号, 
    93.      * 运营商再把这个信号发给发送方,发送方此时可确认对方接收成功 
    94.      * 模拟器不支持,真机上需等待片刻 
    95.      * @author user 
    96.      * 
    97.      */  
    98.     private class DeliverReceiver extends BroadcastReceiver {  
    99.   
    100.         @Override  
    101.         public void onReceive(Context context, Intent intent) {  
    102.             Toast.makeText(context, "Delivered Successfully.", Toast.LENGTH_SHORT).show();  
    103.         }  
    104.     }  
    105. }  

    布局文件如下:

    [html] view plaincopy
     
     
     
    1. <?xml version="1.0" encoding="utf-8"?>  
    2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    3.     android:orientation="vertical"  
    4.     android:layout_width="fill_parent"  
    5.     android:layout_height="fill_parent">  
    6.     <TextView  
    7.         android:layout_width="fill_parent"  
    8.         android:layout_height="wrap_content"  
    9.         android:text="address"/>  
    10.     <EditText  
    11.         android:id="@+id/address"  
    12.         android:layout_width="fill_parent"  
    13.         android:layout_height="wrap_content"/>  
    14.     <TextView  
    15.         android:layout_width="fill_parent"  
    16.         android:layout_height="wrap_content"  
    17.         android:text="body"/>  
    18.     <EditText  
    19.         android:id="@+id/body"  
    20.         android:layout_width="fill_parent"  
    21.         android:layout_height="150dp"  
    22.         android:gravity="top"/>  
    23.     <Button  
    24.         android:layout_width="fill_parent"  
    25.         android:layout_height="wrap_content"  
    26.         android:text="sendSMS"  
    27.         android:onClick="sendSMS"/>  
    28. </LinearLayout>  

    需要注意的是,这个过程要声明发送短信的权限和写入短信的权限,我们在AndroidManifest.xml的声明如下:

    [html] view plaincopy
     
     
     
    1. <!-- 发送短消息 -->  
    2.    <uses-permission android:name="android.permission.SEND_SMS"/>  
    3.    <!-- 写入短消息 -->  
    4.    <uses-permission android:name="android.permission.WRITE_SMS" />  

    然后,运行该程序,我们让Jack给Lisa发送一条短信,看看结果如何:

    看来我们的操作成功了,到底Jack能不能泡到Lisa呢,朋友们,发挥你们的想象力吧。

    最后需要注意的一件事,代码里也提到过,就是在模拟器测试时,是不支持“接收成功”这个功能的,所以朋友们想要看到“Delivered Successfully”,还必须在真机上试,并且需要耐心等上片刻。感兴趣的朋友赶紧试一试吧。

    来源:http://blog.csdn.net/liuhe688/article/details/7020612

  • 相关阅读:
    Let和Const的使用
    Spring框架学习10——JDBC Template 实现数据库操作
    python 学习
    delphi
    mysql 客户端连接报错Illegal mix of collations for operation
    tnsping 不通
    orm总结
    other
    resultset 查询时返回多个相同值
    vlan 知识学习
  • 原文地址:https://www.cnblogs.com/mochaMM/p/5160937.html
Copyright © 2011-2022 走看看