忽然飞来一个新的需求,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();
.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啊。反正我是没有想到其他好的办法,如果你有好的办法,请告诉我,如果你有什么好的观点,咱们可以探讨下。谢谢!!