天天看點

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>
           

繼續閱讀