【Android】16.2 Started Services
分类:C#、Android、VS2015;
创建日期:2016-03-01
一、简介
Started Service是指被同一个应用程序的某个对象显式启动,或者在设备引导时就已经启动了(配置了服务的情况)。
二、Started Services的生命周期
前面我们说过,Service只是一种被分离出来的组件(例如从某个Activity中分离出来),可被单独启动启动和停止。因此不论是Started Services还是Bound Services,这些Services都有它自己独立的生命周期。
下图演示了Started Services的生命周期期间调用的方法。
一旦服务被启动(started),它就拥有了自己的生命周期,这是独立于启动它的组件的。并且它能够在后台一直运行下去,即使启动它的组件已被销毁也是如此。因此,服务应该能够在完成工作后自行终止,通过调用StopSelf()即可终止服务自身,或者由其它组件通过调用StopService()也可以终止服务。
对于activity之类的应用程序组件,可以通过调用StartService()启动服务,并传入一个给出了服务和服务所需数据的Intent对象。服务将在OnStartCommand()方法中接收到该Intent对象。举个例子,假定某activity需要把一些数据保存到在线数据库中,此activity可以启动一个守护服务并通过传入StartService()的一个intent把需要保存的数据发送给该服务,该服务在OnStartCommand()内接收intent、连接Internet,再进行数据库事务处理。当事务完成后,服务自行终止,并被系统销毁。
警告:默认情况下,运行服务的进程与应用程序相同,并且运行在应用程序的主线程中。 因此,如果你的服务要执行计算密集或阻塞的操作,而同时用户又需要与同一个应用程序中的activity进行交互,那么服务将会降低activity的性能。为了避免对应用程序性能的影响,你应该在服务中启动一个新的线程。
使用服务时,最重要的、需要重写的回调方法有下面几种。
1、OnStartCommand()
任何一个对象请求开始服务时,系统都会调用这个方法。比如一个activity通过调用StartService()请求服务时,系统将会调用本方法。调用StartService()、重启系统等也会调用该方法。
一旦本方法执行,服务就被启动,并在后台一直运行下去。 如果你的代码实现了本方法,你就有责任在完成工作后通过调用StopSelf()或StopService()终止服务。
OnStartCommand要求必须返回一个StartCommandResult枚举类型的值,它告诉安卓系统如果获取的服务已停止是否应该重新启动这个服务。例如,下面的代码返回StartResultCommand.Sticky枚举值,此时执行OnStartCommand方法时将自动重新启动该服务:
public override StartCommandResult OnStartCommand (……)
{
// start a task here
……
return StartCommandResult.Sticky;
}
StartCommandResult枚举值可以是下面的选项之一:
- Sticky – 此选项表示将重新启动指定的服务,同时传递给OnStartCommand方法一个值为null的Intent类型的参数。这种服务常用于不断执行一个需要长时间运行的操作(比如股票行情)。
- RedeliverIntent – 此选项用于正常执行服务时Intent包含有关键的附加信息(extra information)的情况。如果在最后一个Intent发送前停止了服务,此时将重新启动该服务,并将这个Intent传递给OnStartCommand方法。
- NotSticky –该服务不会自动重新启动。
- StickyCompatibility – 该选项仅仅是为了与API 5之前的版本兼容而提供的,其含义与Sticky的含义相同,现在的项目中很少用到它。
在这些返回的选项中,最常用的是StartCommandResult.Sticky。当然其他选项也会在不同的场合下用到,否则提供不同的选项就没有意义了。
注:Android 1.6及更低版本使用的是OnStart()方法而不是OnStartCommand()方法。从Android 2.0开始,OnStart()已经过时,改为用OnStartCommand()取而代之。
2、OnBind()
当其它组件需要通过BindService()绑定服务时(比如执行RPC),系统会调用本方法。 在本方法的实现代码中,你必须返回IBinder来提供一个接口,客户端用它来和服务进行通信。 你必须确保实现本方法,不过假如你不需要提供绑定,那就返回null即可。
3、OnCreate()
当首次启动服务时被调用一次,一般用它实现初始化工作。
注意仅在第一次启动服务时,才会调用一次这个方法。如果服务已经运行,则不会再调用本方法。
4、OnDestroy()
当服务用不上了并要被销毁时,系统会调用本方法。你的服务应该在这个方法中进行清理服务占用的资源,比如线程、已注册的侦听器listener和接收器receiver等等。这将是服务收到的最后一个调用。
如果组件通过调用StartService()(这会导致onStartCommand()的调用)启动了服务,那么服务将一直保持运行,直至自行用stopSelf()终止或由其它组件调用StopService()来终止它。
如果组件调用BindService()来创建服务(此时OnStartCommand()就不会被调用),则服务的生存期就与被绑定的组件一致。一旦所有客户端都对服务解除了绑定,系统就会销毁该服务。
仅当内存少得可怜、且必须覆盖拥有用户焦点的activity的系统资源时,Android系统才会强行终止一个服务。 如果服务被拥有用户焦点的activity绑定着,则它一般不会被杀死。 如果服务声明为“在前台运行服务”,则它几乎永远不会被杀死。 否则,如果服务已被启动并且已运行了很长时间,那么系统将会随时间推移而降低它在后台任务列表中的级别, 此类服务将很有可能会被杀死——如果服务已经启动,那你必须好好设计代码,使其能完美地应付被系统重启的情况。如果系统杀死了你的服务,只要资源再度够用,系统就会再次启动服务(当然这还取决于OnStartCommand()的返回值)。
关于系统可能会在何时销毁服务的详细信息,请参阅进程和线程。
三、创建、启动和停止Started服务
1、创建自定义的Service
创建服务的第一步是创建继承自Service的子类(Service类是所有服务的基类)。
与自定义的Activities相对应,通过ServiceAttribute特性声明(用C#声明特性时先去掉Attribute后缀然后再用中括号将其括起来)可告诉系统这是一个自定义的服务:
[Service]
public class MyService : Service
{
...
}
用ServiceAttribute类声明Service特性后,它就会自动在AndroidManifest.xml中注册这个服务,而不需要我们去手工配置AndroidManifest.xml文件。例如,假定项目名为ServiceDemo1,用[Service]声明后,它就会自动在AndroidManifest.xml中添加下面的代码:
<service android:name="servicedemo1.ServiceDemo1"></service>
当然也可以手工在AndroidManifest.xml中直接添加配置代码,但一般不这样做(18.1已经说过一遍了),这是因为在配置文件中添加时没有智能提示,特别是对于初学者来说比较容易出错。而用继承自Service的子类实现时,在.cs文件中添加特性时有智能提示,既免去了配置的麻烦,用起来也非常简单、直观、方便。
2、启动Service
在上下文中(例如某个Activity)调用StartService()方法可以初始化Started Services。
如果该服务正在从某项活动中启动,那么可以直接在该活动中调用StartService()方法,否则,可先通过Android.App.Application.Context获取当前上下文,然后再调用该方法。
要启动一个服务,需要传递一个Intent指定要启动的服务类型以及当前上下文。
例如,下面的代码在一个活动中启动MyService类型的服务:
this.StartService (new Intent (this, typeof(MyService)));
从Started Services生命周期中我们已经知道,调用StartService()方法将导致Android调用服务中提供的OnStartCommand()方法。同时也知道了OnStartCommand()要求必须返回一个StartCommandResult枚举类型的值,它告诉安卓系统如果获取的服务已停止是否应该重新启动这个服务。
例如,下面的代码返回StartResultCommand.Sticky枚举值,此时执行OnStartCommand方法时将自动重新启动该服务:
public override StartCommandResult OnStartCommand (Intent intent, StartCommandFlags flags, int startId)
{
// start a task here
new Task (() => {
// long running code
DoWork();
}).Start();
return StartCommandResult.Sticky;
}
【注意】:在该方法中必须使用Task或者自定义的Thread来执行服务的初始化工作。这是因为服务是运行在UI线程上的,任何长时间运行的任务都会让UI渲染停顿,从而导致应用程序无响应。而使用Task或自定义的Thread来执行服务的初始化工作,不会引起界面停顿的现象。
3、停止Service
除非任务开始后打算无限期地运行下去,否则一个已启动的服务应调用StopSelf方法停止它无休止地长时间运行。这很重要,因为Started Services是一个独立运行的组件,运行期间将继续占用系统的绘制资源,直到它被显式停止或被操作系统关闭。
下面的代码演示了如何在完成任务后调用StopSelf()方法停止服务:
public void DoWork ()
{
var t = new Task (() => {
Thread.Sleep (5000); //模拟长时间执行的任务
StopSelf ();
});
t.Start();
}
或者:
public void DoWork ()
{
var t = new Thread (() => {
Log.Debug ("DemoService", "Doing work");
Thread.Sleep (5000);
Log.Debug ("DemoService", "Work complete");
StopSelf ();
});
t.Start ();
}
另外,为了避免无限期地继续服务的可能性,调用方还可以通过调用StopService方法请求停止该服务,如下所示:
StopService (new Intent (this, typeof(MyService)));
当服务停止时,Started Service会自动调用服务中的OnDestroy方法,在这个方法中应该做一些清理服务所占用的资源的工作。
在服务类中,只需要重写OnDestroy方法即可:
public override void OnDestroy ()
{
base.OnDestroy ();
// 在此处编写清理资源的代码
}
多个调用方都可以请求启动服务,如果某个外部请求启动服务,也可以将startId传递到OnStartCommand方法,以防止该服务被过早地停止。StartId对应最后一次调用的StartService方法,每次执行OnStartCommand方法都会递增该值。因此,如果对StartService后面的请求还没有导致对OnStartCommand的调用,此时服务可以调用StopSelfResult方法并传递它收到的startId最新值。如果调用StartServic没有导致运行OnStartCommand,则系统不会停止该服务,因为startId调用中所使用的方法将不会对应于最新的StartService调用。
一个started服务必须自行管理生命周期。也就是说,系统不会终止或销毁这类服务,除非必须恢复系统内存并且服务返回后一直维持运行。 因此,服务必须通过调用stopSelf()自行终止,或者其它组件可通过调用stopService()来终止它。
再强调一遍:当服务完成工作后,你的应用程序应该及时终止它,这非常重要。因为这样可以避免系统资源的浪费,并能节省电池电量的消耗。必要时,其它组件可以通过调用StopService()来终止服务。即使你的服务允许绑定,你也必须保证它在收到对OnStartCommand()的调用时能够自行终止。
用StopSelf()或StopService()的终止请求一旦发出,系统就会尽快销毁服务。
不过,如果你的服务要同时处理多个OnStartCommand()请求,那么,在处理启动请求的过程中,你就不应该去终止服务,因为你可能接收到了一个新的启动请求(在第一个请求处理完毕后终止服务将停止第二个请求的处理。为了避免这个问题,你可以用StopSelf(int)来确保终止服务的请求总是根据最近一次的启动请求来完成。也就是说,当你调用StopSelf(int) 时,你把启动请求ID(发送给OnStartCommand()的startId)传给了对应的终止请求。这样,如果服务在你可以调用StopSelf(int)时接收到了新的启动请求,则ID将会不一样,服务将不会被终止。
四、示例1--StartedServiceDemo1
运行截图
主要设计步骤
(1)添加ch1601_Main.axml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:id="@+id/ch1601StartService"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="启动服务" />
<Button
android:id="@+id/ch1601StopService"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="停止服务" />
</LinearLayout>
(2)添加ch1601ServiceDemo.cs
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Widget;
using System.Threading; namespace MyDemos.SrcDemos
{
[Service]
public class ch1601ServiceDemo : Service
{
Thread thread; [return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
//获取主线程的消息循环后,就可以在主线程中显示来自服务的消息了
var myHandler = new Handler(MainLooper); //在此处执行需要长时间处理的服务
thread = new Thread(() =>
{
//处理过程中,还可以告诉用户处理的状态
//这里用每隔3秒显示一次消息来模拟,此服务可随时被MainActivity终止
for (int i = 1; i <= 10; i++)
{
var msg = string.Format("这是来自服务的第{0}个消息", i);
Thread.Sleep(3000);
myHandler.Post(() =>
{
Toast.MakeText(this, msg, ToastLength.Long).Show();
});
}
StopSelf();
});
thread.Start(); return StartCommandResult.NotSticky;
} public override void OnDestroy()
{
base.OnDestroy(); thread.Abort();
var myHandler = new Handler(MainLooper);
myHandler.Post(() =>
{
Toast.MakeText(this, "服务已停止", ToastLength.Long).Show();
});
} //基类要求实现的接口
public override IBinder OnBind(Intent intent)
{
return null;
}
}
}
注意,如果你在运行中发现中文显示为乱码,别忘了你需要在AssemblyInfo.cs文件中指定区域语言(前面章节已经介绍过),即修改下面的语句(在参数中指定“zh-CN”):
[assembly: AssemblyCulture("zh-CN")]
网上介绍的什么更改高级保存选项都是挖坑的,千万别信。
(3)添加ch1601MainActivity.cs
using Android.App;
using Android.Content;
using Android.OS;
using Android.Widget; namespace MyDemos.SrcDemos
{
[Activity(Label = "ch1601MainActivity")]
public class ch1601MainActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.ch1601_Main); Intent intent = new Intent(this, typeof(ch1601ServiceDemo)); var start = FindViewById<Button>(Resource.Id.ch1601StartService);
start.Click += delegate
{
StartService(intent);
Toast.MakeText(this, "服务已启动!", ToastLength.Short).Show();
}; var stop = FindViewById<Button>(Resource.Id.ch1601StopService);
stop.Click += delegate
{
StopService(intent);
Toast.MakeText(this, "服务被强行请求停止!", ToastLength.Short).Show();
};
}
}
}
【Android】16.2 Started Services的更多相关文章
- 【Android】16.3 带Intent过滤器的Services
分类:C#.Android.VS2015: 创建日期:2016-03-01 一.简介 这一节演示带Intent过滤器的Services的基本用法. 1.配置Intent Filter 不论是本地解决方 ...
- 【Android】17.1 Bound Services基本概念
分类:C#.Android.VS2015: 创建日期:2016-03-03 一.Bound Services—被绑定的服务 1.什么是Bound Service Bound Service是指通过接口 ...
- 【Android】16.0 第16章 自定义服务和系统服务—本章示例主界面
分类:C#.Android.VS2015: 创建日期:2016-03-01 一.简介 本章主要演示Started Service.带Intent过滤器的Started Service.IntentSe ...
- 【Android】16.5 Android内置的系统服务
分类:C#.Android.VS2015: 创建日期:2016-03-01 一.简介 实际上,在Android.Content.Context类中,Android已经提供了多种类型的系统服务,这些服务 ...
- 【Android】16.4 IntentService类
分类:C#.Android.VS2015: 创建日期:2016-03-01 一.简介 为了进一步简化Intent过滤器的用法,Android系统又提供了一个IntentService类,这样一来,你也 ...
- 【Android】16.1 Android Service基本概念
分类:C#.Android.VS2015: 创建日期:2016-03-01 一.简介 为了解决在后台运行任务的问题,Android引入了一个称为Service的应用程序组件.Service的职责是专门 ...
- 【Android】16.0 UI开发(七)——列表控件RecyclerView的点击事件实现
1.0 在各布局的基础上,修改ProvinceAdapter.java的代码: package com.example.recyclerviewtest; import android.support ...
- 【Android】一种提高Android应用进程存活率新方法
[Android]一种提高Android应用进程存活率新方法 SkySeraph Jun. 19st 2016 Email:skyseraph00@163.com 更多精彩请直接访问SkySeraph ...
- 【Android】Android 移动应用数据到SD
[Android]Android 移动应用数据到SD 在应用的menifest文件中指定就可以了,在 <manifest> 元素中包含android:installLocation 属性, ...
随机推荐
- combogrid翻页后保持显示内容为配置的textField解决办法
easyui的combogrid当配置pagination为true进行分页时,当datagrid加载其他数据页,和上一次选中的valueField不匹配时,会导致combogrid直接显示value ...
- opencv CxImage 互转 (Mat)
//to Mat CxImage img; img.Load("C:\\f.jpg"); uint8_t* buf=NULL; int32_t len=0; bool rs =im ...
- Android 4.4 KitKat升级率已经接近18%(2014-07-09 07:29)
腾讯数码讯(编 译:张秀梅)按照惯例, 每个月的第一个星期的星期一谷歌都会发布最新一期Android版本分布图.从去年十月末谷歌发布Android 4.4 KitKat以来,截止到目前为止Androi ...
- OpenGL: 你不知道的左右手坐标系
左右手坐标系 众所周知,OpenGL使用的是右手坐标系,而Direct3D使用的是左手坐标系. 除了上面Z轴的方向不一样外,左右手坐标系的还有其他区别: 正向旋转方向:在左手系中用Left-Hand ...
- liunx修改字体为宋体
有找到修改Linux默认字体的方法sudo vi /etc/fonts/conf.d/69-language-selector-zh-cn.conf修改下sans-serif相关设定 <m ...
- 服务 进程守护 MarsDaemon 简介
MarsDaemon 基本功能 https://github.com/Marswin/MarsDaemon It is a lite library, you can make your projec ...
- jquery easyui里datagrid用法记录
1.删除行方法(deleteRow) $(); //1代表选中的行索引 2.删除多行数据 var rows = $('#ruleManagementTable').datagrid("get ...
- <The Art of Readable Code> 笔记二 (上)
第2章 封装信息到名字 (Packing information into names) 2.1 use specific words GetPage() 不如 FetchPage() 和 Dow ...
- ExplorerControls的显示问题
我们都知道ArcGIS桌面版本"添加数据"的功能是弹出一选择对话框,如下图所示,但我们总想搞自己的,以便融入自己的风格.下图左边是ArcGIS自带的"添加数据" ...
- TCP客户/服务器程序实例——回射服务器
目录 客户/服务器程序源码 POSIX信号处理 POSIX信号语义 处理SIGCHLD信号 处理僵死进程 处理被中断的系统调用 wait和waitpid函数 wait和waitpid函数的区别 网络编 ...