可以在Android上发定时短信
定时短信的界面比较简单,只是几个EditText和Button,功能包括添加联系人,编写短信,设定时间,保存发送等。Android号称拥有四大组件:Activity、Intent、Broadcast和Service。只要熟练掌握了这四大组件,Android开发就变得很简单了。在这个项目中,我用到了前三个,Service没有用到。Activity可以理解为一个窗口或者容器,它是可视化的,里面可以承载各种控件。对于Intent和Broadcast会在后面介绍。
时间的设定是通过android.app.TimePickerDialog类来实现,这个类提供了一个可视化的窗口,对于用户来说界面十分友好。
Android里的数据存储有三种方式。Android中的数据全部都是私有的,但是不同程序之间还是可以互相传递数据,那么Android是怎么做到的呢?原来Android中有一个ContentProvide,ContentProvide是来封装数据的,外界的程序可以通过 ContentProvide 的接口访问数据。Android中使用的是SQLite这个轻量级的嵌入式数据库。这个数据库对于硬件的要求很低,而且占用内存很小,但是速度很快,在嵌入式设备中被广泛应用。不过由于定时短信中需要存储的仅仅是联系人电话和编写的短信,所以我选择了另一种存储方式,那就是SharedPreferences。SharedPreferences提供了一种简单的,键值对的存储方式。一般用来存储一些简单的,少量的数据,存储一条短信再合适不过了。
完成这个小软件,必须解决的问题就是当设定好时间软件关闭后,时间到达时软件能够重新启动发送短信。实现这个功能的是Android里的闹钟唤醒。在android.app.AlarmManager类中提供了一种机制,使程序可以访问到系统的闹铃服务,这样应用程序就可以设定在未来的某个时间点执行。当到达那个时间点时,系统将Broadcast一个Intent来启动注册了闹铃服务的程序。 Intent在官方文档中是这样定义的:An intent is an abstract description of an operation to be performed.可以理解成Intent是对即将要执行的动作的抽象描述。在这里,我主要是把Intent给Broadcast 出去,然后在新建一个BroadcastReceiver 来接收,时间到达时开启一个新的Activity来发送短信。
发送短信需要新建一个Activity,在本Activity里面只需使用 SmsManager这个类就可以了,这个类提供了发送短信的方法。在短信发出之后,一定要记着关掉这个发送短信的Activity,因为这个Activity的生命周期是0,发送完短信之后就没有必要存在了,使用finish()方法就可以了。
这就是这个软件实现的主体思想,但是这样粗略地完成之后还有很多问题,比如在时间设定上,如果设定的时间已经过去,那么点击保存发送之后信息会立即发送;编写好的短信在发送之前仍然不能够查看和编辑;发送号码一栏里只能手动输入号码,不能从通讯录导入...
于是,我把主要的精力用在了软件的完善上。时间设定的问题比较容易解决,只要在点击短信保存按钮时对时间的合理性进行判断即可。但是在点击短信保存按钮的方法中已经不能获取设定的时间了,因此必须在点击设置时间的按钮里进行时间的判断,然后通过一个变量根据时间的合理性进行变化,在点击保存短信的方法中对这个变量进行判断,看看时间的设定是否满足要求。需要注意的是,这里系统时间的获取仍然是采用Java的语法,即final Calendar ca = Calendar.getInstance();int hours = ca.get(Calendar.HOUR);int minutes=ca.get(Calendar.MINUTE);这样就出现了一个小问题。众所周知,Java语言获取的系统时间是12小时制的,也就是说如果系统时间为14:00,那么用户设定8:00显然是不允许的,因为定时短信只能在一天之内发送,8:00代表早上八点,已经过去了。但是系统获取到的时间小时数却是2,因为获取系统时间为12小时制,通过比较8>2,时间设置看似没问题,但实际上时间设置是不正确的。所以还应当通过判断ca.get(Calendar.AM_PM)的值来得知是上午还是下午,如果是下午的话,小时数还应当再加上12。
相对于时间问题,短信的保存就显得很简单了,只要在主Activity里再SharedPreferences一次就可以了,然后在短信发送成功之后把值再变为空。
在整个软件的制作过程中,我遇到的最大问题就是联系人的导入。我前面说过,Android sdk更新的速度很快而且变化很大,这一点在通讯录方面尤为明显!由于我使用的是最新的Android sdk 2.2,可是有关读取通讯录联系人的资料都是使用Android sdk 1.5的API写的,当我在Android sdk 2.2下使用Android sdk 1.5的API时,Eclipse就会提出警告说不建议。没办法,我只能重新学习Android sdk 2.2里面相关的API。二者的API发生了变化,在之后与ListView进行适配时也出现了很多问题。对于联系人的读取都是获取光标实例,然后通过query()方法根据Uri读取,即Cursor c= getContentResolver().query(Phones.CONTENT_URI, null,null, null, null);不同的是,在Android sdk 2.2中Phones.CONTENT_URI变成了ContactsContract.Contacts.CONTENT_URI,另外读取联系人的姓名和电话号码也不同。怪不得有人说做Android开发很痛苦,因为要考虑不同版本之间的兼容性问题。这也没办法,Android系统毕竟是一个新的系统,在成熟性方面还不能与Symbian和WM相比。不过在现在看来,Android更新的速度已经降下来了,而且以后应该也不会出现API大幅的变化了。开发人员应该放心了!之后就是与ListView进行适配,把联系人信息显示在ListView中,在ListView的点击方法中把存储的联系人电话号码传到主Activity中。数据的传递又要使用到Intent,只不过与之前简单的跳转不同,这次的跳转需要获取数据,startActivity()变成了startActivityForResult()。
这样,整个程序就相对完善了。当然这个小软件还存在很多问题,比如不能跨天发短信等,界面也不是很友好。这些问题的解决还需要我不断地去钻研和尝试。通过这一个小程序,我认识到自己的Android开发之旅才刚刚起步。不过我也发现Android手机不仅能够吸引用户,因为大家都说Android手机是玩机的最佳选择,而且在这上面做开发也是一种很棒的感觉,因为它提供了一套很优秀很强大的开发工具。借着开源世界强大的Eclipse和Java语言之风,我们有理由相信这个小绿人会飞得越来越高,越来越远...
主程序除了在onCreat()中创建两个EditText控件与一个Button控件外,分别设置onClickLinstener()让用户单击EditText控件时,同时清除内容,在单击Button时送出短信,并通过isPhoneNumberValid()与iswithin70()这两个自定义的方法来检查收件人电话号码的正则表达式,以及短信正文的字数是否超过70个字符。
在两项检查同时通过的前提下,通过PendingIntent.getBroadcast()的方法自定义PendingIntent并进行Broadcasting,而后使用SmsManager.getDefault()(当处理SMS短信相关的活动,例如发送数据、文字与pdu SMS信息,都需要调用这种静态的方法)所预先构建的SmsManager使用sendTextMessage()方法,将相关数据以参数带入,即可完成发送短信的任务。
- /*检查字符串是否为电话号码的方法,并返回true or false的判断值*/
- public static boolean isPhoneNumberValid(String phoneNumber)
- {
- boolean isValid = false;
- /* 可接受的电话格式有:
- * ^//(? : 可以使用 "(" 作为开头
- * (//d{3}): 紧接着三个数字
- * //)? : 可以使用")"继续
- * [- ]? : 在上述格式后可以使用具有选择性的 "-".
- * (//d{3}) : 再紧接着三个数字
- * [- ]? : 可以使用具有选择性的 "-" 继续.
- * (//d{5})$: 以五个数字结束.
- * 可以比较下列数字格式:
- * (123)456-7890, 123-456-7890, 1234567890, (123)-456-7890
- */
- String expression =
- "^//(?(//d{3})//)?[- ]?(//d{3})[- ]?(//d{5})$";
- CharSequence inputStr = phoneNumber;
- /*创建Pattern*/
- Pattern pattern = Pattern.compile(expression);
- /*将Pattern 以参数传入Matcher作Regular expression*/
- Matcher matcher = pattern.matcher(inputStr);
- /*创建Pattern2*/
- Pattern pattern2 =Pattern.compile(expression2);
- /*将Pattern2 以参数传入Matcher2作Regular expression*/
- Matcher matcher2= pattern2.matcher(inputStr);
- if(matcher.matches()||matcher2.matches())
- {
- isValid = true;
- }
- return isValid;
- }
- public static boolean iswithin70(String text)
- {
- if (text.length()<= 70)
- {
- return true;
- }
- else
- {
- return false;
- }
- }
- }
请注意,需要添加发送短信的权限android.permission.SEND_SMS。
扩展学习
//取得android系统中默认的短信管理器
SmsManager manager=SmsManager.getDefault();
//如果短信内容过长时,则对短信内容进行拆分
ArrayList<String> texts=manager.divideMessage(content);
for(String text:texts){
//第一个参数:对方手机号码
//第二个参数:短信中心号码,一般设置为空
//第三个参数:短信内容
//第四个参数:sentIntent判断短信是否发送成功,如果你没有SIM卡,或者网络中断,则可以通过这个intent来判断。
//注意强调的是“发送”的动作是否成功。那么至于对于对方是否收到,另当别论
//第五个参数:当短信发送到收件人时,会收到这个deliveryIntent。即强调了“发送”后的结果
//就是说是在"短信发送成功"和"对方收到此短信"才会激活sentIntent和deliveryIntent这两个Intent。这也相当于是延迟执行了Intent
manager.sendTextMessage(mobile,null, text,null,null);
}
//Toast.makeText(getApplicationContext(), "发送成功", Toast.LENGTH_LONG).show();
Toast.makeText(MainActivity.this,"发送成功",Toast.LENGTH_LONG).show();
}
本范例使用到的PendingIntent对象,具有下列的特性:当接收到PendingIntent对象时,会进行broadcast的动作,就如同使用Context.sendBroadcast()方法一样,这也就是为什么在SmsManager.sendTextMessage()方法中需要传入PendingIntent作为传送服务的参数之一。
在主程序中使用发送短信的方式,只展示了SmsManager类中,可使用的3种传送短信的方法之一,而完整的3种可用方法,整理如表5-1所示。
表5-1 SmsManager类中可使用的3种方法
方 法 名 称 |
传 入 参 数 |
使 用 时 机 |
sendDataMessage |
String destinationAddress, String scAddress, short destin- |
发送Data格式的SMS传送到特定程序的Port |
sendMultipartTextMessage |
String destinationAddress, String scAddress, ArrayList |
发送多条文字短信 |
sendTextMessage |
String destinationAddress, String scAddress, String text, |
发送文字短信 |
另外,本范例并没有实现接收sms的部分,仅发出短信,由于单纯通过运行程序的模拟器,将无法了解短信是否真的有送出,而收件人是否真的有收到。因此,在程序开发的过程中,读者可以通过下面的小技巧来打开两个模拟器,一个进行传送,另一个进行收件的模拟测试。
步骤一:先进入Eclipse,compile运行程序,并顺利开始第一个模拟器实例(Instance)。
步骤二:打开DOS窗口(cmd),并输入命令,进入文件夹:
D:/>cd D:/SDK/android/tools/
步骤三:输入shell command,其中foo为AVD的名称。
D:/SDK/android/tools>emulator -data foo
此时,窗口会跳出另一个模拟器,通过输入左上方的InstanceID(例:5546)作为收件人的电话号码,即可测试短信送达的状态。
最后提到了拆分短信,此范例中虽然自制了简单的判断字符串字符数,却只能接受单则的短信,事实上,在SmsManager里尚有一个公有方法:
public ArrayList<String> divideMessage (String text)