仿系统闹钟,实现更新安装之后还可以响铃,开机启动功能需要手机自带开机自启动

忽然飞来一个新的需求,APP的日程安排需要添加一个闹钟提醒,我的天,这是要逆天啊,刚开始做的时候,没想那么多,因为了解的少吗,再加上不知道QQ和微信等已经被厂商加入了白名单,本来想也就是几天的工作量,没想到结果一做就是一星期,感觉脑细胞已经死光,我还能不能知道1+1=3啊抓狂抓狂抓狂抓狂抓狂抓狂

在15年使用过一个软件,灵犀语音助手(我不是在打广告得意,他也不给我钱发火),他的功能是可以语音定时,告诉他定时时间和需要做的任务,他帮你定时,到时候提醒你,但是做android开发的,都知道android系统的机制,内存不足的时候,会杀死一部分进程发火,导致灵犀还没有提醒你,他的进程就被杀死了。一个星期以前再次下载了,他还是那样,功能也没有修改,到时增加了其他的功能,没有具体看,反正跟我没关系得意,我只在乎闹钟。

需求:

1.APP关闭了,到点了,闹钟必须响铃,日程安排必须提醒

2.APP更新之后,闹钟还能继续运行,日程安排还可以提醒。

3.新的日程安排有服务器使用jputh(极光推送)进行推送,android进行闹钟设置。

4.开机之后不用用户启动APP,手机自启动APP(坑爹的厂商,不说了,反正我做不出来了,说多了都是泪)

5.还有什么啊,我也不知道了,反正就是跟系统闹钟的日程安排一样一样的,就是这个功能。

好了,接下来交给你们了,你们自己想怎么做吧,我颠儿了,继续装逼去得意








*********************************************我是分割线********************************************

装完逼真刺激,所以说,人啊要会装逼,不会装逼,你可以学嘛,啥,你学都学不会,那你活着多无聊啊!!!玩笑话,进入正题。

相比接触过开机,闹钟,服务,广播的一些人(毕竟有些APP基本上不需要这些来实现功能),已经知道android的一些知识

1.android3.0以后,Google对系统做了修改,尽可能的防止了流氓软件的存在,为什么呢?自己看下吧,点击进去,了解下就可以了。然而这只是Google的办法,再说了,咱们的APP开发出来,肯定是运行一遍了,那么他的逻辑就不存在了啊,那为什么还是存在问题呢,这就要归功于各大万恶的开发商了(华为的还好吧,至少有个开机自启动,而我只是为三星的平板做的一款APP,三星怎么还没有滚出中国啊,禁韩令完蛋了吗(三星粉丝请无视)),原因是各大厂商已经吧开机广播屏蔽掉了,最坑爹的是,今天像接受其他的广播,然而,锁屏广播和接触锁屏广播全部需要动态加载,监听网络变化的广播竟然也给我屏蔽,心中千万只草泥马奔腾而过发火

2.服务的问题,相信你们思考的时候已经想过了,有什么服务可以保证一直在后台运行,并且不会被系统杀死。或许你们会说,我可以响铃的时候接收到的广播,在广播里边重启服务啊,还有的人会说,简单,我直接进行双进程守护就可以啊,但是你了解过吗,在android5.0的时候Google又来了一次对流氓软件的惩戒,直接在系统杀死APP主进程之后,子进程直接被杀死。这又是咋回事呢?(没找到原文地址,这个里边排版有问题,凑合着看吧,了解下,明白咋回事就可以了)。

3.至于其他的一些问题了,都是小问题,就是上边两部分,只要解决了,就没有问题了,可是本菜能力一般,水平有限,只能解决第二个问题,第一个问题一直不知道咋解决,有知道的大神,指点一下,有好的建议的,大家探讨下。


*******************************************我是分割线**********************************************

言尽于此,开胃菜已经上完了,主菜马上上,来来来,帅哥美女让个道,上主菜了!!!

1.针对5.0的杀进程论,Google也没有直接一棒子打死,5.0新出的JobScheduler已经完美解决了问题,具体怎么使用,他的机制是什么,大家百度看下吧,或者亦或者,至于怎么保活的呢,看下这段代码

JobScheduler jobScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
                    JobInfo jobInfo = new JobInfo.Builder(1, new ComponentName(getPackageName(), MyJobService.class.getName()))
                            .setPeriodic(2000)
                            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                            .build();
其中主要的红色部分的代码,以毫秒计算,重启时间是多少秒(反正我是这么理解的)。运行流程,从onCreate()到onDestory()全部走一遍,当触发事件的时候,走的方法是OnStartJob()。你只需要知道这些就可以使用了,记得要使用异步哦!

private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //闹钟开启
            int index = 0;
            calendar = Calendar.getInstance();
            calendar.setTimeInMillis(System.currentTimeMillis());
            index = new ShareUtils(getApplicationContext()).getClockIndex();
            pendingIntent = PendingIntent.getBroadcast(MyJobService.this, index +=1, intent1, PendingIntent.FLAG_UPDATE_CURRENT);
            //获取系统时间
            Date date = new Date(System.currentTimeMillis());
            long time1 = date.getTime();
            if (time1 <= time) {
                int time2 = (int) (time - time1);
                calendar.add(Calendar.SECOND, (time2 / 1000));
                manager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
                BaseApplication.time = 0;
            }
            new ShareUtils(getApplicationContext()).setClockIndex(index);
        }
    };
因为设置的多个闹钟,所以需要使用SharedPreferences来进行保存,什么你说可以用静态变量,好吧,你开心就好。

接下来,该处理什么了,走神了,唉,老了。

接下来就是接收推送的消息了

 ToastUtil.showShort(context, "有新的日程安排");
                long date = jsonObject.getLong("remindTime");
                BaseApplication.time = date;
                pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
                wakeLock = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP
                        | PowerManager.SCREEN_DIM_WAKE_LOCK, "WakeAndLock");
                wakeLock.acquire();
                int index = new ShareUtils(context).getClockIndex();
                bean = new ClockTimeBean(index += 1, date + "");
                LogUtil.i("abc123", date + "");
                ClockDAO clockDAO = new ClockDAOImpl(context);
                clockDAO.save(bean);
                android.util.Log.i("cxq", "screenOn");
什么,你说wakelock是干什么的,好吧,这是用来点亮屏幕的,在锁屏的时候,service处于停止状态,需要唤醒屏幕,让jobservice再次跑起来,奔跑吧,jobservice。不然,闹钟是设置不成功的。至于用法,不会还要我帮你百度吧,行吧, 小弟认怂了 。你还有啥不懂的?你别跟我说clockDao,这是数据库,封装好的,没办法跟你解释了,自己看我demo里边的代码吧。

你不会还要我帮你写BroadCastReceiver吧,给你,给你,都给你大哭

public class ScheduleReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        // 默认通知 API16及之后可用
        PendingIntent pendingIntent3 = PendingIntent.getService(context, 0, new Intent(context, MyJobService.class), 0);
        // 通过Notification.Builder来创建通知,注意API Level (API16之后才支持)
        Notification notify3 = new Notification.Builder(context)
                .setSmallIcon(R.mipmap.icon_bigdata)
                .setTicker("提醒: " + "您有新短消息,请注意查收!")
                .setContentTitle("日程安排")
                .setContentText("您有新的日程安排")
                .setContentIntent(pendingIntent3).setNumber(1)
                // 需要注意build()是在API level16及之后增加的,API11可以使用getNotificatin()来替代
                .build();
        // FLAG_AUTO_CANCEL表明当通知被用户点击时,通知将被清除。
        notify3.flags |= Notification.FLAG_AUTO_CANCEL;
        // 步骤4:通过通知管理器来发起通知。如果id不同,则每click,在status哪里增加一个提示
        manager.notify(1, notify3);

        //启动Service播放音乐
        context.startService(new Intent(context, MusicService.class));
        context.startService(new Intent(context, ScreenService.class));

        //再次开启ScheduleService这个服务,从而可以
//        Intent i = new Intent(context, MyJobService.class);
//        context.startService(i);
    }

音乐播放service

public class MusicService extends Service {

    //为日志工具设置标签
    private static String TAG = "MusicService";
    //定义音乐播放器变量
//    private MediaPlayer mPlayer;
//    //    private Vibrator vibrator;
//    private AlertDialog.Builder builder;
//    private AlertDialog dialog;
//    private boolean isPlaying = false;

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void onStart(Intent intent, int startId) {
//        mPlayer.start();
        super.onStart(intent, startId);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //创建vibrator对象
        Log.e(TAG, "MusicSerice onCreate()");
//        vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
        // 第一个参数为等待指定时间后开始震动,震动时间为第二个参数。后边的参数依次为等待震动和震动的时间
//        long[] pattern = {100, 400, 100, 400};
//        //-1为不重复,0为一直震动
////        vibrator.vibrate(pattern, 0);
//        mPlayer = new MediaPlayer();
//        try {
//            //默认的闹钟铃声
//            Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
//            mPlayer.setDataSource(this, uri);
//            final AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
//            if (audioManager.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION) != 0) {
//                //收到通知时的声音
//                mPlayer.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
//                mPlayer.setLooping(true);
//                mPlayer.prepare();
//                mPlayer.start();
//            }
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//        //创建提示窗口
//        builder = new AlertDialog.Builder(this);
//        builder.setTitle("提示");
//        builder.setMessage("您有新的日程安排该完成了");
//        builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
//            @Override
//            public void onClick(DialogInterface dialog, int which) {
//                isPlaying = true;
////                vibrator.cancel();
//                mPlayer.stop();
//                dialog.dismiss();
//            }
//        });
//        builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
//            @Override
//            public void onClick(DialogInterface dialog, int which) {
//                isPlaying = true;
////                vibrator.cancel();
//                mPlayer.stop();
//                dialog.dismiss();
//            }
//        });
//        dialog = builder.create();
//        //在dialog  show方法之前添加如下代码,表示该dialog是一个系统的dialog**
//        dialog.getWindow().setType((WindowManager.LayoutParams.TYPE_SYSTEM_ALERT));
//        dialog.show();
//        Message msg = new Message();
//        msg.what = 1;
//        handler.sendMessage(msg);

        Intent intent1 = new Intent(MusicService.this, MusicActivity.class);
        intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent1);

        return super.onStartCommand(intent, flags, startId);
    }
//
//    private Handler handler = new Handler() {
//        @Override
//        public void handleMessage(Message msg) {
//            super.handleMessage(msg);
//            if (!isPlaying) {
//                new Thread(new Runnable() {
//                    @Override
//                    public void run() {
//                        try {
//                            Thread.sleep(120000);
////                            vibrator.cancel();
//                            mPlayer.stop();
//                            isPlaying = false;
//                        } catch (InterruptedException e) {
//                            e.printStackTrace();
//                        }
//                    }
//                }).start();
//            }
//        }
//    };

    @Override
    public void onDestroy() {
        Log.e(TAG, "MusicSerice onDestroy()");
//        isPlaying = true;
////        vibrator.cancel();
//        mPlayer.stop();
//        dialog.dismiss();
        super.onDestroy();
    }

    //其他对象通过bindService 方法通知该Service时该方法被调用
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "MusicSerice onBind()");
//        mPlayer.start();
        return null;
    }

    //其它对象通过unbindService方法通知该Service时该方法被调用
    @Override
    public boolean onUnbind(Intent intent) {
        Log.e(TAG, "MusicSerice onUnbind()");
//        mPlayer.stop();
        return super.onUnbind(intent);
    }
唤醒屏幕service

public class ScreenService extends Service {

    //声明键盘管理器
    KeyguardManager mKeyguardManager = null;
    //声明键盘锁
    private KeyguardManager.KeyguardLock mKeyguardLock = null;
    //声明电源管理器
    private PowerManager pm;
    private PowerManager.WakeLock wakeLock;

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    @Override
    public void onCreate() {
        //获取电源的服务
        pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        //获取系统服务
        mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
        super.onCreate();
    }

    @Override
    public void onStart(Intent intent, int startId) {
        //点亮亮屏
        wakeLock = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
        wakeLock.acquire();
        Log.i("Log : ", "------>mKeyguardLock");
        //初始化键盘锁,可以锁定或解开键盘锁
        mKeyguardLock = mKeyguardManager.newKeyguardLock("");
        //禁用显示键盘锁定
        mKeyguardLock.disableKeyguard();
    }

    @Override
    public void onDestroy() {
        wakeLock.release();
        super.onDestroy();
    }
不知道为什么,我在手机上测试,音乐播放service是可以使用的,dialog显示也没有问题,我明明已经设置了dialog为系统级的dialog,而权限我也添加了,
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> 
但是为什么三星平板还会报错,直接崩了,错误信息是内存泄漏,不明白为什么,也没有找到有原因。只能写一个透明的activity,来实现功能了。你们可以试下,毕竟这样的效果比较好的,代码也没有那么多。
public class MusicActivity extends Activity {

    //定义音乐播放器变量
    private MediaPlayer mPlayer;
    private Vibrator vibrator;
    private AlertDialog.Builder builder;
    private AlertDialog dialog;
    private boolean isPlaying = false;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initView();
    }

    private void initView(){
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
        // 第一个参数为等待指定时间后开始震动,震动时间为第二个参数。后边的参数依次为等待震动和震动的时间
        long[] pattern = {100, 400, 100, 400};
        //-1为不重复,0为一直震动
        vibrator.vibrate(pattern, 0);
        mPlayer = new MediaPlayer();
        try {
            //默认的闹钟铃声
            Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
            mPlayer.setDataSource(this, uri);
            final AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
            if (audioManager.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION) != 0) {
                //收到通知时的声音
                mPlayer.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
                mPlayer.setLooping(true);
                mPlayer.prepare();
                mPlayer.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        //创建提示窗口
        builder = new AlertDialog.Builder(this);
        builder.setCancelable(false);
        builder.setTitle("提示");
        builder.setMessage("您有新的日程安排该完成了");
        builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                setClose();
            }
        });
        builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                setClose();
            }
        });
        dialog = builder.create();
        dialog.show();
        Message msg = new Message();
        msg.what = 1;
        handler.sendMessage(msg);
    }

    @Override
    protected void onStart() {
        super.onStart();
        mPlayer.start();
    }

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (!isPlaying) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(120000);
                            vibrator.cancel();
                            mPlayer.stop();
                            isPlaying = false;
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }
    };

    @Override
    public void onDestroy() {
        setClose();
        super.onDestroy();
    }

    private void setClose(){
        isPlaying = true;
        vibrator.cancel();
        mPlayer.stop();
        finish();
        dialog.dismiss();
    }
至于其他的代码呢,我就不贴出来了,下边我会写项目链接,自己下载看下吧。

基本上一半功能实现了,剩下的就是,更新APP之后该怎么实现闹钟设置了。代码很简单,逻辑也很简单,利用数据库,吧数据存到数据库中,第一次进入jobService的时候,取出数据库的数据,设置闹钟,就是这么简单。

private Handler handler1 = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            intent1 = new Intent(MyJobService.this, ScheduleReceiver.class);
            manager = (AlarmManager) getSystemService(ALARM_SERVICE);
            holder = new ArrayList<>();
            calendar = Calendar.getInstance();
            ClockDAO clockDAO = new ClockDAOImpl(getApplicationContext());
            holder = clockDAO.findAll();
            if (BaseApplication.isClock) {
                for (int i = 0; i < holder.size(); i++) {
                    if (i == holder.size() - 1) {
                        BaseApplication.isClock = false;
                    }
                    time = Long.parseLong(holder.get(i).getDate());
                    if (time != 0) {
                        pendingIntent = PendingIntent.getBroadcast(MyJobService.this, holder.get(i).getId(), intent1, PendingIntent.FLAG_UPDATE_CURRENT);
                        //获取系统时间
                        Date date = new Date(System.currentTimeMillis());
                        long time1 = date.getTime();
                        calendar.setTimeInMillis(System.currentTimeMillis());
                        if (time1 <= time) {
                            LogUtil.i("datas", calendar.getTimeInMillis() + "  ============系统时间");
                            int time2 = (int) (time - time1);
                            calendar.add(Calendar.SECOND, (time2 / 1000));
                            manager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
                            LogUtil.i("datas", calendar.getTimeInMillis() + "  ============变更时间");
                            BaseApplication.time = 0;
                            LogUtil.i("datas", holder.get(i).getId() + "    " + holder.get(i).getDate());
                        }
                    }
                }
            }
        }
    };




********************************************我又出来了*****************************************


好了,系统闹钟就这样了,如果手机自带开机自启动,直接设置同意你的APP,然后,系统闹钟的日程安排就完成了,如果不带开机自启动的功能,那么你可以监听联网状态广播,如果还是不行,那没办法了,花钱吧,找厂商把你的APP加入白名单,为什么QQ和微信可以不被系统杀死,因为人家花钱了,人民币APP啊。反正我是没有想到其他好的办法,如果你有好的办法,请告诉我,如果你有什么好的观点,咱们可以探讨下。 谢谢!!




***************************************************************************************************************************************

本菜在进行最后的测试,测试完成,马上上传项目,开机重新启动闹钟依然能够运行的功能已经完成。





猜你喜欢

转载自blog.csdn.net/sinat_29874521/article/details/70859830