Android學習筆記(8)

myxs發表於2017-04-03

服務

標籤: Android


1 服務基本概念

服務可以實現程式後臺執行,適合長期執行且不與使用者互動的任務。 服務不是執行在一個獨立的程式中,而是依賴於建立服務的應用程式程式。當它被kill掉後,依賴該程式的服務也會停止執行。 服務的程式碼執行在主執行緒中,但是執行具體任務的時候需要在服務內部建立新執行緒,否則會阻塞主執行緒

1.1 多執行緒

繼承,實現Runnable介面以及匿名類,3種方式

更新UI 子執行緒中不能直接更新UI元素,但可以利用非同步訊息處理機制,解決在子執行緒中進行UI操作

非同步訊息處理機制:

子執行緒中傳送訊息,主執行緒接收訊息並處理。

  1. Message

    Message是執行緒間傳遞的訊息,攜帶資訊,如Message的what欄位,arg1\arg2攜帶整形資料,obj攜帶Object物件

  2. Handler

    處理者,用於傳送訊息和處理訊息。 傳送訊息:

    Handler.sendMessage()
    

    處理訊息:

    handleMessage
    
  3. MessageQueue

    訊息佇列,存放通過Handler傳送的訊息,每個執行緒只有一個MessageQueue物件

  4. Looper

    管理MessageQueue的物件,每個執行緒只有一個Looper物件,呼叫Looper的loop方法後,就能從MessageQueue取出訊息當存在一條訊息時。

    public class MainActivity extends AppCompatActivity implements View.ONClickListener {
    public static final int Update_Text = 1;
    private TextView text;
    
    
    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case Update_Text:
                    text.setText("Nice to meet you");
                    break;
                default:
                    break;
            }
        }
    };
    ...
    @Override
    public void onClick(View v) {
        switch (v.getId(){
            case R.id.text:
                new Thread(new Runnable(){
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = Update_Text;
                        handler.sendMessage(message);
                    }
                }).start();
                break;
            default:
                break;
            }
        }
    }
    

主執行緒中建立一個Handler物件,重寫handleMessage方法 子執行緒建立一個Message物件,通過Handler傳送訊息 訊息進入到訊息佇列中並被取出分發回Handler的handleMessage方法中

runOnUIThread是非同步訊息處理機制的介面封裝

使用AsyncTask

AsyncTask是抽象類,需要繼承它,指定3個泛型引數,分別為

  • Params:傳入引數,用於後臺任務
  • Progress:任務進度
  • Result:返回結果的返回值型別

    class DownloadTask extends AsyncTask<void, Integer, Boolean> {
    ...
    }
    

重寫方法

  • onPreExecute()

    後臺任務開始執行前呼叫,初始化介面,如顯示一個進度條對話方塊

  • doInBackground(Params...)

    在子執行緒中執行,此方法中不能進行UI操作,如果需要更新UI元素,如反饋當前任務的執行進度,可以呼叫publicProgress(Progress...)方法

  • onProgressUpdate(Pogress...)

    當呼叫pullishProgress(Progress...)方法後,onProgressUpdate(Progress...)被呼叫。可以對UI進行操作,利用引數中攜帶的數值對介面元素進行相應的更新

  • onPostExecute(Result)

    後臺任務執行完畢後通過return語句返回,會呼叫此方法。可以對UI進行操作,如提醒任務執行結果,以及關閉進度條對話方塊等

    class DownloadTask extends AsyncTask<void, Integer, Boolean> {
    
    
    
    @Override
    protected void onPreExecute(){
        progressDialog.show();
    }
    
    
    @Override
    protected Boolean doInBackground(Void.. params) {
        try{
            while(true) {
                int downloadPercent = doDownload();
                publicProgress(downloadPercent);
                if (downloadPercent&gt;=100)
                    break;
            }
        }catch(Exception e){
            return false;
        }
        return true;
    }
    
    
    @Override
    protected void onPostExecute(Boolean result) {
        progressDialog.dismiss();
        if (result){
            Toast.makeText(context, "Succeed", Toast.LENGTH_SHORT).show();
        else
            Toast.makeText(context, "Failed", Toast.LENGTH_SHORT).show();
        }
    }
    
    }

啟動任務

new DownloadTask().execute();

1.2 服務基本用法

類似其它元件,利用AS快速新建一個服務並重寫方法

public class MyService extends Service {

    public MyService() {
    }
    @Override
    public IBinder onBind(Intent intent) {
        //
    }

    @Override
    public void onCreate() {//服務建立的時候呼叫
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {//服務啟動的時候呼叫
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {//服務銷燬時呼叫
        super.onDestroy();
    }
}

啟動和停止服務

使用Intent實現

Intent startIntent = new Intent(this, MyService.class);
startService(startintent);//啟動服務

Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent);//停止服務

當需要讓服務自己停止,需要在MyService的任一位置呼叫stopSelf方法

活動和服務進行通訊

比如在活動中可以決定何時開始下載,以及隨時檢視下載進度。通過建立一個Binder物件對下載功能進行管理

public class MyService extends Service {
    private DonwloadBinder mBinder = ne DownloadBinder();

    class DownloadBinder extends Binder {
        public void startDownload() {
            //
        }
        public int getProgress() {
            //
            return 0;
        }
    }

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

在活動中呼叫服務中的方法

public class MainActivity extends AppCompatActivity implements View.onClickListener {

    private MyService.DownlloadBinder downloadBinder;

    private ServiceConnection connectio = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }
    };
    ...
}

活動與服務繫結以及解除繫結

Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE);
...
unbindService(connection);

當活動和服務成功繫結後,呼叫onServiceConnected方法,解除繫結呼叫onServiceDisconnected方法。

1.3 服務的高階技巧

前臺服務

前臺服務與普通服務不同的是它會在系統的狀態列顯示,下拉會顯示,類似通知。

在服務中的onCreate中指定建立一個前臺服務

public class MyService extends Service {
    ...

    @Override
    public void onCreate() {
        ...
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new NotificationCompat.Builder(this)
                    .setContentTitle("title")
                    .setContentText("conent text")
                    .setWhen(System.currentTimeMills())
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                    .setConetentIntnet(pi)
                    .builde();
        startForeground(1, notification);
    }
}

IntentService 為了避免ANR,需要在服務的每個具體方法中開啟執行緒,當處理完畢時,呼叫stopSelf或者stopService停止服務。IntentService可以解決忘記開啟執行緒以及呼叫stopSelf方法,建立一個服務

public class MyIntentService extends IntnetService {
    public MyIntentService() {
        super("MIntentService");
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        //
    }
    @Override
    public void onDestroy(){
        super.onDestroy();
    }

在活動內啟動

Intent intent = new Intent(this, MyIntentService.class);
startService(intentService);

1.4 下載功能實現

OkHttp實現網路請求

compile 'com.squareup.okhttp3:okhttp:3.7.0'

定義會調介面,監聽下載過程的狀態和回撥

public interface DownloadListener {
    void onProgress(int progress);
    void onSuccess();
    void onFaild();
    void onPaused();
    void onCanceled();
}

使用AsyncTask實現下載功能

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

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


private DownloadListener listener;

private boolean isCanceled = false;

private boolean isPaused = false;

private int lastProgress;

public DownloadTask(DownloadListener listener) {
    this.listener = listener;
}

@Override
protected Integer doInBackground(String... params) {
    InputStream is = null;
    RandomAccessFile savedFile = null;
    File file = null;
    try {
        long downloadedLength = 0; // 記錄已下載的檔案長度
        String downloadUrl = params[0];
        String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
        String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
        file = new File(directory + fileName);
        if (file.exists()) {
            downloadedLength = file.length();
        }
        long contentLength = getContentLength(downloadUrl);
        if (contentLength == 0) {
            return TYPE_FAILED;
        } else if (contentLength == downloadedLength) {
            // 已下載位元組和檔案總位元組相等,說明已經下載完成了
            return TYPE_SUCCESS;
        }
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                // 斷點下載,指定從哪個位元組開始下載
                .addHeader("RANGE", "bytes=" + downloadedLength + "-")
                .url(downloadUrl)
                .build();
        Response response = client.newCall(request).execute();
        if (response != null) {
            is = response.body().byteStream();
            savedFile = new RandomAccessFile(file, "rw");
            savedFile.seek(downloadedLength); // 跳過已下載的位元組
            byte[] b = new byte[1024];
            int total = 0;
            int len;
            while ((len = is.read(b)) != -1) {
                if (isCanceled) {
                    return TYPE_CANCELED;
                } else if(isPaused) {
                    return TYPE_PAUSED;
                } else {
                    total += len;
                    savedFile.write(b, 0, len);
                    // 計算已下載的百分比
                    int progress = (int) ((total + downloadedLength) * 100 / contentLength);
                    publishProgress(progress);
                }
            }
            response.body().close();
            return TYPE_SUCCESS;
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (is != null) {
                is.close();
            }
            if (savedFile != null) {
                savedFile.close();
            }
            if (isCanceled && file != null) {
                file.delete();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    return TYPE_FAILED;
}

@Override
protected void onProgressUpdate(Integer... values) {
    int progress = values[0];
    if (progress > lastProgress) {
        listener.onProgress(progress);
        lastProgress = progress;
    }
}

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

public void pauseDownload() {
    isPaused = true;
}


public 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 contentLength = response.body().contentLength();
        response.close();
        return contentLength;
    }
    return 0;
}

}

新建一個服務用於下載

public class DownloadService extends Service {

private DownloadTask downloadTask;

private String downloadUrl;

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

    @Override
    public void onSuccess() {
        downloadTask = null;
        // 下載成功時將前臺服務通知關閉,並建立一個下載成功的通知
        stopForeground(true);
        getNotificationManager().notify(1, getNotification("Download Success", -1));
        Toast.makeText(DownloadService.this, "Download Success", 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, "Paused", Toast.LENGTH_SHORT).show();
    }

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

};

private DownloadBinder mBinder = new DownloadBinder();

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

class DownloadBinder extends Binder {

    public 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();
        }
    }

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

    public void cancelDownload() {
        if (downloadTask != null) {
            downloadTask.cancelDownload();
        } else {
            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();
            }
        }
    }

}

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

private Notification getNotification(String title, int progress) {
    Intent intent = new Intent(this, MainActivity.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();
}

}

DownloadListener匿名類例項中實現了介面方法
DownloadBinder用於讓服務和活動通訊
其它細節東西參考<<第一行程式碼>>P375

相關文章