65.9K
CodeProject 正在变化。 阅读更多。
Home

MonoAndroid:使用 Started Service

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (12投票s)

2013年9月4日

CPOL

5分钟阅读

viewsIcon

50003

downloadIcon

619

已启动服务的演示。

引言

我将再次带来我的 **MonoAndroid** 系列文章。前几篇文章请参阅文章底部。在这篇文章中,我将演示如何在移动应用中创建已启动的服务。它类似于 Windows 服务,但有一个区别,它是由前端应用程序创建和运行的,而 Windows 服务则需要由它们自己的控制台进行安装和管理。

根据 Android 开发者指南,“服务是应用程序组件,表示应用程序在不与用户交互的情况下执行较长时间操作的意愿,或为其他应用程序提供可供使用的功能。” 

服务有两种类型:已启动服务和绑定服务。“已启动服务”将一直运行,直到被要求关闭;而“绑定服务”的生命周期与其创建它的应用程序绑定。下面是已启动服务的生命周期图:

在本文中,我将创建一个名为 **“长时计算”** 的应用程序。在此应用程序中,我们将使用 `ArrayAdapter` 来模拟 `ListView` 中的数学表格。

我们将一步一步进行。

  1. 通过选择 **新建 -> 解决方案** 并为其命名为“`myStartedServiceApp`”来创建 Android 应用程序。



    图 1:创建Android项目!
     
  2.  
  3. 现在,在“布局”下的 `Main.axml` 文件中进行以下更改。添加 UI 代码后,它看起来大致如下:
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <TextView
            android:text="@string/entername"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:layout_width="110dp"
            android:layout_height="24.7dp"
            android:id="@+id/textView1"
            android:layout_marginRight="50dp" />
        <Button
            android:id="@+id/btnCalculate"
            android:layout_width="fill_parent"
            android:layout_height="31.3dp"
            android:text="@string/hello"
            android:textSize="10dp"
            android:layout_toRightOf="@+id/textView1"
            android:layout_marginBottom="0.0dp" />
        <EditText
            android:inputType="numberSigned"
            android:layout_width="110dp"
            android:layout_height="wrap_content"
            android:id="@+id/txtNumber"
            android:layout_below="@+id/textView1"
            android:layout_marginRight="50dp"
            android:layout_marginBottom="8.7dp" />
        <Button
            android:id="@+id/btnStopService"
            android:layout_width="fill_parent"
            android:layout_height="31.3dp"
            android:text="@string/StopService"
            android:textSize="10dp"
            android:layout_below="@+id/btnCalculate"
            android:layout_toRightOf="@+id/txtNumber"
            android:layout_marginBottom="0.0dp" />
        <ListView
            android:minWidth="25px"
            android:minHeight="25px"
            android:layout_width="fill_parent"
            android:layout_height="match_parent"
            android:id="@+id/LVTable"
            android:layout_below="@+id/txtNumber" />
    </RelativeLayout>

    在此布局中,我添加了两个按钮:一个用于启动服务,另一个用于停止服务;以及用于获取生成数学表格的数字的 EditText。完成后,UI 如下所示:




    图:UI 屏幕
     
  4. 现在是时候在项目中添加 **BroadcastReceiver** 的派生类 **MyServiceBR** 了。此类将充当从服务向前端活动发送信息的媒介。添加后,代码看起来如下:

    [BroadcastReceiver]
    public class MyServiceBR : BroadcastReceiver
    {
    	public event Action<Context,Intent> OnBroadcastReceive = null;
    	public override void OnReceive (Context context, Intent intent)
    	{
    		if (OnBroadcastReceive != null)
    			OnBroadcastReceive.Invoke (context, intent);
    	}
    }            
    
  5. 我添加了一个包含字符串常量的类,我将在整个项目中使用这些常量。代码如下:

     
    public class MyServiceConstant
    {
    	public const string MSG_FROM_SERVICE ="MSG_FROM_SERVICE";
    	public const string VAR_CALCNUMBER ="CalculateNumber";
    	public const  string VAR_RETN_TABLE ="ReturnedTable";
    }
    

  6. 现在是时候添加我们派生的服务类了,我们将其命名为 **MyStartedServiceCls**。您需要实现 `OnBind` 函数,从中可以向操作系统指示这是已启动服务还是绑定服务。如果从 `OnBind` 方法返回 NULL,则表示当前服务是已启动的服务;否则,如果您传递 `IBinder` 派生类的对象,则表示您打算创建绑定服务。所以我们在这里传递 `NULL`。
    #region implemented abstract members of Service
            public override IBinder OnBind (Intent intent)
            {
                return null;
            }
    #endregion 
    
    通常,任何服务开发者都会重写以下方法:
    • OnStart :所有服务初始化代码都放在这里
    • OnStartCommand:每次有人调用 `StartService` 方法时都会调用此方法,因为调用参数中提供了一个 `intent`,您可以在调用时向其传递消息。
    • OnDestroy :您想要执行的所有清理任务。
    public override void OnStart (Intent intent, int startId){
    }
    
    public override void OnDestroy (){
                base.OnDestroy ();
    } 
    

  7. 现在,在我们的 **OnStartCommand** 方法中,添加以下代码:
    public override StartCommandResult OnStartCommand (Intent intent, 
    StartCommandFlags flags, int startId)
    {
    
          int variable = int.Parse(
    intent.GetStringExtra (MyServiceConstant.VAR_CALCNUMBER));
    
    Thread tVar = new Thread(() =>
    {
      for (int value = 1; value <= 10; value++) {
          Intent newIntent = 
            new Intent(MyServiceConstant.MSG_FROM_SERVICE);
      
          newIntent.PutExtra(MyServiceConstant.VAR_RETN_TABLE,
             string.Format(" {0} X {1} = {2}",
                         variable,value,variable*value));
    
          SendBroadcast(newIntent);
          Thread.Sleep(1000);
      }
    });
    
         tVar.Start ();
         return base.OnStartCommand (intent, flags, startId);
    } 
    

    在此方法中,我们返回数学表格(我们小时候在学校学过的,任何数字的一到十的乘法)。我们从 `intent` 中检索需要生成表格的数字。然后,我们使用 **SendBroadcast** 方法广播格式化后的字符串,为了模拟长时间运行的任务,我们将每秒广播一次消息。


  8. 现在,在 `MainActivity.cs` 中添加以下代码:
    public class MainActivity : Activity
    {
            EditText _txtNumber = null;
            Button _btnCalculate =null,_btnStopService=null;
            ListView _lvTable=null;
            MyServiceBR _myServiceBR = new MyServiceBR();
            List<String> _myStringList = new List<String> ();
            Intent _serviceIntent; 
    
    // Other Methods
    } 
    
    类方法及其说明:

    • OnCreate:- 在此方法中,我们将初始化主屏幕上的所有 UI 控件,为 `OnBroadcastReceive` 创建事件处理程序,并使用 **RegisterReceiver** 方法注册 **MyServiceConstant.MSG_FROM_SERVICE** 的广播接收器。
      protected override void OnCreate (Bundle bundle) {
                  base.OnCreate (bundle);
                  // Set our view from the "main" layout resource
          SetContentView (Resource.Layout.Main);
          GetUIControls ();
      
          _myServiceBR.OnBroadcastReceive += HandleOnBroadcastReceive;
          
      RegisterReceiver(_myServiceBR,
      new IntentFilter(MyServiceConstant.MSG_FROM_SERVICE));
      
      }
      
    • GetUIControls :此方法将所有 UI 控件与变量关联。
      void GetUIControls ()
      {
        _btnCalculate = FindViewById<Button>  (Resource.Id.btnCalculate);
        _txtNumber = FindViewById<EditText>  (Resource.Id.txtNumber);
        _lvTable = FindViewById<ListView>  (Resource.Id.LVTable);
        _btnStopService= FindViewById<Button>  (Resource.Id.btnStopService);
        
        _btnCalculate.Click += btnCalculate_Click;
        _btnStopService.Click += btnStopService_Click;
      } 
      
    • btnCalculate_Click :- 此方法将清除所有字符串列表,然后我们将创建一个 **MyStartedServiceCls** 类型的 `intent`,将其与 **EditBox** 中的文本关联,并使用 **StartService** 方法启动服务。 subsequent 对此方法的调用将调用服务的 **OnStartCommand** 方法,而不是一次又一次地启动服务。
      void btnCalculate_Click (object sender, EventArgs e)
      {
        _myStringList.Clear ();
        _serviceIntent = new Intent (this, typeof(MyStartedServiceCls));
      
        _serviceIntent.PutExtra (MyServiceConstant.VAR_CALCNUMBER, 
      _txtNumber.Text);
      
           StartService (_serviceIntent);
      } 
      
      
    • btnStopService_Click:此方法将停止正在运行的服务(如果您想停止它)。
      void btnStopService_Click (object sender, EventArgs e)
      {
           StopService (_serviceIntent);
      } 
      
    • HandleOnBroadcastReceive :此方法将处理由服务发送的广播,并使用服务发送的信息更新 `ListView`。
      void HandleOnBroadcastReceive (Context arg1, Intent arg2)
      {
              _myStringList.Add (arg2.GetStringExtra (
      MyServiceConstant.VAR_RETN_TABLE));
      
         _lvTable.Adapter = new ArrayAdapter<string>  (this, 
              Android.Resource.Layout.SimpleListItem1,
             _myStringList);
      
      } 
      
      为了更新 `ListView`,我们使用 `ArrayAdapter`,通过传递 Android 通用布局类型 `SimpleListItem1` 和 `myStringList` 作为要更新的项目列表。

  9. 现在构建并运行应用程序,
    初始屏幕 更新 更新时间
            


  10. 如果您不停止服务,您可以在应用控制台中检查它,如下面的图像所示。(它位于设置->应用->正在运行的进程下)
    图片 1 图片 2


StartCommandResult

本节文本摘自 这里,逐字翻译。

当系统内存不足时,Android 可能会停止任何正在运行的服务。

当服务被系统停止时,Android 将使用 `OnStartCommand` 的返回值来确定服务应该如何或是否应该重新启动。此值为 `StartCommandResult` 类型,可以是以下任一类型:

返回类型 描述
Sticky  粘性服务将被重启,并且在重启时会向 `OnStartCommand` 传递一个空 `intent`。当服务持续执行长时间运行的操作时使用,例如更新股票行情。
RedeliverIntent 服务将被重启,并且服务被系统停止前传递给 `OnStartCommand` 的最后一个 `intent` 将被重新传递。用于继续长时间运行的命令,例如大文件上传的完成。
NotSticky 服务不会自动重启。
StickyCompatibility 在 API 级别 5 或更高版本上,重启行为将类似于 Sticky;但在早期版本上,行为将降级到级别 5 之前的行为。
     

关注点

我使用 **MonoAndroid for C#** 和 Xamarin Studio 构建了本教程。请留意,如果我继续学习,您可能会很快看到更多文章。

尽管Xamarin Studio是专有软件,但他们提供了免费的入门版本来构建、测试和发布Android应用程序。我正在使用它来学习。

本系列文章!

本系列技巧/窍门

历史   

  • 2013 年 9 月 4 日:初版 
  • 2013 年 9 月 6 日:更新了文章和技巧/窍门部分 
  • 2013年10月11日:更新了文章部分。
  • 2013年11月5日:更新了文章部分。
  • 2013年11月22日:更新了其他文章部分
© . All rights reserved.