WHAT
- 不需要交互但是需要长期运行的后台任务。
- 服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。
- 服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的
应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。 - 不要被服务的后台概念所迷惑,实际上服务并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况。
注意事项
- Service不是更好的AsyncTask
Service不是用来执行异步、后台操作的,它被创造的目的就是在Activity不可见时仍能执行逻辑,要把Service想成不可见的Activity。
要记住每个Service都是有使用成本的特殊组件,不只是对于APP,更是对整个系统。
-
默认情况下Service在主线程运行
可以选择让Service在其他线程运行,但除非必要情况时应尽量避免这么做,因为这么做是有代价的。 -
IntentService并不神奇
IntentService是通过创建一个HandlerThread并在其中排列逻辑来工作的,可以很轻松的跳出Service实现这种方式。
IntentService是一个很简单的类,只有164行代码,去掉注释74行,可以自己去看看。
-
一个Service同时只能有一个实例
不管怎么new,一个Service永远只能有一个实例,即使外部APP/进程与它交互也是这样。 -
Service很容易被干掉
不要以为内存少是很极端的情况,应该在编写Service代码时让它可以很优雅的处理系统对它的重启,这是生命周期的一部分。
可以把Service标记为foreground让它更难被干掉,但应该只在必要时这么做。
注意当执行onCreate()、onStartCommand()、onDestroy()方法时,Service被视作foreground虽然它并不是。
下面这个链接可以告诉你你的进程有多么可能被干掉。
安卓开发者平台
-
一个Service可以在同时被启动与绑定
明确的停止绑定了其他组件的Service并不会使它结束。解绑Service的所有组件而不结束它也不会使它停止。还有就是不论你调用startService()多少次,只需调用一次stopService()或stopSelf()就可以结束它。具体参考下图:
Service LifeCycle -
STARTFLAGREDELIVERY可以避免输入数据丢失
如果在启动Service时传入数据并在onStartCommand()中返回STARTFLAGREDELIVERY可以有效地避免在Service处理数据被杀死时丢失数据。 -
前台的通知可以被部分遮盖
当一个前台的Service要发送一个通知时,可以传入一个优先级值PRIORITY_MIN来在状态栏中隐藏它,而在通知栏中仍然可见。 -
Service可以启动Activity
就像不是Activity的所有Context一样,Service可以在添加FLAGACTIVITYNEW_TASK标记时启动Activity。
还可以显示Toast或状态栏通知,一样的道理。
- 可以引入单一职责原则
最好不要在Service中写逻辑代码,而是分离在另一个类中,这样除了可以复用外,说不定还有其他好处。
场景
服务在后台监听蓝牙打印机的状态,当打印机异常(缺纸)时,弹出提示
弹出 Toast
使用 hanlder 传递 Toast
hanlder.post(new Runnable() {
@Override
public void run() {
Toast.makeText(DialogService.this, "打印机缺纸", 1).show();
}
});
弹出 Dialog
Dialog的显示是需要依附于一个Activity,如果需要在 Servcie 中显示需要把 Dialog 设置成一个系统级 Dialog(TYPE_SYSTEM_ALERT),即全局性质的提示框.
在安卓8及以上版本,需要 使用 TYPE_APPLICATION_OVERLAY 权限才能在 后台 或者 其他窗口 弹出。
private Handler mHandler;
//在 Service 生命周期方法 onCreate() 中初始化 mHandler
mHandler = new Handler(Looper.getMainLooper());
//在子线程中想要 Toast 的地方添加如下
mHandler.post(new Runnable() {
@Override
public void run() {
//show dialog
justShowDialog();
}
});
private void justShowDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(getApplicationContext())
.setIcon(android.R.drawable.ic_dialog_info)
.setTitle("service中弹出Dialog了")
.setMessage("是否关闭dialog?")
.setPositiveButton("确定",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
}
})
.setNegativeButton("取消",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
}
});
//下面这行代码放到子线程中会 Can't create handler inside thread that has not called Looper.prepare()
AlertDialog dialog = builder.create();
//设置点击其他地方不可取消此 Dialog
dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(false);
//8.0系统加强后台管理,禁止在其他应用和窗口弹提醒弹窗,如果要弹,必须使用TYPE_APPLICATION_OVERLAY,否则弹不出
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
dialog.getWindow().setType((WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY));
}else {
dialog.getWindow().setType((WindowManager.LayoutParams.TYPE_SYSTEM_ALERT));
}
dialog.show();
}
参考
https://www.jb51.net/article/172238.htm
错误 Can't create handler inside thread that has not called Looper.prepare()
new Thread(new Runnable() {
@Override
public void run() {
Looper.perpare();//增加部分
timer = new Timer(mTotalTime, TimeSetted.SECOND_TO_MILL);
timer.start();
Log.d(TAG,"Countdown start");
Looper.loop();//增加部分
}
}).start();
//版权声明:本文为CSDN博主「雷霆管理层」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
//原文链接:https://blog.csdn.net/weixin_42694582/article/details/81535083