1. 程式人生 > >第一行程式碼——第十章:後臺默默的勞動者——探究服務

第一行程式碼——第十章:後臺默默的勞動者——探究服務

目錄:

10.1 服務是什麼

10.2 Android 多執行緒程式設計

10.2.1 執行緒的基本用法

10.2.2 在子執行緒中更新UI

10.2.3 解析非同步訊息處理機制

10.2.4 使用AsyncTask

10.3 服務的基本用法

10.3.1 定義一個服務

10.3.2 啟動和停止服務

10.3.3 活動和服務進行通訊

10.4 服務的宣告週期

10.5 服務的更多技巧

10.5.1 使用前臺服務

10.5.2 使用IntentService

10.6 服務的最佳實戰——完整版的下載例項

10.7 小結與點評


知識點:

10.1 服務是什麼

服務(Service)是 Android 中實現程式後臺執行的解決方案,它非常適合去執行那些不需要和使用者互動而且還要求長期執行的任務。
服務並不是執行在一個獨立的程序當中的,而是依賴於建立服務時所在的應用程式程序。
服務並不會自動開啟執行緒,所有程式碼預設執行在主執行緒中。

10.2 Android 多執行緒程式設計

當我們執行一些耗時操作,如發起一條網路請求時,考慮到網速等其他原因,伺服器未必會立刻響應我們的請求,若不將這類操作放在子執行緒中執行,會導致主執行緒被阻塞,從而影響軟體的使用。下面就來學習下 Android 多執行緒程式設計。

10.2.1 執行緒的基本用法

Android 多執行緒程式設計並不比 Java 多執行緒程式設計特殊,基本都是使用相同的語法。

繼承 Thread 類
新建一個類繼承自 Thread,然後重寫父類的 run() 方法:

    class MyThread extends Thread{
        @Override
        public void run() {
            // 處理具體的邏輯
        }
    }

    // 啟動執行緒,run()方法中的程式碼就會在子執行緒中運行了
    new MyThread().run(); 

實現 Runnable 介面
新建一個類實現 Runnable 介面,啟動再 new Thread():

   class MyThread2 implements Runnable{
        @Override
        public void run() {
            // 處理具體的邏輯
        }
    }

    // 啟動執行緒
    MyThread2 myThread2 = new MyThread2();
    new Thread(myThread2).start();

當然也可用匿名類方式實現 Runnable 介面:

    // 匿名類方式實現
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 處理具體的邏輯
        }
    }).start();

 

10.2.2 在子執行緒中更新UI

Android 的 UI 是執行緒不安全的,若想要更新應用程式的 UI 元素,必須在主執行緒中進行,否則會出現異常。

這應該在之前的系列文章中提到過。

一是:使用Handler 

public class UpdateUITestActivity extends AppCompatActivity implements View.OnClickListener {

    private Button btn_change_text;
    private TextView tv_text;
    
    // 定義一個整型常量用於表示更新TextView這個動作
    public static final int UPDATE_TEXT = 1;
    
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case  UPDATE_TEXT:
                    // 在這裡可以進行 UI 操作
                    tv_text.setText("你好世界");
                    break;
                
                 default:
                     break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_update_uitest);

        tv_text = (TextView) findViewById(R.id.tv_text);
        btn_change_text = (Button) findViewById(R.id.btn_change_text);
        btn_change_text.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
         switch (v.getId()){
             case R.id.btn_change_text:
                 new Thread(new Runnable() {
                     @Override
                     public void run() {
                         // 建立Message物件,並將它的what欄位指定為UPDATE_TEXT
                         Message message = new Message();
                         message.what = UPDATE_TEXT;
                         handler.sendMessage(message);//將Message物件傳送出去
                     }
                 }).start();
                 break;

             default:
                 break;
         }
    }
}

二是:使用 runOnUiThread

  new Thread(new Runnable() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText();
                    }
                });
            }
        }).start();

10.2.3 解析非同步訊息處理機制

Android 中的一部訊息處理主要由4個部份組成:Message、Handler、MessageQueue和Looper。

Message:包含描述和任意資料物件的訊息,用於傳送給Handler。

Handler:主要是用於傳送和處理訊息的。傳送訊息一般用它的 sendMessage() 方法,傳送的訊息經過一系列地輾轉處理後,最終傳遞到它的 handleMessage() 方法中。

MessageQueue:顧名思義,訊息佇列。內部儲存著一組訊息。對外提供了插入和刪除的操作。MessageQueue內部是以單鏈表的資料結構來儲存訊息列表的。

Looper:主要用於給一個執行緒輪詢訊息的。執行緒預設沒有Looper,在建立Handler物件前,我們需要為執行緒建立Looper。

使用Looper.prepare()方法建立Looper,使用Looper.loop()方法執行訊息佇列。

更多內容 請看 原始碼分析(面試常客):

https://blog.csdn.net/lfdfhl/article/details/53332936

10.2.4 使用AsyncTask

Andorid提供的工具,實現原理也是基於非同步訊息處理機制的。

用到時只需要 

new DownloadTask().execute();

3個泛型引數:

  • Params
    在執行 AsyncTask 時傳入的引數,用於後臺任務中使用

  • Progress
    後臺任務執行時,若需在介面顯示當前進度,則使用這裡指定的泛型作為進度單位

  • Result
    當任務執行完畢後,若需對結果進行返回,則使用這裡指定的泛型作為返回值型別

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {

        //在後臺任務執行前呼叫,用於一些介面上的初始化操作
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        //在子執行緒中執行,在這處理所有耗時操作
        //注意:不可進行 UI 操作,若需要可呼叫 publishProgress(Progress...)方法來完成
        @Override
        protected Boolean doInBackground(Void... voids) {
            while (true) {
                int downloadPercent=12;
                publishProgress(downloadPercent);
                if (downloadPercent >= 100) {
                    break;
                }
            }


            return null;
        }

        //當後臺任務中呼叫了 publishProgress(Progress...)方法後呼叫
        //返回的資料會作為引數傳遞到此方法中,可利用返回的資料進行一些 UI 操作
        @Override
        protected void onProgressUpdate(Integer... values) {
            Toast.makeText(OkHttpActivity.this, values[0]+"%", Toast.LENGTH_SHORT).show();


        }
        //當後臺任務執行完畢並通過 return 語句進行返回時呼叫
        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
        }
    }

10.3 服務的基本用法

服務(Service)是 Android 中實現程式後臺執行的解決方案,它非常適合去執行那些不需要和使用者互動而且還要求長期執行的任務。

服務並不是執行在一個獨立的程序當中的,而是依賴於建立服務時所在的應用程式程序。

服務並不會自動開啟執行緒,所有程式碼預設執行在主執行緒中。

10.3.1 定義一個服務

File -> New -> Service  -> Service 這樣 將會在AndroidManifest.xml 中自動新增 Service

package com.dak.administrator.firstcode.service;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import com.dak.administrator.firstcode.R;

/**
 * Created by Administrator on 2018/11/1.
 */

public class MyService extends Service {
    private final String TAG = this.getClass().getName();

   /**
     * 在服務建立時呼叫
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("------MyService------", "onCreate: ");
    }

    /**
     * 在每次服務啟動時呼叫
     * 若服務一旦啟動就立刻執行某個動作,可以將邏輯寫在此方法中
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("------MyService------", "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 在服務銷燬時呼叫,回收不再使用的資源
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("------MyService------", "onDestroy: ");
    }

}

AndroidManifest.xml:

 <service
       android:name=".service.MyService"
       android:enabled="true" />

10.3.2 啟動和停止服務

啟動和停止服務的方法主要是藉助 Intent 來實現的。下面就在專案中嘗試去啟動和停止服務。

在佈局中新增兩個按鈕,分別用於啟動和停止服務:

    <Button
        android:id="@+id/btn_start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="StartButton" />

    <Button
        android:id="@+id/btn_stop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="StopButton" />

Activity:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_service);

        Button btn_start= (Button) findViewById(R.id.start_service);
        Button btn_stop= (Button) findViewById(R.id.stop_service);
        btn_start.setOnClickListener(this);
        btn_stop.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_start:
                Intent startIntent = new Intent(this,MyService.class);
                startService(startIntent);// 啟動服務
                break;
            case R.id.btn_stop:
                Intent stopIntent = new Intent(this,MyService.class);
                stopService(stopIntent);// 停止服務
                break;
            default:
                break;
        }
    }

注意:onCreate() 在服務第一次建立時呼叫,onStartCommand() 在每次啟動服務時都會呼叫,上面第一次點選啟動服務時兩個方法都會執行,之後再點選啟動服務按鈕就只有 onStartCommant() 方法執行了。

10.3.3 活動和服務進行通訊

藉助OnBind() 方法,比如說,目前MyService中需要提供一個下載功能

 @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind(Intent intent)");

        return mBinder;
    }

    private DownloadBinder mBinder = new DownloadBinder();

    class DownloadBinder extends Binder{
        void startDownload(){
            Log.e(TAG, "startDownload: executed");

        }

         int getProgress(){
            Log.e(TAG, "getProgress: executed");
            return 0;
        }
    }

然後修改我們的Activity:

package com.dak.administrator.firstcode.service;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

import com.dak.administrator.firstcode.R;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class BackActivity extends AppCompatActivity {


    @BindView(R.id.btn_start)
    Button btnStart;
    @BindView(R.id.btn_stop)
    Button btnStop;
    @BindView(R.id.bind_service)
    Button bindService;
    @BindView(R.id.unbind_service)
    Button unbindService;
    @BindView(R.id.start_intentService)
    Button startIntentService;
    private MyService.DownloadBinder downloadBinder;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            downloadBinder = (MyService.DownloadBinder) iBinder;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_back);
        ButterKnife.bind(this);
    }


    @OnClick({R.id.btn_start, R.id.btn_stop,R.id.bind_service, R.id.unbind_service,R.id.start_intentService})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_start:
                startService(new Intent(BackActivity.this, MyService.class));
                break;
            case R.id.btn_stop:
                stopService(new Intent(BackActivity.this, MyService.class));
                break;
            case R.id.bind_service:
                bindService(new Intent(BackActivity.this, MyService.class),connection,BIND_AUTO_CREATE);
                break;
            case R.id.unbind_service:
                unbindService(connection);
                break;
            case R.id.start_intentService:
                startService(new Intent(BackActivity.this, MyIntentService.class));
                break;
        }
    }

}

10.4 服務的宣告週期

具體如下圖所示:

注意: 當我們對一個服務既呼叫了 startService() 方法,又呼叫了 bindService() 方法時,要同時呼叫 stopService() 和 unbindService() 方法,onDestroy() 方法才會執行。

10.5 服務的更多技巧

10.5.1 使用前臺服務

服務幾乎都是後臺執行的,但是,當系統出現記憶體不足的情況是,就有可能會回收掉正在執行的後臺服務,這時,就可以考慮使用前臺服務。它和普通服務的區別就是,它會一直有一個正在執行的圖示在系統的狀態列顯示。

在MyServiece的onCreate中 建立:

  @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate()");

        Intent intent = new Intent(this,BackActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new NotificationCompat.Builder(this)
                .setContentTitle("This is content title")
                .setContentText("This is content text")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                .setContentIntent(pi)
                .build();
        startForeground(1, notification);

    }

10.5.2 使用IntentService

服務中的程式碼都是預設在主執行緒中的,如果直接在服務裡面處理一些耗時的邏輯,很容易出現ANR,這時候就要用到多執行緒技術了。

public class MyService extends Service {
    . . .

   /**
     * 在每次服務啟動時呼叫
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 處理具體的邏輯        
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }
}

服務開啟後會一直處於執行狀態,必須呼叫 stopService() 或者 stopSelf() 才能停止服務,所以要實現一個服務在執行完畢後自動停止,可以這樣寫:

 @Override
 public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 處理具體的邏輯        
                stopSelf();
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
 }

當然,為了可以簡單地建立一個非同步地、會自動停止地服務,Android 專門提供了一個 IntentService 類。

一個非同步,會自動停止的服務。

package com.dak.administrator.firstcode.service;

import android.app.IntentService;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.util.Log;

import static android.content.ContentValues.TAG;

/**
 * Created by Administrator on 2018/11/2.
 */

public class MyIntentService extends IntentService {
    //非同步,會自動停止的服務

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        //列印當前執行緒id
        Log.e(TAG, "onHandleIntent: " + Thread.currentThread());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }


}

 

10.6 服務的最佳實戰——完整版的下載例項

在這裡我們要實現一個下載功能。

首先我們實現建立一個介面,用於對下載過程中的各種回撥:

package com.dak.administrator.firstcode.service_best_practice;

/**
 * Created by Administrator on 2018/11/2.
 */

public interface DownloadListener {
    void progerss(int progress);

    void onSuccess();

    void onFailed();

    void onPaused();

    void onCanceled();
}

之後建立編寫我們的下載功能

package com.dak.administrator.firstcode.service_best_practice;

import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 * Created by Administrator on 2018/11/2.
 */

public class DownloadTask extends AsyncTask<String, Integer, Integer> {

    private static final int TYPE_SUCCESS = 0;
    private static final int TYPE_FAILED = 1;
    private static final int TYPE_PAUSED = 2;
    private static final int TYPE_CANCELED = 3;

    private DownloadListener listener;

    private boolean isCanceled = false;
    private boolean isPause = false;
    private int lastProgress;

    public DownloadTask(DownloadListener downloadListener) {
        listener = downloadListener;
    }

    @Override
    protected Integer doInBackground(String... params) {
        InputStream is = null;
        RandomAccessFile savedFile = null;
        File file = null;

        try {
            long downloadLength = 0;//記錄已經下載過檔案的長度
            String downloadUrl = params[0];
            String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));//解析 url 檔名
            String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();//sd卡 DownLoad 目錄
            file = new File(directory + fileName);
            if (file.exists()) {
                //如果已經存在讀取已下載的位元組數
                downloadLength = file.length();
            }
            long contentLength = getContentLength(downloadUrl);//獲取檔案總長度
            if (contentLength == 0) {
                return TYPE_FAILED;
            } else if (contentLength == downloadLength) {
                //已下載位元組和檔案總位元組相等,說明已經下載完成了
                return TYPE_SUCCESS;
            }
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    //斷點下載,指定從哪個位元組開始下載
                    //告訴伺服器 我想要從哪個位元組開始下載
                    .addHeader("RANGE", "bytes=" + downloadLength + "-")
                    .url(downloadUrl)
                    .build();
            Response response = client.newCall(request).execute();

            if (response != null) {
                is = response.body().byteStream();//使用檔案流方式讀取
                savedFile = new RandomAccessFile(file, "rw");
                savedFile.seek(downloadLength);//跳過已經下載的位元組
                byte[] b = new byte[1024];
                int total = 0;
                int len;
                while ((len = is.read(b)) != -1) {
                    if (isCanceled) {
                        return TYPE_CANCELED;
                    } else if (isPause) {
                        return TYPE_PAUSED;
                    } else {
                        total += len;
                        savedFile.write(b, 0, len);

                        int progress = (int) ((total + downloadLength) * 100 / contentLength);
                        publishProgress(progress);
                    }
                }
                response.body().close();
                return TYPE_SUCCESS;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
                if (savedFile != null) {
                    savedFile.close();
                }
                if (isCanceled && file != null) {
                    file.delete();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


        return TYPE_FAILED;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        int progress = values[0];
        //和上次下載進度 比 有變化呼叫介面
        if (progress > lastProgress) {
            listener.progerss(progress);
            lastProgress = progress;
        }
    }

    @Override
    protected void onPostExecute(Integer integer) {
        switch (integer) {
            case TYPE_SUCCESS:
                listener.onSuccess();
                break;
            case TYPE_FAILED:
                listener.onFailed();
                break;
            case TYPE_PAUSED:
                listener.onPaused();
                break;
            case TYPE_CANCELED:
                listener.onCanceled();
                break;
        }
    }

    void pauseDownload() {
        isPause = true;
    }

    void cancelDownload() {
        isCanceled = true;
    }

    private long getContentLength(String downloadUrl) throws IOException {
        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder().url(downloadUrl).build();
        Response response = client.newCall(request).execute();

        if (response != null && response.isSuccessful()) {
            long conentLength = response.body().contentLength();
            response.body().close();
            return conentLength;
        }
        return 0;
    }


}

之後建立後臺的服務

package com.dak.administrator.firstcode.service_best_practice;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.widget.Toast;

import com.dak.administrator.firstcode.R;

import java.io.File;

public class DownloadService extends Service {

    private DownloadTask downloadTask;

    private String downloadUrl;

    private DownloadListener listener = new DownloadListener() {
        @Override
        public void progerss(int progress) {
            getNotificationManager().notify(1, getNotification("Downloading...", progress));
        }

        @Override
        public void onSuccess() {
            downloadTask = null;
            stopForeground(true);
            getNotificationManager().notify(1, getNotification("Download Sucess", -1));
            Toast.makeText(DownloadService.this, "DownloadSucess", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onFailed() {
            downloadTask = null;
            //下載失敗時 將前臺服務通知關閉,並建立一個下載失敗的通知
            stopForeground(true);
            getNotificationManager().notify(1, getNotification("Download Failed", -1));
            Toast.makeText(DownloadService.this, "Download Failed", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onPaused() {
            downloadTask = null;
            Toast.makeText(DownloadService.this, "Download onPaused", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onCanceled() {
            downloadTask = null;
            stopForeground(true);
            Toast.makeText(DownloadService.this, "Download onCanceled", Toast.LENGTH_SHORT).show();
        }
    };

    private Notification getNotification(String title, int progress) {
        Intent intent = new Intent(this, DownActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        builder.setSmallIcon(R.mipmap.ic_launcher);
        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
        builder.setContentIntent(pi);
        builder.setContentTitle(title);
        if (progress >= 0) {
            //當progress大於或等於0時 才需顯示下載進度
            builder.setContentText(progress + "%");
            builder.setProgress(100, progress, false);
        }
        return builder.build();
    }

    private NotificationManager getNotificationManager(){
        return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    }




    public DownloadService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return new DownloadBinder();
    }

    class DownloadBinder extends Binder{

        void startDownload(String url) {
            if (downloadTask == null) {
                downloadUrl = url;
                downloadTask = new DownloadTask(listener);
                downloadTask.execute(downloadUrl);
                startForeground(1, getNotification("Downloading...", 0));
                Toast.makeText(DownloadService.this, "Downloading", Toast.LENGTH_SHORT).show();
            }
        }

        void pauseDownload(){
            if (downloadTask != null) {
                downloadTask.pauseDownload();
            }
        }

         void cancelDownload(){
            if (downloadTask != null) {
                downloadTask.cancelDownload();
            }

            if (downloadUrl != null) {
                //取消下載時 需將檔案刪除,並將通知關閉
                String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
                String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
                File file = new File(directory + fileName);
                if (file.exists()) {
                    file.delete();
                }
                getNotificationManager().cancel(1);
                stopForeground(true);
                Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

再然後建立 我們的頁面,用於如何呼叫:

package com.dak.administrator.firstcode.service_best_practice;

import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.dak.administrator.firstcode.R;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class DownActivity extends AppCompatActivity {

    @BindView(R.id.start)
    Button start;
    @BindView(R.id.pause)
    Button pause;
    @BindView(R.id.cancel)
    Button cancel;

    private DownloadService.DownloadBinder mBinder;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mBinder = (DownloadService.DownloadBinder) iBinder;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_down);
        ButterKnife.bind(this);

        Intent intent = new Intent(this, DownloadService.class);
        startService(intent);
        bindService(intent, connection, BIND_AUTO_CREATE);
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
                PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        }
    }

    @OnClick({R.id.start, R.id.pause, R.id.cancel})
    public void onClick(View view) {
        if (mBinder == null) {
            return;
        }

        switch (view.getId()) {
            case R.id.start:
                String url = "https://raw.githubusercontent.com/guolindev/eclipse/" +
                        "master/eclipse-inst-win64.exe";
                mBinder.startDownload(url);
                break;
            case R.id.pause:
                mBinder.pauseDownload();
                break;
            case R.id.cancel:
                mBinder.cancelDownload();
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "拒絕許可權將無法使用程式", Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="start Button" />
    <Button
        android:id="@+id/pause"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="pause Button" />
    <Button
        android:id="@+id/cancel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="cancel Button" />

</LinearLayout>

對於RandomAccessFile的瞭解:https://blog.csdn.net/akon_vm/article/details/7429245

10.7 小結與點評

郭霖總結:

在本章中,我們學習了很多與服務相關的重要知識點,包括Android多執行緒程式設計、服務的基本用法、服務的生命週期、前臺服務和IntentService等。這些內容已經覆蓋了大部分你在日常開發中可能用到的服務技術,再加上最佳實踐部分學習的下載示例程式,相信以後不管遇到什麼樣的服務難題,你都能從容解決。

另外,本章同樣是有里程碑式的紀念意義的,因為我們已經將Android中的四大元件全部學完,並且本書的內容也學習-大半了。 對於你來說,現在你已經脫離了Android初級開發者的身份,並應該具備了獨立完成很多功能的能力。
那麼後面我們應該再接再厲,爭取進一步提升 自身的能力,所以現在還不是放鬆的時候,下一章中我們準備去學習一下 Android特色開發的相關內容。

我的總結:

服務這塊內容是我一直沒有搞明白的,但是看了這篇文章後,有了更多的認識。但是並不是很滿足,因為這篇文章還是比較基礎內容,或許在以後我會了解的更多更多,當然對於郭神還是要給一個大大的讚的~