天天看点

Android Api Demos登顶之路(五十八)Service Start Argument Control

Service 的onStartCommand 或是 onStart(2.1版本之前)是使用调用它的Android组件(通常是Activity)同一个Thread来执行的,对应Activity来说,这个Thread通常是UI Thread,当我们需要在Service当中进行一些耗时的操作,比如网络通讯,比如数据库操作,这个时候就会阻塞UI线程。

所以通常需要单独开启一个线程来执行这些耗时的操作。

Android.os 的Handler, HandlerThread, Loop, Message 常用于Service中,用于客户端与服务端的通讯和交互。

Android 中每个Thread都可以有一Message Queue,并通过Looper实现对消息的管理,但除UI Thread外,因为UI线程中自身就带有一个Message Queue和一个Looper。

Thread缺省情况下不带Message Queue, 要为一个Thread 创建一个Message Queue,Looper.prepare()用来创建一个Message Queue, Looper.loop() 处理消息直到Loop停止。 在Thread在创建的Handler将会和Thread的Message Queue关联。Handler的handleMessage用来处理消息,其类型为Message类。

HandlerThread继承了Thread,它是一个特殊的Thread,因为它本身就带有Looper,可以让我们方便地在线程中使用handler来处理客户端传递过来的信息。

我们现在来简单梳理一下这个demo的基本思路:

1.在客户端启动服务时通过Intent的putExtr()方法将服务端需要的参数传递到服务端。

2.在服务端的onStrartCommand()方法中将Intent中的数据取出。

构造一条Message发送给Handler,让它去处理这条消息。

3.在消息的处理中采用使进程停顿5秒的方式来模拟耗时操作。

另外还需要注意的一点是:我们可以使用KillProcess来结束服务的进程,但这个进程并没有被彻底杀死,当需要使用到服务时系统会自动重新启动这个进程。

在本例中,通过KillProcess来模拟进程意外终止的情况,但由于在结束进程时,服务还有任务没有处理完,所以进程结束后会马上重新启动。服务会被重新创建,重新开始没有完成的那部分任务。无论我们在onStartCommand方法中是否返回的是START_REDELIVER_INTENT。所以当我们点击Strart failuer delivery按钮时,执行完onStartCommand方法中的前半部分后,进程结束,但马上进程又会被重新启动,重新创建服务,重新将未完成的Intent传入,并继续执行完毕。

当我们点击Strart three w/redeliver按钮,然后再点击Strart failuer delivery时服务被重新启动后首先恢复的是three w/redeliver的Intent,执行完这个Intent后再去执行未完成任务的failure的Intent。

activity_main.xml

<LinearLayout 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:orientation="vertical" 
    android:padding="5dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="@string/hello_world" />
    <Button 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:id="@+id/start_one"
        android:text="Start one no deliver"/>
     <Button 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:id="@+id/start_two"
        android:text="Start two with redeliver"/>
      <Button 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:id="@+id/start_failure"
        android:text="Start failed deliver"/>
       <Button 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:id="@+id/kill_progress"
        android:text="Kill Progress"/>

</LinearLayout>
           

ServiceStartArgument.java

public class ServiceStartArgument extends Service {
    private NotificationManager mNM;
    /*
     * Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。
     * 而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。 这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
     */
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mHandler;

    /*
     * 构建一个处理消息的类,这个类继承Handler,并且用作一个独立的线程当中
     * 所以必须要有一个Looper用来创建和管理Message队列,同时这个looper也
     * 用来沟通Handler和Thread,让handler与thread一一对应起来,明确当前的handler是 用来处理哪个线程中的消息的。
     */
    private class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Bundle bundle = (Bundle) msg.obj;
            String txt = bundle.getString("name");
            // 打印一下日志
            Log.i("ServiceStartArguments",
                    "Message: " + msg + ", " + bundle.getString("name"));
            // 区分一下Intent是否是进程意外终止后又重新传入的
            if ((msg.arg2 & START_FLAG_REDELIVERY) == ) {
                txt = "New cmd #" + msg.arg1 + ": " + txt;
            } else {
                txt = "Re-delivered #" + msg.arg1 + ": " + txt;
            }

            // 显示Notification
            showNotification(txt);

            // 停顿5秒模拟耗时操作
            synchronized (this) {
                try {
                    Thread.sleep( * );
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            hideNotification();
            // 打印日志
            Log.i("ServiceStartArguments", "Done with #" + msg.arg1);
            // 结束服务
            stopSelf(msg.arg1);
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    public void hideNotification() {
        mNM.cancel(R.string.hello_world);
    }

    public void showNotification(String txt) {
        PendingIntent contentIntent = PendingIntent.getActivity(this, ,
                new Intent(this, MainActivity.class), );
        Notification.Builder builder=new Builder(this);
        builder.setSmallIcon(R.drawable.ic_launcher)
               .setTicker(txt)
               .setContentIntent(contentIntent)
               .setContentTitle("Notification")
               .setContentText("Notification info")
               .setWhen(System.currentTimeMillis());
        Notification noti=builder.build();
        mNM.notify(R.string.hello_world, noti);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        // HandlerThread继承了Thread,它是一个特殊的Thread,因为它本身就带有Looper,
        // 可以让我们方便地在线程中使用handler来处理客户端传递过来的信息。
        HandlerThread thread = new HandlerThread("ServiceStartArgument",
                Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();
        mServiceLooper = thread.getLooper();
        mHandler = new ServiceHandler(mServiceLooper);
    }

    /*
     * 当开启并onCreate()服务后调用此方法,我们可以在这里完成一些操作。 该方法带有一个整形的返回值,通常有以下几个常量:
     * START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,
     * 但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,
     * 所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有
     * 任何启动命令被传递到service,那么参数Intent将为null。
     * START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,
     * 服务被异常kill掉,系统将会把它置为started状态,系统不会自动重启该服务, 直到startService(Intent
     * intent)方法再次被调用;
     * START_REDELIVER_INTENT:重传Intent。使用这个返回值时,如果在执行完onStartCommand后,
     * 服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。
     * START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。
     * 该方法的第二个参数和返回值相关,flags值和onStartCommand()的返回值有着直接的关系。
     * 如果Service被系统意外终止,重启的时候,传入的flags。 flags有3种取值,0, START_FLAG_REDELIVERY, or
     * START_FLAG_RETRY. 当返回值为START_REDELIVER_INTENT时服务被意外终止,重启时则传入
     * START_FLAG_REDELIVERY, 将重新传入返回START_REDELIVER_INTENT时的Intent。
     * START_FLAG_RETRY表示启动服务的Intent没有被OnStrartCommand接收到或返回, 所以需要一直重新尝试。
     * 第三个参数表示当前启动的Id,用来标识当前的启动,同时这个值也表示启动的次数。
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Log.i("ServiceStartArgument",
                "Starting #" + startId + ": " + intent.getExtras());
        // 根据客户端传递过来的数据构造消息
        Message msg = mHandler.obtainMessage();
        msg.arg1 = startId;
        msg.arg2 = flags;
        msg.obj = intent.getExtras();
        mHandler.sendMessage(msg);
        Log.i("ServiceStartArguments", "Sending: " + msg);

        /*
         * 当我们点击"Start failed deliver"按钮时来模拟意外终止进程的情况。
         * 另外还需要注意的一点是:我们可以使用KillProcess来结束服务的进程,
         * 但这个进程并没有被彻底杀死,当需要使用到服务时系统会自动重新启动这个进程。
         * 在本例中,通过KillProcess来模拟进程意外终止的情况,但由于在结束进程时,
         * 服务还有任务没有处理完,所以进程结束后会马上重新启动。服务会被重新创建,
         * 重新开始没有完成的那部分任务。无论我们在onStartCommand方法中是否返回的是 START_REDELIVER_INTENT。
         * 所以当我们点击Strart failuer delivery按钮时,执行完onStartCommand方法中
         * 的前半部分后,进程结束,但马上进程又会被重新启动,重新创建服务,重新将未完成的Intent传入, 并继续执行完毕。 当我们点击Strart
         * three w/redeliver按钮,然后再点击Strart failuer delivery 时服务被重新启动后首先恢复的是three
         * w/redeliver的Intent,执行完这个Intent后再去执 行未完成任务的failure的Intent。
         */
        if (intent.getBooleanExtra("fail", false)) {
            if ((flags & START_FLAG_RETRY) == ) {
                // 如果flags的值不是START_FLAG_RETRY我们就结束掉进程
                // 如果是START_FLAG_RETRY因为启动的意图一直都没有传递进来所以会一直retry
                // 在这种情况下我们不需要关心,因为系统会放弃启动服务,如果Intent一直无法传递进来的话
                Process.killProcess(Process.myPid());
            }
        }

        // 判断一下如果客户端传递过来的参数是redeliver则将返回值设置为START_REDELIVER_INTENT
        // 否则设置为:START_NOT_STICKY
        return intent.getBooleanExtra("redeliver", false) ? START_REDELIVER_INTENT
                : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        //退出looper
        mServiceLooper.quit();
        hideNotification();
        Toast.makeText(this, "服务已停止!", ).show();
    }

    public static class MainActivity extends Activity implements
            OnClickListener {
        private Button start_one, start_two, start_failure, kill_progress;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            start_one = (Button) findViewById(R.id.start_one);
            start_two = (Button) findViewById(R.id.start_two);
            start_failure = (Button) findViewById(R.id.start_failure);
            kill_progress = (Button) findViewById(R.id.kill_progress);

            start_one.setOnClickListener(this);
            start_two.setOnClickListener(this);
            start_failure.setOnClickListener(this);
            kill_progress.setOnClickListener(this);
        }

        // 开启服务的同时向服务端传递参数
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(MainActivity.this,
                    ServiceStartArgument.class);
            switch (v.getId()) {
            case R.id.start_one:
                intent.putExtra("name", "one");
                startService(intent);
                break;
            case R.id.start_two:
                intent.putExtra("name", "two");
                intent.putExtra("redeliver", true);
                startService(intent);
                break;
            case R.id.start_failure:
                intent.putExtra("name", "failure");
                intent.putExtra("fail", true);
                startService(intent);
                break;
            case R.id.kill_progress:
                Process.killProcess(Process.myPid());
                break;
            }
        }

    }

}
           

配置文件

<activity
            android:name="com.fishtosky.servicestartargument.ServiceStartArgument$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="com.fishtosky.servicestartargument.ServiceStartArgument"></service>
           

继续阅读