Android App 可以定时启动! 并且完成短信自动发送获取内容功能 (以获取闪讯密码为例 大学宿舍宽带)

接上一篇:android 发送短信sendTextMessage()真机运行报错,退出,在已申请SEND_SMS权限的情况下Android send SMS not working uid 。。。

重开一篇,完整讲述我这个半吊子的android 入门人员是怎么做出一个可以定时启动并且发送短信,读取回信里面的密码 这个功能的app 至于我为什么要做这个功能,可以看上一篇文章。主要是大学里面宽带密码定期更新手动去获取太麻烦~~

得先放出运行效果视屏才行,如下:只是点击了发送按钮,就会做到自动发送、接收、提取短信收到的密码,更能够在29个小时后自动启动。

有了上面的gif 好解释多了, 不能放视屏这个很头大。。视屏上去截取gif 还不能超过5M。。。我忍。。

权限:

<uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />

其中  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<String> 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 的广播,我们只要注册一个针对这个广播的广播接收器就行
<receiver
            android:name=".SmsReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter android:priority="999">
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver>

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                            会听取各位指教

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值