Android16.2 Started Services

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android16.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();
            };
        }
    }
}

以上是关于Android16.2 Started Services的主要内容,如果未能解决你的问题,请参考以下文章

Android模拟器上网 DNS_PROBE_STARTED

Android模拟器上网 DNS_PROBE_STARTED

python 自动化uiautomator 测试android报错:ioerror RPC server not started

[react-native]Getting Started for Android on Windows

[react-native]Getting Started for Android on Windows

react16.2新特性