可能有人会疑惑,这样的需求有什么用?接下来,我就先讲讲有什么用!对于那些经常玩准时抢购,抢优惠券的人来说,时间的精确度是非常重要的,手慢1秒就没了。而安卓手机中,却没法准确的看到秒数,并且是悬浮在其他应用之上的时钟就很有必要了!
首先先捋一捋实现本功能的关键所在!
得实现无界面。
悬浮在其他应用之上,可任意移动。随时关闭。
准确的北京时间,每秒、甚至毫秒级的刷新时间。
接下来就来谈谈具体如何实现?
如何实现无界面?
其实这个很简单,现在大家都明白是怎么回事。无界面,那就用Service呗!是的,这是毋庸置疑的,不过我的实现方式,不知道与你们是否有所区别!
首先,程序的入口还是个mainActivity,只是这个活动有点特殊,我们不给它设置view,只在它的oncreate中启动服务,然后finish掉activity。这里还有一点需要注意的,咋看之下,好像已经是没有界面的,其实不然,除此之外,还需要在配置文件中,把app的主题设置为
android:theme="@android:style/Theme.NoDisplay">
这样便实现了没有任何界面,只有一个悬浮窗!这里还有一个要注意的,在安卓6.0(api23)之后,对于危险的权限(是危险还是正常,都是谷歌说的算),需要动态获取权限!在本例子中,就需要用户授予悬浮的权限。代码如下
if (Build.VERSION.SDK_INT >= 23) {
if (! Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent,10);
}
}
Intent intent = new Intent(MainActivity.this, FxService.class);
//启动FxService
startService(intent);
finish();到此为止,无界面已经实现了。既然没有界面,那悬浮按钮上显示时间的view就只能依赖于Service了!接下来就看看这个Service是怎么实现的。
其实在Service中添加view也是挺简单的,只要在Service中获取到WindowManager,绑定定义好的layout,然后调用addView添加即可。
mWindowManager = (WindowManager) getApplication().getSystemService(getApplication().WINDOW_SERVICE);
wmParams = new WindowManager.LayoutParams(); //获取浮动窗口视图所在布局 mFloatLayout = (LinearLayout) inflater.inflate(R.layout.float_layout, null); //添加mFloatLayout mWindowManager.addView(mFloatLayout, wmParams); //浮动窗口按钮 tvTime = (TextView) mFloatLayout.findViewById(R.id.tv_time); closeBtn = (ImageView) mFloatLayout.findViewById(R.id.img_close);
值得注意的是,在安卓8.0(API25)起,添加悬浮窗口的类型从TYPE_PHONE改成TYPE_APPLICATION_OVERLAY,所以还需要通过下方代码进行适配。
if (Build.VERSION.SDK_INT > 25) {
wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}悬浮窗口的移动,点击关闭等也是在Service中来实现。
//设置监听浮动窗口的触摸移动
mFloatLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
//g这里实现自己的逻辑
//刷新ui
mWindowManager.updateViewLayout(mFloatLayout, wmParams);
return false; //此处必须返回false,否则OnClickListener获取不到监听
}
});点击关闭按钮,Service结束掉自身,并强制退出系统。
closeBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stopSelf();
System.exit(0);
}
});到此为止,所有的准备工作都已经完毕。接下来就是本应用的核心:获取北京时间,并实时刷新ui,显示时间。
本例中,获取网络时间使用的http请求框架是OkHttp,相信大家都不陌生。不清楚的可以查阅。
public static long getNetworkTime() {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("这里修改成能获取到北京时间的url即可").build();
try {
Response response = client.newCall(request).execute();
if (response.code() == 200) {
return Long.parseLong(response.body().string());
}
}catch (Exception e){
return 0;
}
return 0;
}由于Service中不能进行耗时操作,所以网络请求需要放在子线程中,而子线程不能更新ui,并且在本例中的Service无法调用runOnUiThread,所以我选择了Handler发送message的方式来刷新ui,然而Handler.sendEmptyMessage可能会存在一定的延时,这对于要求时间精准度非常高的应用有一定的影响。最后是通过了一个小小的技巧来弥补一下。在子线程获取到时间的时候,定义个全局变量来记录获取到北京时间的当前时间戳。然后在准备刷新ui的时候,再获取到时的时间戳,两个相减得到时间差。
new Thread(new Runnable() {
@Override
public void run() {
dl = TimeUtils.getNetworkTime();
Message msg=new Message();
start = System.currentTimeMillis();
refreshHandler.sendEmptyMessage(0);
}
}).start();在本例中,获取网络时间,只在打开应用的时候请求了一次,后面都是通过从请求到时间那刻开始,到当前时间的时间差来累计时间的。也算是这小技巧!
refreshHandler.post(new Runnable() {
@Override
public void run() {
long d= System.currentTimeMillis()-start;
String time= TimeUtils.getBJTime(dl+d);;
tvTime.setText(time);
refreshHandler.postDelayed(this, 1);
}
});效果图!左边是北京时间,时间几乎是0.1秒是看不到误差。
可以悬浮在任何应用之上!
第一次写。有误之处,还请指教!
共同学习,写下你的评论
评论加载中...
作者其他优质文章

