忽然飞来一个新的需求,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()); } } } } } };
********************************************我又出来了*****************************************
***************************************************************************************************************************************
本菜在进行最后的测试,测试完成,马上上传项目,开机重新启动闹钟依然能够运行的功能已经完成。