在Android服务中为长时间运行的任务实现状态机

时间:2014-01-10 05:58:48

标签: android android-service state-machine

想知道如何将Android服务中的后台任务分解为不同的状态,并在进入服务时通知这些状态。分享我对任何对Android服务感兴趣的人以及从服务到UI线程的通知。

在下面的示例中,我将展示如何将服务中运行的长时间运行的后台任务分解为状态机的不同状态,并在服务中发生的每个阶段通知前端UI。在这里,我使用了一个名为LongRunningService的服务,它实际上(理论上)完成了从网络服务器下载大文件的任务(但是,为了简单起见,我刚刚用延迟为1000毫秒的线程删除了实际的下载代码)。根据状态机,该后台任务被分为不同的状态,如“Start Connection”,“Connection Completed”,“Start Downloading”和“Stop Downloading”。此应用程序还展示了通过Android messenger框架从后台服务到前端UI的通信概念。

让我们开始深入研究应用程序的源代码。

首先是主要的Activity类。

从代码中可以清楚地看出,主活动有一个信使,其消息处理部分已由一个名为MessageHandler的类(从Handler派生)定义。这是后台服务通过UI线程的信使对象。

UI有一个按钮。点击它后,它启动服务,一旦启动服务,服务就开始通过信使通知服务的不同状态。

这很简单。右!!!

类MainActivity.Java

package com.somitsolutions.android.example.statepatterninservice;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity implements OnClickListener{
    private static final int CONNECTING = 1;
    private static final int CONNECTED = 2;
    private static final int DOWNLOADSTARTED = 3;
    private static final int DOWNLOADFINISHED = 4;

    Button startButton;
    private MessageHandler handler;
    private static MainActivity mMainActivity;

    public Messenger mMessenger = new Messenger(new MessageHandler(this));
    private class MessageHandler extends Handler{
        private Context c;

        MessageHandler(Context c){
         this.c = c;    
        }
        @Override
        public void handleMessage(Message msg) {
            switch(msg.what){
            case CONNECTING:
                Toast.makeText(getApplicationContext(), "Connecting", Toast.LENGTH_LONG).show();
                break;
            case CONNECTED:
                Toast.makeText(getApplicationContext(), "Connected", Toast.LENGTH_LONG).show();
                break;
            case DOWNLOADSTARTED:
                Toast.makeText(getApplicationContext(), "Download Started", Toast.LENGTH_LONG).show();
                break;
            case DOWNLOADFINISHED:
                Toast.makeText(getApplicationContext(), "Download Finished", Toast.LENGTH_LONG).show();
                break;
            default:
                super.handleMessage(msg);

            }
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mMainActivity = this;
        startButton = (Button)findViewById(R.id.button1);

        startButton.setOnClickListener(this);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
    public static MainActivity getMainActivity(){
        return mMainActivity;
    }


    @Override
    public void onClick(View arg0) {
        // TODO Auto-generated method stub
        Intent serv = new Intent(MainActivity.this, LongRunningService.class);
        startService(serv);
    }
}

现在让我们开始挖掘LongrunningServivce类。

我们知道服务通常在主线程中运行。因此,在长背景服务的情况下,UI线程似乎被冻结。为了克服在创建后台线程的同时启动服务并在该线程中执行任务的时刻。从以下代码中可以清楚地看出这一点。

@Override
      public void onCreate() {
        // Start up the thread running the service.  Note that we create a
        // separate thread because the service normally runs in the process's
        // main thread, which we don't want to block.  We also make it
        // background priority so CPU-intensive work will not disrupt our UI.
        HandlerThread thread = new HandlerThread("ServiceStartArguments",
                Thread.NORM_PRIORITY);
        thread.start();

        // Get the HandlerThread's Looper and use it for our Handler
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
      }

服务类还有一个名为ServiceHandler的Handler类,我们通过它将消息从服​​务发送到线程的消息循环。在消息循环中,我们实际上完成了长时间运行的任务。让我们看一下这个ServiceHandler类

private final class ServiceHandler extends Handler {
          public ServiceHandler(Looper looper) {
              super(looper);
          }
          @Override
          public void handleMessage(Message msg) {


              Messenger messenger= MainActivity.getMainActivity().mMessenger;

                 try {


                     messenger.send(Message.obtain(null, CONNECTING, "Connecting"));
                    // Normally we would do some work here, like download a file.
                      // For our sample, we just sleep for 10 seconds.
                    Thread.sleep(1000);
                    // Normally we would do some work here, like download a file.
                      // For our sample, we just sleep for 10 seconds.

                    messenger.send(Message.obtain(null, CONNECTED, "Connected"));
                    // Normally we would do some work here, like download a file.
                    // For our sample, we just sleep for 10 seconds.
                    Thread.sleep(1000);


                    messenger.send(Message.obtain(null, DOWNLOADSTARTED, "Download Started"));
                    // Normally we would do some work here, like download a file.
                    // For our sample, we just sleep for 10 seconds.
                    Thread.sleep(1000);


                    messenger.send(Message.obtain(null, DOWNLOADFINISHED, "Download Finished"));


                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (RemoteException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
              // Stop the service using the startId, so that we don't stop
              // the service in the middle of handling another job
              stopSelf(msg.arg1);
          }
      }

从上面的代码可以清楚地看出,在服务处理程序的这个重写的HandleMessage函数中,我们获取了对主活动的信使的引用,它通过“连接”,“连接”,“启动”等不同状态下载“和”完成下载“。在每个状态中,通过信使将不同的整数常量传递给UI线程。

在主UI线程中,messenger的处理函数处理来自服务的这些消息,并显示每个状态的状态。

之后服务停止。

服务类

package com.somitsolutions.android.example.statepatterninservice;


import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.widget.Toast;

public class LongRunningService extends Service {

    private static final int CONNECTING = 1;
    private static final int CONNECTED = 2;
    private static final int DOWNLOADSTARTED = 3;
    private static final int DOWNLOADFINISHED = 4;

    private Looper mServiceLooper;
    private ServiceHandler mServiceHandler; // Handler that receives messages from the thread
    private final class ServiceHandler extends Handler {
          public ServiceHandler(Looper looper) {
              super(looper);
          }
          @Override
          public void handleMessage(Message msg) {


              Messenger messenger= MainActivity.getMainActivity().mMessenger;

                 try {


                     messenger.send(Message.obtain(null, CONNECTING, "Connecting"));
                    // Normally we would do some work here, like download a file.
                      // For our sample, we just sleep for 10 seconds.
                    Thread.sleep(1000);
                    // Normally we would do some work here, like download a file.
                      // For our sample, we just sleep for 10 seconds.

                    messenger.send(Message.obtain(null, CONNECTED, "Connected"));
                    // Normally we would do some work here, like download a file.
                    // For our sample, we just sleep for 10 seconds.
                    Thread.sleep(1000);


                    messenger.send(Message.obtain(null, DOWNLOADSTARTED, "Download Started"));
                    // Normally we would do some work here, like download a file.
                    // For our sample, we just sleep for 10 seconds.
                    Thread.sleep(1000);


                    messenger.send(Message.obtain(null, DOWNLOADFINISHED, "Download Finished"));


                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (RemoteException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
              // Stop the service using the startId, so that we don't stop
              // the service in the middle of handling another job
              stopSelf(msg.arg1);
          }
      }

    @Override
      public void onCreate() {
        // Start up the thread running the service.  Note that we create a
        // separate thread because the service normally runs in the process's
        // main thread, which we don't want to block.  We also make it
        // background priority so CPU-intensive work will not disrupt our UI.
        HandlerThread thread = new HandlerThread("ServiceStartArguments",
                Thread.NORM_PRIORITY);
        thread.start();

        // Get the HandlerThread's Looper and use it for our Handler
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
      }

      @Override
      public int onStartCommand(Intent intent, int flags, int startId) {
          Toast.makeText(this, "download service starting", Toast.LENGTH_SHORT).show();

          // For each start request, send a message to start a job and deliver the
          // start ID so we know which request we're stopping when we finish the job
          Message msg = mServiceHandler.obtainMessage();
          msg.arg1 = startId;
          mServiceHandler.sendMessage(msg);

          // If we get killed, after returning from here, restart
          return START_STICKY;
      }

      @Override
      public IBinder onBind(Intent intent) {
          // We don't provide binding, so return null
          return null;
      }

      @Override
      public void onDestroy() {
        Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
      }
    }

main.xml布局文件如下所示:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="165dp"
        android:text="Start Service" />

</RelativeLayout>

此应用程序的清单文件如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.somitsolutions.android.example.statepatterninservice"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.somitsolutions.android.example.statepatterninservice.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".LongRunningService"></service>
    </application>

</manifest>

希望它能够清除Android信使背后的一些想法,以及我们如何将其用于从后台服务到前端用户界面的通知。

0 个答案:

没有答案