转自: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的单元测试类,用于读取会话信息和短信内容,代码如下:

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

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

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

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

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

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

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

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

  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/”的子集,分别用了不同的选择条件对短信表进行查询,我们看一下具体的源代码:

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

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

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

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

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

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

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

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

  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,如下:

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

布局文件如下:

  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的声明如下:

  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

基础总结篇之七:ContentProvider之读写短消息的更多相关文章

  1. 基础总结篇之八:创建及调用自己的ContentProvider

    转自:http://blog.csdn.net/wellsoho/article/details/49494141 若不能坚持到底,即使是朽木也不能折断:只要坚持不停地用刀刻,就算是金属玉石也可以雕出 ...

  2. C# Xamarin移动开发基础进修篇

    一.课程介绍 英文原文:C# is the best language for mobile app development. Anything you can do in Objective-C, ...

  3. FPGA基础入门篇(四) 边沿检测电路

    FPGA基础入门篇(四)--边沿检测电路 一.边沿检测 边沿检测,就是检测输入信号,或者FPGA内部逻辑信号的跳变,即上升沿或者下降沿的检测.在检测到所需要的边沿后产生一个高电平的脉冲.这在FPGA电 ...

  4. 鸿蒙内核源码分析(VFS篇) | 文件系统和谐共处的基础 | 百篇博客分析OpenHarmony源码 | v68.01

    子曰:"质胜文则野,文胜质则史.文质彬彬,然后君子." <论语>:雍也篇 百篇博客系列篇.本篇为: v68.xx 鸿蒙内核源码分析(VFS篇) | 文件系统和谐共处的基 ...

  5. 鸿蒙内核源码分析(字符设备篇) | 字节为单位读写的设备 | 百篇博客分析OpenHarmony源码 | v67.01

    百篇博客系列篇.本篇为: v67.xx 鸿蒙内核源码分析(字符设备篇) | 字节为单位读写的设备 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一 ...

  6. 云小课|DGC数据开发之基础入门篇

    阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说).深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云.更多精彩内容请单击此处. 摘要:欢迎来到DGC数据 ...

  7. javamail模拟邮箱功能发送电子邮件-基础实战篇(javamail API电子邮件实例)

    引言: JavaMail 是一种可选的.能用于读取.编写和发送电子消息的包 JavaMail jar包下载地址:http://java.sun.com/products/javamail/downlo ...

  8. 【Java面试】基础知识篇

    [Java面试]基础知识篇 Java基础知识总结,主要包括数据类型,string类,集合,线程,时间,正则,流,jdk5--8各个版本的新特性,等等.不足的地方,欢迎大家补充.源码分享见个人公告.Ja ...

  9. 安卓布局修改基础常识篇之TextView属性

    [天使]安卓布局修改基础常识篇之TextView属性 在修改布局xml文件时需要熟练掌握一些属性,以下是TextView也就是文本的属性:android:autoLink 是否自动链接网址或邮箱地址: ...

随机推荐

  1. 【转载】C/C++编译过程分析

    转自:http://www.360doc.com/content/14/0109/16/835125_343879650.shtml C/C++编译过程 C/C++编译过程主要分为4个过程 1) 编译 ...

  2. python接口自动化测试 - requests库的post请求进行文件上传

    前言 如果需要发送文件到服务器,比如上传图片.视频等,就需要发送二进制数据. 一般上传文件使用的都是 Content-Type: multipart/form-data; 数据类型,可以发送文件,也可 ...

  3. Bugku-CTF之login1(SKCTF) [SQL约束攻击]

    Day26 login1(SKCTF) http://123.206.31.85:49163/flag格式:SKCTF{xxxxxxxxxxxxxxxxx}hint:SQL约束攻击  本题要点:SQL ...

  4. pikachu平台搭建

    1.将pikachu转移至htdocs 2.然后打开pikachu文件夹里的inc文件夹 3.里面对应的内容该成之前刚刚设置好的数据库服务器地址,用户名,密码和端口号 4.打开浏览器,输入http:/ ...

  5. bugku 点击1000000次

    首先看一下题目发现进入网页之后是这个样的 然后点击一下发现是有变化 然后用F12 然后选择post data 然后输入clicks=1000000 然后就会发现答案 (clicks 是点击的意思)

  6. 微信个人支付接口---YunGouOS 1.1.3 版本发布,新增个人微信/支付宝收款接口

    软件接口  https://www.oschina.net/news/113305/yungouos-1-1-3-released 文档说明  https://www.oschina.net/p/Yu ...

  7. 【音乐欣赏】《TIT FOR TAT》 - MYTH & ROID

    曲名:TIT FOR TAT 作者:MYTH & ROID [00:00.000] 作曲 : MYTH & ROID [00:01.000] 作词 : MYTH & ROID ...

  8. 每天进步一点点------Sobel算子(3)基于彩色图像边缘差分的运动目标检测算法

    摘  要: 针对目前常用的运动目标提取易受到噪声影响.易出现阴影和误检漏检等情况,提出了一种基于Sobel算子的彩色边缘图像检测和帧差分相结合的检测方法.首先用Sobel算子提取视频流中连续4帧图像的 ...

  9. XFire调用CXF参数为Null的问题

    最近,领导分配了一个任务,做接口联调.情况是这样,对方客户升级了接口采用CXF,而我们还是用的XFire1.2.6,首先就遇到了这个问题:XFire调用CXF参数为Null的问题 . 在网上搜了一大堆 ...

  10. ajax请求无法下载文件的原因

    原因: Ajax下载文件的这种方式本来就是禁止的.出于安全因素的考虑,javascript是不能够保存文件到本地的, 所以ajax考虑到了这点,只是接受json,text,html,xml格式的返回值 ...