在Android服务中为长时间运行的任务实现状态机
我想知道Android服务中的后台任务如何被分解成不同的状态,并在服务中进行时通知这些状态。为任何对Android服务感兴趣的人分享我的文章,以及从服务到UI线程的通知 在下面的示例中,我将展示如何将在服务中运行的长时间运行的后台任务分解为状态机的不同状态,并在服务中发生的每个阶段通知前端UI。这里我使用了一个名为LongRunningService的服务,它实际上(理论上)执行从网络服务器下载一个大文件的任务(然而,为了简单起见,我刚刚用一个延迟为1000毫秒的线程删除了实际的下载代码)。此后台任务已根据状态机划分为不同的状态,如“启动连接”、“连接已完成”、“开始下载”和“停止下载”。该应用程序还展示了通过Android messenger框架从后台服务到前端UI通信的概念 因此,让我们开始深入研究应用程序的源代码 首先是课堂上的主要活动 从代码中可以清楚地看到,主活动有一个messenger,其消息处理部分由一个名为MessageHandler(从Handler派生)的类定义。这是后台服务通知UI线程的messenger对象 UI有一个按钮。单击后,它将启动服务,一旦启动服务,服务将通过messenger开始通知服务的不同状态 这很简单。对 类MainActivity.Java在Android服务中为长时间运行的任务实现状态机,android,android-service,state-machine,Android,Android Service,State Machine,我想知道Android服务中的后台任务如何被分解成不同的状态,并在服务中进行时通知这些状态。为任何对Android服务感兴趣的人分享我的文章,以及从服务到UI线程的通知 在下面的示例中,我将展示如何将在服务中运行的长时间运行的后台任务分解为状态机的不同状态,并在服务中发生的每个阶段通知前端UI。这里我使用了一个名为LongRunningService的服务,它实际上(理论上)执行从网络服务器下载一个大文件的任务(然而,为了简单起见,我刚刚用一个延迟为1000毫秒的线程删除了实际的下载代码)。此后
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);
}
}
现在让我们开始挖掘LongRunningService类
正如我们所知,服务通常在主线程中运行。因此,在后台服务很长的情况下,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的处理程序类,我们通过它将消息从服务发送到线程的消息循环。在消息循环中,我们实际上完成了长时间运行的任务。让我们看看这个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函数中,我们获取了对主活动的messenger的引用,它处于不同的状态,如“连接”、“连接”、“开始下载”和“完成下载”。在每个状态下,通过messenger向UI线程传递一个不同的整数常量
在主UI线程中,messenger的handler函数处理来自服务的这些消息,并显示每个状态的状态
之后,服务自动停止
服务类
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 messenger背后的一些想法,以及我们如何使用它从后台服务向前端用户界面发送通知