【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 属性, ...
随机推荐
- Source Insight 源代码查看工具
在开发的过程中,有时候我们需要研究源代码,查看源码是一个好的习惯,能帮我们学到很多的东西,比如JDK可以帮助我们理解很多设计模式在实际开发中的应用,又或者android开发者,源代码更是必不可少的,当 ...
- react组件引用时的default常见错误
1.下面使用方法是正确的: export class StepLoad extends React.Component {} 引用上面插件的方法,重命名 import {StepLoad as Ste ...
- vmware workstation无法打开内核设备问题处理办法
vmware workstation无法打开内核设备:\\Global\\vmx86 ? 解决办法如下: 开始 - 运行(输入CMD)- 确定或者回车,打开管理员命令窗口: net start vmc ...
- adore-ng笔记和Linux普通用户提权
官网:https://github.com/trimpsyw/adore-ng 安装: [root@xuegod63 ~]# unzipadore-ng-master.zip [root@xuegod ...
- Javascript屏蔽鼠标右键-超简单,不过还是记录一下
Javascript屏蔽鼠标右键,其实作用也不大,也许是为了防止别人查看源代码,其实想查看源代码有多种途径,未必需要通过鼠标右键.不过当我们自定义鼠标右键弹出菜单时,也许需要这些. 下面是源码: 1 ...
- C++实现委托机制(二)
1.引言: 上一篇文章已经介绍了如何构建一个无参数无返回值的函数指针的委托,这篇文章将对上一文章所述委托进行扩展,使得可以注册任意函数指针,不过再讲篇内容之前先要介绍一下实现这个功能所需要了解的C++ ...
- 已知m和n是两个整数,并且m^2+mn+n^2能被9整除,试证m,n都能被3整除。
引证:m,n都是整数,m2=3n,求证m是3的倍数. 引证证明:(反证法)假设m并非3的倍数,那么m2则不含因数3,则m2≠3n,这与已知条件相反. 所以,当m2=3n时,m必是3的倍数. 有了引证, ...
- ASPX导入JS,JavaScript乱码怎么办
不管你把JS改成UTF-8还是ASCII格式,弹出都是乱码. 你只要在ASPX文件顶部加上"ResponseEncoding="gb2312" ContentType=& ...
- How to change in the Cocos2d-x project from landscape to portrait both in iOS and Android
iOS: This is done using XCode project details (select the required orientation) Android: AndroidMani ...
- iOS 图片比例缩放
方法 //Resize image - (UIImage *)resizeImage:(UIImage *)image withQuality:(CGInterpolationQuality)qual ...