写在前面:
刚学习Android开发就很想做这么一个工具了。最近终于用eclipse把代码敲出来了,写博客记录之。
首先说下整体思路如下:
- 后台开启一个Servic通过ContentObserver监听通话记录表的变化。
- 如果有变化则通过代码判断是否是刚产生的未接来电。
- 需要发生短信,则调用发送短信的代码。
我遇到的问题:
- 我监听的位置是“CallLog.Calls.CONTENT_URI”,然而我却发现ContentObserver的onChange方法被频繁触发。(即使没有产生通话电记录)
- 发生短信为了防止发送失败,注册短信发生状态的广播接收。通过intent传递电话号码和短信发生内容。然而测试中却发生intent中获取到的值都是第一次添加的值。(并不会更新)
问题的不优雅解决:(希望得到前辈们指点,优雅地解决这两个问题)
- 既然ContentObserver的onChange方法被频繁触发,那么多一些判断,判断是否是刚发生的未接来电记录是则往下运行,不是则忽略。
- 既然intent中获取的数据不准确,那么就换个地方获取数据。我选择了MyApplication做数据中转站。
关键代码片段:
package com.zji.service;
import java.util.Date;
import com.zji.activity.MyApplication;
import com.zji.broadcase.SendMessageReceiver;
import com.zji.db.MyDatabaseHelper;
import com.zji.utils.SendMessage;
import com.zji.utils.Timer;
import com.zji.utils.WriteAndRead;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.database.Cursor;
import android.os.Handler;
import android.os.IBinder;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.widget.Toast;
/**
* 后台运行的服务,负责开启监听通话记录表的变化
* @author phlofy
* @date 2016年3月3日 下午2:13:29
*/
public class MainService extends Service{
MyContentObserver mMyContentObserver;
SendMessageReceiver mSendMessageReceiver;
public static boolean isWorking = false; // 方便MainFragment知道是否开启后台监听服务
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
// 注册通话记录表监听
mMyContentObserver = new MyContentObserver(this, new Handler());
this.getContentResolver().registerContentObserver(CallLog.Calls.CONTENT_URI, false, mMyContentObserver);
// 注册短信发送状态监听
IntentFilter intentFilter = new IntentFilter("SENT_SMS_ACTION");
mSendMessageReceiver = new SendMessageReceiver();
this.registerReceiver(mSendMessageReceiver, intentFilter);
isWorking = true;
try{
Toast.makeText(this, "服务开始运行", Toast.LENGTH_LONG).show();
}catch(Exception e){}
}
@Override
public void onDestroy() {
super.onDestroy();
// 注销两个监听
this.getContentResolver().unregisterContentObserver(mMyContentObserver);
this.unregisterReceiver(mSendMessageReceiver);
isWorking = false;
try{
Toast.makeText(this, "服务停止运行", Toast.LENGTH_LONG).show();
}catch(Exception e){}
}
}
/**
* 通话记录表变化的监听者
* @author Administrator
*
*/
class MyContentObserver extends ContentObserver{
Context context;
MyDatabaseHelper db;
SharedPreferences preferences;
SharedPreferences.Editor editor;
public MyContentObserver(Context context, Handler handler) {
super(handler);
this.context = context;
db = new MyDatabaseHelper(context);
preferences = context.getSharedPreferences("autosend", Context.MODE_WORLD_READABLE);
editor = preferences.edit();
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
/****************获取到通话记录表的最新一条消息******************/
Cursor cursor = context.getContentResolver().query(CallLog.Calls.CONTENT_URI, new String[]{Calls.NUMBER,Calls.CACHED_NAME,Calls.DATE,Calls.TYPE}, Calls.TYPE+" = ?", new String[]{Calls.MISSED_TYPE+""}, Calls.DEFAULT_SORT_ORDER);
cursor.moveToFirst();
String name = cursor.getString(cursor.getColumnIndex(Calls.CACHED_NAME));
String number = cursor.getString(cursor.getColumnIndex(Calls.NUMBER));
long date = cursor.getLong(cursor.getColumnIndex(Calls.DATE));
int type = cursor.getInt(cursor.getColumnIndex(Calls.TYPE));
if(cursor != null){
cursor.close();
}
/**
* 判断该未接来电是否是该软件安装后发生。
* 防止没有未接来电,但onChange还是被执行的情况。
* 解决软件第一次安装后onChange被触发自动发送一条短信问题
*/
long lifeStart = preferences.getLong("life_start", 0); //试图获取软件安装时间
if(lifeStart == 0){
// 为0说明软件第一次执行,记录此时时间为软件安装时间
editor.putLong("life_start", new Date().getTime());
editor.commit();
}
if(lifeStart == 0 || date < lifeStart){
// 忽略掉软件安装前的未接来电
return;
}
/*******************查找短信发送表中近“经济时间”内是否有该号码********************/
long whereTime = date - preferences.getInt("time", 30)*60000; // 记录的时间 - “经济时间”
// 该号码在短信发送表中的近“经济时间”内的记录
Cursor cursorDb = db.getReadableDatabase().rawQuery("select * from "+db.SEND_NOTES+" where "+Calls.NUMBER+" = ? and time > ? ", new String[]{number,whereTime+""});
/*********************短信操作***********************/
if(cursorDb.moveToNext()){
// 有记录,不发送短信
}
else{
// 没有记录,发送短信
MyApplication instance = MyApplication.getInstance();
if(instance.getNumber() != null) {
// 已经规定MyApplication中的name、number、content为“现在”变量,
// 因此过一定时间(一般为短信开始发送到发送成功的时间)后将为被置空
// 如果不为空,说明发生了onChange短时间被多次触发
return;
}
instance.setName(name);
instance.setNumber(number);
instance.setContent(preferences.getString("content", "抱歉,未能及时接听您的来电。\n【来电管家自动回复】"));
SendMessage.sendTextMessage(context, name, number, instance.getContent());
}
if(cursorDb != null){
cursorDb.close();
}
if(db != null){
db.close();
}
}
}
package com.zji.utils;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.telephony.SmsManager;
import android.widget.Toast;
/**
* 发送短信类
* @author phlofy
* @date 2016年3月3日 下午9:58:45
*/
public class SendMessage {
synchronized public static void sendTextMessage(Context context, String name, String number, String content){
Intent in = new Intent("SENT_SMS_ACTION");
PendingIntent pi = PendingIntent.getBroadcast(context, 0, in, 0);
SmsManager.getDefault().sendTextMessage(number, null, content, pi, null);
}
}
package com.zji.broadcase;
import com.zji.activity.MyApplication;
import com.zji.db.MyDatabaseHelper;
import com.zji.utils.SendMessage;
import com.zji.utils.Timer;
import com.zji.utils.WriteAndRead;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.preference.Preference;
import android.telephony.SmsManager;
import android.widget.Toast;
public class SendMessageReceiver extends BroadcastReceiver{
MyDatabaseHelper db;
static int errCount = 0; // 记录一条短信发送失败次数
@Override
public void onReceive(Context context, Intent intent) {
if("SENT_SMS_ACTION".equals(intent.getAction())){
try{
MyApplication instance = MyApplication.getInstance();
switch (getResultCode()) {
// 短信发送成功
case Activity.RESULT_OK:
db = new MyDatabaseHelper(context);
db.getReadableDatabase().execSQL("insert into "+db.SEND_NOTES+" values(null , ? , ? , ?)",new String[]{instance.getName(),instance.getNumber(),Timer.getNowDate()+""});
if(db != null){
db.close();
}
errCount = 0;
// 短时间变量,用完后将其置空
instance.setName(null);
instance.setNumber(null);
instance.setContent(null);
break;
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
case SmsManager.RESULT_ERROR_NO_SERVICE:
case SmsManager.RESULT_ERROR_NULL_PDU:
case SmsManager.RESULT_ERROR_RADIO_OFF:
if(errCount < 2){
// 最多可以尝试发送三遍
SendMessage.sendTextMessage(context, instance.getName(), instance.getNumber(), instance.getContent());
errCount++;
}
else {
// 尝试发送三遍仍然发送不出去,放弃发送
errCount = 0;
// 短时间变量,用完后将其置空
instance.setName(null);
instance.setNumber(null);
instance.setContent(null);
}
break;
default:
break;
}
}catch(Exception e){
e.printStackTrace();
}
finally{
}
}
}
}
package com.zji.activity;
import android.app.Application;
/**
* 用于存放中间变量
* @author phlofy
* @date 2016年3月4日 下午9:55:33
*/
public class MyApplication extends Application{
private static MyApplication myApplication = null;
/**
* “现在”短信要发送的目标
* 1.为了防止MyContentObserver.onChange方法短时间内被多次触发,
* 造成还未来得及插入短信发送成功的记录,短信重复发送出去
* 2.解决传递给SendMessageReceiver的Intent数据为上一次(第一次)
* 的数据。替代通过Intent得到number和name
*/
String number;
String name;
String content;
@Override
public void onCreate() {
super.onCreate();
//由于Application类本身已经单例,所以直接按以下处理即可。
myApplication = this;
}
/**
* 获取Application实例
* @return
*/
public static MyApplication getInstance(){
return myApplication;
}
public void setNumber(String number) {
this.number = number;
}
/**
* 获取现在短信的目标号码
* @return
*/
public String getNumber() {
return number;
}
public void setName(String name) {
this.name = name;
}
/**
* 获取现在短信的目标者名称
* @return
*/
public String getName() {
return name;
}
public void setContent(String content) {
this.content = content;
}
/**
* 获取短信内容
* @return
*/
public String getContent() {
return content;
}
}
最后附上源代码:
AutoSend示例代码