• 【Android】16.2 Started Services


    分类:C#、Android、VS2015;

    创建日期:2016-03-01

    一、简介

    Started Service是指被同一个应用程序的某个对象显式启动,或者在设备引导时就已经启动了(配置了服务的情况)。

    二、Started Services的生命周期

    前面我们说过,Service只是一种被分离出来的组件(例如从某个Activity中分离出来),可被单独启动启动和停止。因此不论是Started Services还是Bound Services,这些Services都有它自己独立的生命周期。

    下图演示了Started Services的生命周期期间调用的方法。

    image

    一旦服务被启动(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

    运行截图

    image   image

    主要设计步骤

    (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();
                };
            }
        }
    }
  • 相关阅读:
    Exaple2_1(显示转换)
    Example2_4(数据的输入Scanner)
    安装jdk遇到的问题
    Java应用程序,用户从键盘只能输入整数,程序输出这些整数的乘积
    Hello.Java//Tom and Jerry
    Example2_3(数据输出System.out.printf)
    Example2_2(基本类型转换)
    c++与java的区别
    大龄屌丝自学笔记Java零基础到菜鸟004
    大龄屌丝自学笔记Java零基础到菜鸟003
  • 原文地址:https://www.cnblogs.com/rainmj/p/5229568.html
Copyright © 2020-2023  润新知