接上一篇:android 发送短信sendTextMessage()真机运行报错,退出,在已申请SEND_SMS权限的情况下Android send SMS not working uid 。。。
重开一篇,完整讲述我这个半吊子的android 入门人员是怎么做出一个可以定时启动并且发送短信,读取回信里面的密码 这个功能的app 至于我为什么要做这个功能,可以看上一篇文章。主要是大学里面宽带密码定期更新手动去获取太麻烦~~
得先放出运行效果视屏才行,如下:只是点击了发送按钮,就会做到自动发送、接收、提取短信收到的密码,更能够在29个小时后自动启动。
有了上面的gif 好解释多了, 不能放视屏这个很头大。。视屏上去截取gif 还不能超过5M。。。我忍。。
权限:
其中 SEND_SMS、 READ_SMS 、RECEIVE_SMS 为危险权限,android6.0之后要动态申请,这个一开始也是入坑吃了亏。。
动态申请权限方法:
多个权限一起申请
askPermissions();// 运行时权限 (动态权限申请)
if(!permissionList.isEmpty()){
String[] permissions = permissionList.toArray(new String[permissionList.size()]);//list-->String
ActivityCompat.requestPermissions(MainActivity.this,permissions,1);
}
askPermissions();
public void askPermissions(){
if(ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.SEND_SMS)!=
PackageManager.PERMISSION_GRANTED){
permissionList.add(Manifest.permission.SEND_SMS);
}
if(ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.READ_SMS)!=
PackageManager.PERMISSION_GRANTED){
permissionList.add(Manifest.permission.READ_SMS);
}
if(ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.RECEIVE_SMS)!=
PackageManager.PERMISSION_GRANTED){
permissionList.add(Manifest.permission.RECEIVE_SMS);
}
//多的同样加进去
}
先大致说一下这个App的内部组成构建
不要嫌弃我字丑的一张思路图:
没错,我就是写字写的不好报的计算机系,我去,这个决定看起来太英明了。。。
其中右上角 程序执行到MainActivity之前打了 “x”叉叉, 因为我这个程序是想要让他自动自动的,然后按照原来的循序下去就不能在此自动启动这个MainActivity了 ,所以就加了一个rebootBroadcaseReceiver 这个广播接收器,也就是绿色箭头指向的程序块,让他接受来自sendMessageService发送的广播,继而再通过startActivity的方法启动MainActivity,而且这个rebootBroadcaseReceiver必须是静态注册的,整个程序中还有一个SmsReceiver 也是通过静态注册的,道理很简单,这个程序要能够自动启动主界面(MainActivity),那时候主页面是没有的,MainActivity是没有运行的,那么广播接收器就不可以在MainActivity里面动态注册。突然发现讲的有好多,,,先来讲各个组件 及其功能吧
0、Send按钮 按着思路图说下去吧,这个send 就是 gif里的发送按钮,点击之后是启动一个后台服务——SendMessageService
1、sendMessageService 这个服务是利用了 android 的Alarm机制,这个app能自动定时启动(不关机前提)的功能就全靠它了,我要一言不合放代码了
package com.example.getpassword;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
public class sendMessageService extends Service {
public sendMessageService() {
}
private String TAG = "sendMessageService";
public static Boolean continueSend;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "weimeng 启动了服务发送短信");
continueSend = getSharedPreferences("data",MODE_PRIVATE).
getBoolean("ContinueSend",true);
// sharedPreference在一个app里是共享的
Log.d(TAG, "weimeng 布尔变量continueSend = "+continueSend);
//判断是否继续发送
if(continueSend){
new Thread(new Runnable() {
@Override
public void run() {
//MainActivity.sendMessage(MainActivity.destinationAddress,MainActivity.MESSAGE);
//改用广播,上面那个方法牵扯到MainActivity的好多属性,不能直接来用
Intent mybroadcastIntent = new Intent();
mybroadcastIntent.setAction(MainActivity.rebootBroadcast);
//1 包名 2 接收器类名
mybroadcastIntent.setComponent(new ComponentName("com.example.getpassword",
"com.example.getpassword.rebootBroadcastReceiver"));//android8.0要设置的
sendBroadcast(mybroadcastIntent);//发送一个广播
}
}).start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int aDayMore = 29*60*60*1000;//一天又多个小时
// int aMinute = 60*1000;//测试用
long triggerAtTime = System.currentTimeMillis()+aDayMore;
Intent i = new Intent(this,sendMessageService.class);//这里搞错了,弄了两天
PendingIntent pi = PendingIntent.getService(this,0,i,0);
manager.set(AlarmManager.RTC_WAKEUP,triggerAtTime,pi);
Log.d(TAG, "onStartCommand: 发送成功");
}
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return null;
}
@Override
public void onDestroy() {
// stopSelf();
super.onDestroy();
}
}
就是利用AlarmManager 设置模式、一个 PendingIntent 、时间,就可以过这么多时间启动这个服务了。,RTC_WAKE这个模式是可以在手机待机黑屏的情况下手机还能使用cpu的意思,就是还能跑,另外加了一个continueSend这个flag变量控制继不继续自动发送功能。
你们还注意到了,这个onstartCommand里面还发了一个广播,即这几行
Intent mybroadcastIntent = new Intent();
mybroadcastIntent.setAction(MainActivity.rebootBroadcast);
//1 包名 2 接收器类名
mybroadcastIntent.setComponent(new ComponentName("com.example.getpassword",
"com.example.getpassword.rebootBroadcastReceiver"));//android8.0要设置的
sendBroadcast(mybroadcastIntent);//发送一个广播
有讲到很多,不得不忽略一些细节了,比如这个setComponent 好像是android8.0之后自定义的广播必须要加的一行代码。
setAction中定义的 MainActivity.rebootBroadcast 是这个:
public static final String rebootBroadcast = "action.rebootActivity";
自己定义的一个广播罢了
这个广播发出去,然后一个静态的广播接收器 rebootBroadcastReceiver接收他,然后再startActivity 的方式再转到MainActivity
package com.example.getpassword;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class rebootBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent intentToStartMainActivity = new Intent(context,MainActivity.class);
intentToStartMainActivity.addFlags(intent.FLAG_ACTIVITY_NEW_TASK);
intentToStartMainActivity.putExtra("name","rebootIntent");
context.startActivity(intentToStartMainActivity);//启动MainActivity
}
}
这里广播接收器里的onReceive方法里怎么用Intent启动Activity要加一个
.addFlags(intent.FLAG_ACTIVITY_NEW_TASK);
方法,这个高版本的android系统要有的,这个也是网上查的,可以自行科普。
有人可能会问,为什么要传给一个广播接收器 rebootBroadcastReceiver 而不是直接在MainActivity里面动态注册一个广播接收器,像这样: 有一个动态的在MainActivity注册 广播接收器接受他, 接收到了,会调用MainActivity里的sendMessage功能,这样 短信就真的发送出去了~~这不是很好?
事实上我也这么做过,可这样下一次就自动启动不了了呀!MainActivity必须要在活着的时候才有用,关闭退出之后我怎么再接收到SendMessageService发来的广播? 只有一个 死了还能接收广播的 静态注册的广播接收器 接收到了这个广播,通过这个广播接收器作为中介,再用一个Intent 到MainActivity.class 才能让这个 App “复活” 啊
这篇文章的脊髓都在上面的几个字里了, 另外MainActivity 不能设置成 singleTask 或者其他,这个我也不知道为什么,设置之后这个 “复活” 不过来。。
下面在rebootBroadcastReceiver 运行
context.startActivity(intentToStartMainActivity);//启动MainActivity
之后 接收到Intent 的代码:
public static final String destinationAddress = "106593005";
public static final String MESSAGE = "mm";
Intent rebootIntent = getIntent();
String name = rebootIntent.getStringExtra("name");
if(name!=null&&name.equals("rebootIntent")){//短路机制,前一个条件不满足自动跳过if
Log.d(TAG, "收到重启广播 的 命令并已重启,将发送短信");
sendMessage(destinationAddress,MESSAGE);
}
else{
Log.d(TAG,"没有收到rebootIntent");
}
sendMessage :
/*
*调用系统短信接口发送短信
*/
public static void sendMessage(String phoneNumber, String message) {
SmsManager smsManager = SmsManager.getDefault();
List divideContents = smsManager.divideMessage(message);
for (String text : divideContents) {
smsManager.sendTextMessage(phoneNumber, null, text, MainActivity.sentPI, MainActivity.deliverPI);
}
}
这个号码,这个mm 什么意思?
好了,从按下按钮 -》启动服务-》服务里发送广播-》广播里启动MainActivity-》MainActivity里面发送短信,
绕了一圈,终于把短信发出去了,好像很麻烦,但是你忽略了什么? 那个红色标志的环节!——他会过一段时间自行启动的呀!
这样你按过按钮之后,一段时间后重复 服务-》服务里发送广播-》广播里启动MainActivity-》MainActivity里面发送短信
一段时间后再重复 服务-》服务里发送广播-》广播里启动MainActivity-》MainActivity里面发送短信
。
。
。
不就 一直下去了吗,闪讯密码周期是28小时,所以我给这时间设置了29小时,不是挺好,哈哈哈。。
MainActivity 其他功能自然不必多说,所有UI控件 、动态的一溜儿广播接收器,通过手机接口发送短信的方法函数、有关按键操作的事件。。。
2、SmsReceiver: 这个是手机接收到短信后,会发送一条广播,Manifest.xml 里面定义如下,可见系统会发送一条
android.provider.Telephony.SMS_RECEIVED 的广播,我们只要注册一个针对这个广播的广播接收器就行
SmsReceiver代码如下:
package com.example.getpassword;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.util.Log;
import android.widget.Toast;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SmsReceiver extends BroadcastReceiver {
private String TAG = "SmsReceiver";
MainActivity activity;
private static final String queryString = "尊敬的";//"2018";
private static final String phoneNumber = "106593005";//"10086";
@Override
public void onReceive(Context context, Intent intent) {
// Toast.makeText( "222我接收到了", Toast.LENGTH_SHORT).show();
Log.d(TAG, "onReceive: 广播接收器接收到了");
Bundle bundle = intent.getExtras();
SmsMessage msg;
if(null!=bundle)
{
Object[] smsObj = (Object[]) bundle.get("pdus");
for (Object object : smsObj) {
msg = SmsMessage.createFromPdu((byte[]) object);
Date date = new Date(msg.getTimestampMillis());//时间
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String receiveTime = format.format(date);
String messageBody = msg.getMessageBody();
Log.d("SmsReceiver", "Received: number:" + msg.getOriginatingAddress()
+ " body:" + messageBody + " time:"
+ receiveTime);
//在这里写自己的逻辑
if (msg.getOriginatingAddress().equals(phoneNumber) &&
messageBody.toLowerCase().startsWith(queryString)) {
//是这个号码&& 是这个短信内容
Intent serviceIntent = new Intent(context,GetPasswordService.class);
serviceIntent.putExtra("body",messageBody);
serviceIntent.putExtra("time",receiveTime);
context.startService(serviceIntent);//去 GetPasswordService.class
//此处context是指 我想搞清楚
// Log.d(TAG, "此处context是指 "+context.toString());
//android.app.ReceiverRestrictedContext
// Log.d(TAG, "applicationContext"+context.getApplicationContext().toString());
//android.app.Application@4651f82
}
}
}
}
}
还有一些 当初测试调试时候的 代码,舍不得删,,这个广播接收器不仅可以接受系统受到短信这个广播,还能读取收到短信的内容,虽然不推荐广播接收器里干过多的逻辑处理事情,但是这点处理逻辑还是可以接受的,具体方法可以看上面代码,我所需的内容是 短信的内容 body 和 短信收到的时间 time 这两段内容,就交由我的 下一个服务组件 getPasswordService 来处理了。
3、getPasswordService: 为什么要单开一个服务去读取短信内容 提取密码呢, 就理解为不要让MainActivity 过于冗杂好了,太多的功能,看着就揪心呀,就没有
因为才学android没多久,就用了一个 IntentService 做这次的服务组件,这个类型的Service的好处就是,他会在服务结束后自动关闭自己,而不是像别的服务一样要stopSelf(),但结果创建出来有好多自己生成的代码,这里就不全放出来了,只放有用的:其实就是 一个 onHandleIntent 函数 ,但里面 if(intent!=null) {}语句也是自动生成的。这个intent 就是SmsReceiver 传过来的,包含了 body 和time
//乱七八糟一堆,有用的就下面这个
String body="",time="";
String TAG="GetPassWordService";
@Override
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "weimeng 得到密码了");
if (intent != null) {
final String action = intent.getAction();
if (ACTION_FOO.equals(action)) {
final String param1 = intent.getStringExtra(EXTRA_PARAM1);
final String param2 = intent.getStringExtra(EXTRA_PARAM2);
handleActionFoo(param1, param2);
} else if (ACTION_BAZ.equals(action)) {
final String param1 = intent.getStringExtra(EXTRA_PARAM1);
final String param2 = intent.getStringExtra(EXTRA_PARAM2);
handleActionBaz(param1, param2);
}
}
body = intent.getStringExtra("body");
time = intent.getStringExtra("time");
Log.d(TAG, "weimeng Get body :"+body);
Log.d(TAG, "weimeng Get time"+time);
new Thread(new Runnable() {
@Override
public void run() {
String regEx = "[0-9]{6}";//10086
Pattern pattern = Pattern.compile(regEx);
Matcher matcher = pattern.matcher(body);
String send;
if(matcher.find()){
send = matcher.group();
}else {
send = "没有找到匹配数字";
}
Intent sendIntent = new Intent();
sendIntent.setAction(MainActivity.letPasswordBroadcast);//这里广播的 action 一定要记住,不要搞混了
sendIntent.putExtra("password",send);
sendIntent.putExtra("time",time);
//1 包名 2 接收器类名
// sendIntent.setComponent(new ComponentName("com.example.getpassword",
// "com.example.getpassword.MainActivity.myReceiver"));//android8.0要设置的
sendBroadcast(sendIntent);//轮了一圈再发回 MainActivity
}
}).start();
}
用一个正则表达式解析出短信里的 六位数 密码,然后以广播的形式发送回 ,广播也是自定义广播,在MainActivity里定义:
public static final String letPasswordBroadcast = "action.getPasswordBroadcast";//让密码广播!
这时 因为发送短信前 MainActivity 已经被rebootBroadcastReceiver 叫醒了,那么就可以在他内部 动态注册一个广播接收器:
class myReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
String password = intent.getStringExtra("password");
String time = intent.getStringExtra("time");
SharedPreferences.Editor editor = getSharedPreferences("data",MODE_PRIVATE).edit();
editor.putString("password",password);
editor.putString("time",time);
editor.apply();
showText();
getNotificationManager().notify(1,getNotification("闪讯助手密码更新","password:"+password));
}
}
显示内容封装为 showText()函数:
private void showText(){
SharedPreferences pref = getSharedPreferences("data",MODE_PRIVATE);
String password = pref.getString("password","not get yet");
String time = pref.getString("time","not get yet");
tv.setText("密码: "+password+"\n上次更新时间:"+time);
}
因为要有记忆功能,下次点进去的时候还能看到密码才行,所以用SharedPreference方法 存取 数据
好了,app主体功能就是这样了,然后还做了一个 读取最新一条短信的 按钮 ,这个写这个app时候学到的:
public void getSmsFromPhone() {
ContentResolver cr = getContentResolver();
String[] projection = new String[] { "body" };//"_id", "address", "person",, "date", "type
String where = "address = '106593005'";
// String where = " address = '10086' " ;
Cursor cur = cr.query(SMS_INBOX, projection, null, null, "date desc");
// showToast(""+cur.toString());
//此处读取内容指定为 where 失败了, cur.query第三个 selection 参数指定为where 读取不到不会返回null。。。
if (null == cur) {
showToast("mei");
tv.setText("没有收到过来自" + destinationAddress + "的短信!");
return;
}
if(cur.moveToNext()) { // cur初始是-1 moveToNext后为0 另外 moveToFirst也是0 代表数据库第一行
// int number = cur.getInt(cur.getColumnIndex("address"));//手机号
// String name = cur.getString(cur.getColumnIndex("person"));//联系人姓名列表
String body = cur.getString(cur.getColumnIndex("body"));
Log.d(TAG, "getSmsFromPhone: "+body);
tv.setText(body);
//这里我是要获取自己短信服务号码中的验证码~~
Pattern pattern = Pattern.compile(" [a-zA-Z0-9]{10}");
Matcher matcher = pattern.matcher(body);
if (matcher.find()) {
String res = matcher.group().substring(1, 11);
Log.d(TAG, "getSmsFromPhoneContent: "+res);
}
}
}
也挺有意思的。可以直接查看发来的短信内容,这个也是网上查的,但是改过,仅做参考
好了又可以加入一下环节:
服务-》服务里发送广播-》广播里启动MainActivity-》MainActivity里面发送短信 -》发送短信、收到短信(SmsReceiver)=》 getPasswordService 里面得到密码等内容 发送一条自定义广播=》 MainActivity收到并显示 。
然后这个服务 设置为了29小时候启动,这一切也就周而复始了。
注:
service 的AlarmManager 机制 在android6.0 里面表现很好,即使程序退出、被360清理掉后台内存 ,它也能在指定时间自行“复活” ,息屏状态下也能在那个时候运行,理论上只要不关机这个app 就可以自己定时启动了。
但android8.0 手机 把app后台给“划掉”,完全退出后,就活不过来了。。除非不去清除后台的服务,我也是新手,还不懂这个android各个版本之间的差别,另外别问我为什么就android6.0、8.0我知道,因为我只有这么两只手机,也只能测试到这么多了,bug肯定是有的,不要嫌弃啊。。
只是 懒得 手动去获取密码 ,。。。
代码GitHub地址:AutoReboot-GetPassword-App
apk安装包:百度云地址 提取码:z73y 会听取各位指教