Android小应用——监控屏幕使用时间
idea来源
这个idea是蔡小亦童鞋提出来的。她说看到一条报道说有人看手机看太久眼睛怎么怎么了,所以想弄个应用来监控屏幕使用时间。答应帮她做已经答应很久了,刚好这周没什么事了,于是就开始做。从开始找资料到写代码到美工到调试完成,只花了1天时间,不错不错~因为我觉得这个做得很粗糙别人不可能会怎么用,所以我就针对蔡小亦童鞋定制了流氓兔形象,哦哈哈是不是该感谢我~
预期目标
1、能记录屏幕使用时间
2、每天凌晨清空数据,重新记录
3、用户可以自定义警戒线,当使用时间超过警戒线则在通知栏提醒。
主要代码
1 package com.legend;
2
3 import java.text.SimpleDateFormat;
4 import java.util.Date;
5
6 import android.app.Activity;
7 import android.content.Context;
8 import android.content.Intent;
9 import android.content.SharedPreferences;
10 import android.os.Bundle;
11 import android.view.View;
12 import android.view.View.OnClickListener;
13 import android.widget.Button;
14 import android.widget.EditText;
15 import android.widget.TextView;
16 import android.widget.Toast;
17
18 /**
19 * 目前先实现最小功能,只提取出总的屏幕亮的时间
20 * 通过广播来接收屏幕是否启动这个事件
21 * @author 林培东
22 */
23 public class MainActivity extends Activity
24 {
25 public TextView summary=null;
26 public TextView preset=null;
27 public EditText set=null;
28 public Button submit=null;
29
30 @Override
31 public void onCreate(Bundle savedInstanceState)
32 {
33 super.onCreate(savedInstanceState);
34 setContentView(R.layout.main);
35 startService(new Intent("com.legend.SERVICE_DEMO"));//启动服务
36
37 summary=(TextView)findViewById(R.id.summary);
38 preset=(TextView)findViewById(R.id.preset);
39 set=(EditText)findViewById(R.id.set);
40 submit=(Button)findViewById(R.id.submit);
41
42 //显示已使用屏幕时间
43 SharedPreferences sp=getSharedPreferences("actm", Context.MODE_PRIVATE);
44 int sum=(int)sp.getLong("sum", 0L)/1000;
45 int hour=sum/3600;
46 int minute=(sum-hour*3600)/60;
47 int second=sum%60;
48 //格式化输出日期
49 Date tmp=new Date();
50 tmp.setHours(hour);
51 tmp.setMinutes(minute);
52 tmp.setSeconds(second);
53 SimpleDateFormat sdf=new SimpleDateFormat("HH:mm:ss");
54 String result=sdf.format(tmp);
55 summary.setText(result);//最终显示
56
57 //显示已保存的设置
58 int limit=sp.getInt("limit", 24*60);
59 preset.setText(" 当前设定的预警分钟数为"+Integer.toString(limit));
60
61 //点击确定后重新设置
62 submit.setOnClickListener(new OnClickListener()
63 {
64 @Override
65 public void onClick(View v)
66 {
67 String tmp=set.getText().toString();
68 if(tmp.equals(""))
69 Toast.makeText(MainActivity.this, "输入不能为空!", Toast.LENGTH_SHORT).show();
70 else
71 {
72 SharedPreferences sp=getSharedPreferences("actm", Context.MODE_PRIVATE);
73 SharedPreferences.Editor editor=sp.edit();
74 editor.putInt("limit", Integer.parseInt(tmp));
75 editor.commit();
76 Toast.makeText(MainActivity.this, "已设定!", Toast.LENGTH_SHORT).show();
77 preset.setText(" 当前设定的预警分钟数为"+Integer.parseInt(tmp));
78 }
79 }
80 });
81
82 }
83
84 }
1 package com.legend;
2
3 import java.util.Date;
4 import java.util.Timer;
5 import java.util.TimerTask;
6
7 import android.app.Notification;
8 import android.app.NotificationManager;
9 import android.app.PendingIntent;
10 import android.app.Service;
11 import android.content.BroadcastReceiver;
12 import android.content.Context;
13 import android.content.Intent;
14 import android.content.IntentFilter;
15 import android.content.SharedPreferences;
16 import android.os.IBinder;
17
18 /**
19 * 创建一个服务,该服务主要用来接收广播和创建定时器
20 * @author 林培东
21 */
22 public class LocalService extends Service
23 {
24 private static final int NOTIFY_ID=1234;//通知的唯一标识符
25
26 //主要功能,广播接收器
27 private final BroadcastReceiver receiver=new BroadcastReceiver()
28 {
29 @Override
30 public void onReceive(Context context, Intent intent)
31 {
32 SharedPreferences sp=getSharedPreferences("actm", Context.MODE_PRIVATE);
33 SharedPreferences.Editor editor=sp.edit();
34
35 if(intent.getAction().equals(Intent.ACTION_SCREEN_ON))
36 {
37 //保存屏幕启动时的毫秒数
38 editor.putLong("lasttime", new Date().getTime());
39 editor.commit();
40
41 //根据需要看是否需要在通知栏提醒
42 int sum=(int)sp.getLong("sum", 0L)/1000;
43 int limit=sp.getInt("limit", 1440)*60;
44 if(limit<=sum)
45 {
46 final NotificationManager manager=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);//获取通知管理器
47 Notification notification=new Notification(R.drawable.ic_launcher,"警告",System.currentTimeMillis());//通知的时机
48 notification.flags = Notification.FLAG_AUTO_CANCEL;//点击一次通知就自动消失
49 PendingIntent pIntent=PendingIntent.getActivity(context,0,new Intent(context,MainActivity.class),0);//跳转到主界面
50 notification.setLatestEventInfo(context,"警告","本日使用屏幕已超过预设,如需取消该警告请重新设置!!!",pIntent);//通知栏显示内容
51 manager.notify(NOTIFY_ID, notification);//执行
52 }
53 }
54 else if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF))
55 {
56 //保存屏幕总工作时间
57 long lasttime=sp.getLong("lasttime", new Date().getTime());
58 long sum=sp.getLong("sum", 0L);
59 sum+=new Date().getTime()-lasttime;
60 editor.putLong("sum", sum);
61 editor.commit();
62 }
63 }
64
65 };
66
67 @Override
68 public void onCreate()
69 {
70 //添加过滤器并注册
71 final IntentFilter filter=new IntentFilter();
72 filter.addAction(Intent.ACTION_SCREEN_ON);
73 filter.addAction(Intent.ACTION_SCREEN_OFF);
74 registerReceiver(receiver, filter);
75
76 //创建计划任务
77 TimerTask task=new TimerTask()
78 {
79 @Override
80 public void run()
81 {
82 //每天凌晨自动更新数据
83 SharedPreferences sp=getSharedPreferences("actm", Context.MODE_PRIVATE);
84 SharedPreferences.Editor editor=sp.edit();
85 editor.putLong("sum", 0L);
86 editor.commit();
87
88 //取消通知栏通知
89 final NotificationManager manager=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
90 manager.cancel(NOTIFY_ID);
91 }
92 };
93 Timer timer=new Timer(true);
94 int hour=new Date().getHours();
95 timer.schedule(task,(24-hour)*3600*1000, 24*3600*1000);
96 //timer.schedule(task,180*1000, 180*1000);//测试用
97
98 super.onCreate();
99 }
100
101 @Override
102 public IBinder onBind(Intent arg0)
103 {
104 return null;
105 }
106
107 }
1 <?xml version="1.0" encoding="utf-8"?>
2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="fill_parent"
4 android:layout_height="fill_parent"
5 android:background="@drawable/background"
6 android:orientation="vertical" >
7
8 <TextView
9 android:layout_width="fill_parent"
10 android:layout_height="wrap_content"
11 android:textSize="25dp"
12 android:text="今天屏幕总共使用"
13 android:textColor="#000000"
14 android:gravity="center" />
15
16 <TextView
17 android:id="@+id/summary"
18 android:layout_width="fill_parent"
19 android:layout_height="wrap_content"
20 android:textSize="50dp"
21 android:textColor="#000000"
22 android:gravity="center" />
23
24 <TextView
25 android:id="@+id/preset"
26 android:layout_width="fill_parent"
27 android:layout_height="wrap_content"
28 android:textColor="#000000"/>
29
30 <EditText
31 android:id="@+id/set"
32 android:layout_width="fill_parent"
33 android:layout_height="wrap_content"
34 android:hint="请输入预警提醒分钟数,如80"
35 android:inputType="number" />
36
37 <Button
38 android:id="@+id/submit"
39 android:layout_width="fill_parent"
40 android:layout_height="wrap_content"
41 android:text="确定提交" />
42
43 </LinearLayout>
1 <?xml version="1.0" encoding="utf-8"?>
2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3 package="com.legend"
4 android:versionCode="1"
5 android:versionName="1.0" >
6
7 <uses-sdk android:minSdkVersion="4" />
8
9 <application
10 android:icon="@drawable/ic_launcher"
11 android:label="@string/app_name" >
12 <activity
13 android:name=".MainActivity"
14 android:label="@string/app_name" >
15 <intent-filter>
16 <action android:name="android.intent.action.MAIN" />
17
18 <category android:name="android.intent.category.LAUNCHER" />
19 </intent-filter>
20 </activity>
21
22 <service android:name=".LocalService">
23 <intent-filter>
24 <action android:name="com.legend.SERVICE_DEMO" />
25 <category android:name="android.intent.category.default" />
26 </intent-filter>
27 </service>
28 </application>
29
30 </manifest>
项目分析
我遇到的第一个问题是:如何监控?
经过查资料,我发现当屏幕启用或者锁屏时,系统会分别发送ACTION_SCREEN_ON和ACTION_SCREEN_OFF这两个广播。我们只需要在接收这两个广播时记录时间就可以了。
注意:为了时程序退出后也能运行,必须使用Service。
注意:这两个广播是受保护的,只能在代码中注册。
下面是在Service中注册:
//添加过滤器并注册
final IntentFilter filter=new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(receiver, filter);
在接收器receiver里,定义了onReceive()来处理这些数据,主要功能都在里面实现:
//主要功能,广播接收器
private final BroadcastReceiver receiver=new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
SharedPreferences sp=getSharedPreferences("actm", Context.MODE_PRIVATE);
SharedPreferences.Editor editor=sp.edit();
if(intent.getAction().equals(Intent.ACTION_SCREEN_ON))
{
//保存屏幕启动时的毫秒数
editor.putLong("lasttime", new Date().getTime());
editor.commit();
//根据需要看是否需要在通知栏提醒
int sum=(int)sp.getLong("sum", 0L)/1000;
int limit=sp.getInt("limit", 1440)*60;
if(limit<=sum)
{
final NotificationManager manager=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);//获取通知管理器
Notification notification=new Notification(R.drawable.ic_launcher,"警告",System.currentTimeMillis());//通知的时机
notification.flags = Notification.FLAG_AUTO_CANCEL;//点击一次通知就自动消失
PendingIntent pIntent=PendingIntent.getActivity(context,0,new Intent(context,MainActivity.class),0);//跳转到主界面
notification.setLatestEventInfo(context,"警告","本日使用屏幕已超过预设,如需取消该警告请重新设置!!!",pIntent);//通知栏显示内容
manager.notify(NOTIFY_ID, notification);//执行
}
}
else if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF))
{
//保存屏幕总工作时间
long lasttime=sp.getLong("lasttime", new Date().getTime());
long sum=sp.getLong("sum", 0L);
sum+=new Date().getTime()-lasttime;
editor.putLong("sum", sum);
editor.commit();
}
}
};
另一个问题是如何在每天凌晨自动把sum置零。一开始我查资料找到了ACTION_DATE_CHANGED这个广播,但测试时发现不可靠,网上也说了这个广播各种不可靠。
于是,只能忍痛使用定时器来制定计划任务了:Timer和TimerTask。
//创建计划任务
TimerTask task=new TimerTask()
{
@Override
public void run()
{
//每天凌晨自动更新数据
SharedPreferences sp=getSharedPreferences("actm", Context.MODE_PRIVATE);
SharedPreferences.Editor editor=sp.edit();
editor.putLong("sum", 0L);
editor.commit();
//取消通知栏通知
final NotificationManager manager=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
manager.cancel(NOTIFY_ID);
}
};
Timer timer=new Timer(true);
int hour=new Date().getHours();
timer.schedule(task,(24-hour)*3600*1000, 24*3600*1000);
//timer.schedule(task,180*1000, 180*1000);//测试用
再有就是学习了如何使用通知栏来推送消息。
final NotificationManager manager=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);//获取通知管理器
Notification notification=new Notification(R.drawable.ic_launcher,"警告",System.currentTimeMillis());//通知的时机
notification.flags = Notification.FLAG_AUTO_CANCEL;//点击一次通知就自动消失
PendingIntent pIntent=PendingIntent.getActivity(context,0,new Intent(context,MainActivity.class),0);//跳转到主界面
notification.setLatestEventInfo(context,"警告","本日使用屏幕已超过预设,如需取消该警告请重新设置!!!",pIntent);//通知栏显示内容
manager.notify(NOTIFY_ID, notification);//执行
最后,上一下截图:
--------------------------------------后记-------------------------------------------
本来以为很简单的,没想到修改了两次才能正常运作:不知道为什么系统老是把资源回收了,结果凌晨都无法自动清空数据。
第一次修改,是取消守护线程了。之前对这个不了解,查了下资料,原来所谓的守护线程,就是当线程要守护的资源不存在时,这个线程也就退出了。所以我想这就是原因了吧,修改,还信心满满地以为不用测试了。
结果零点就不行了,严重被打击==!
无奈之下,只好用最原始的方法了:监听Intent.ACTION_TIME_TICK这个广播,因为它一分钟就发送一次,是个可靠的广播,只要判断下时间点,就可以决定是否更新了。
其实这个方法我很早就想到了,只是我觉得这样每分钟都要做一次判断,太麻烦和太耗资源了。这算是程序员的通病吧。
所以,通过这个小软件,我也有了一点体会:功能第一,性能第二。因为用户最后用的是你的软件的功能,而性能是很难看出来的;只要影响不大的话。
所以,真的不应该在这个问题上钻牛角尖,一定要最优化。
最后,修改后的代码:
1 package com.legend;
2
3 import java.util.Calendar;
4 import java.util.Date;
5
6 import android.app.Notification;
7 import android.app.NotificationManager;
8 import android.app.PendingIntent;
9 import android.app.Service;
10 import android.content.BroadcastReceiver;
11 import android.content.Context;
12 import android.content.Intent;
13 import android.content.IntentFilter;
14 import android.content.SharedPreferences;
15 import android.os.IBinder;
16
17 /**
18 * 创建一个服务,该服务主要用来接收广播和创建定时器
19 * @author 林培东
20 */
21 public class LocalService extends Service
22 {
23 private static final int NOTIFY_ID=1234;//通知的唯一标识符
24 private Calendar cal=null;
25
26 //主要功能,广播接收器
27 private final BroadcastReceiver receiver=new BroadcastReceiver()
28 {
29 @Override
30 public void onReceive(Context context, Intent intent)
31 {
32 SharedPreferences sp=getSharedPreferences("actm", Context.MODE_PRIVATE);
33 SharedPreferences.Editor editor=sp.edit();
34
35 if(intent.getAction().equals(Intent.ACTION_SCREEN_ON))
36 {
37 //保存屏幕启动时的毫秒数
38 editor.putLong("lasttime", new Date().getTime());
39 editor.commit();
40
41 //根据需要看是否需要在通知栏提醒
42 int sum=(int)sp.getLong("sum", 0L)/1000;
43 int limit=sp.getInt("limit", 1440)*60;
44 if(limit<=sum)
45 {
46 final NotificationManager manager=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);//获取通知管理器
47 Notification notification=new Notification(R.drawable.ic_launcher,"警告",System.currentTimeMillis());//通知的时机
48 notification.flags = Notification.FLAG_AUTO_CANCEL;//点击一次通知就自动消失
49 PendingIntent pIntent=PendingIntent.getActivity(context,0,new Intent(context,MainActivity.class),0);//跳转到主界面
50 notification.setLatestEventInfo(context,"警告","本日使用屏幕已超过预设,如需取消该警告请重新设置!!!",pIntent);//通知栏显示内容
51 manager.notify(NOTIFY_ID, notification);//执行
52 }
53 }
54 else if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF))
55 {
56 //保存屏幕总工作时间
57 long lasttime=sp.getLong("lasttime", new Date().getTime());
58 long sum=sp.getLong("sum", 0L);
59 sum+=new Date().getTime()-lasttime;
60 editor.putLong("sum", sum);
61 editor.commit();
62 }
63 else if(intent.getAction().equals(Intent.ACTION_TIME_TICK))
64 {
65 cal=Calendar.getInstance();
66 if(cal.get(Calendar.HOUR_OF_DAY)==0 && cal.get(Calendar.MINUTE)==0)
67 {
68 //每天凌晨自动更新数据
69 editor.putLong("sum", 0L);
70 editor.commit();
71
72 //取消通知栏通知
73 final NotificationManager manager=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
74 manager.cancel(NOTIFY_ID);
75 }
76 }
77
78 }
79
80 };
81
82 @Override
83 public void onCreate()
84 {
85 //添加过滤器并注册
86 final IntentFilter filter=new IntentFilter();
87 filter.addAction(Intent.ACTION_SCREEN_ON);
88 filter.addAction(Intent.ACTION_SCREEN_OFF);
89 filter.addAction(Intent.ACTION_TIME_TICK);
90 registerReceiver(receiver, filter);
91
92 super.onCreate();
93 }
94
95 @Override
96 public IBinder onBind(Intent arg0)
97 {
98 return null;
99 }
100
101 }