第一行代碼----服務的最佳實踐(體會,問題,解決)


package com.example.zhudashi.servicebestpractice;

import android.Manifest;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.storage.StorageManager;
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.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.lang.reflect.Method;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private DownloadService.DownloadBinder downloadBinder;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder service) {
            //活動和服務綁定的時候執行
            downloadBinder = (DownloadService.DownloadBinder) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            //活動和服務斷開連接時執行

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startDownload = (Button) findViewById(R.id.start_download);
        Button pauseDownload = (Button) findViewById(R.id.pause_download);
        Button cancelDownload = (Button) findViewById(R.id.cancel_download);
        startDownload.setOnClickListener(this);
        pauseDownload.setOnClickListener(this);
        cancelDownload.setOnClickListener(this);

//        Method mMethodGetPaths = null;
//        String[] paths = null;
//        //通過調用類的實例mStorageManager的getClass()獲取StorageManager類對應的Class對象
//        //getMethod("getVolumePaths")返回StorageManager類對應的Class對象的getVolumePaths方法,這里不帶參數
//        Context context = MainActivity.this;
//        StorageManager mStorageManager = (StorageManager)context
//                .getSystemService(context.STORAGE_SERVICE);//storage
//        try {
//            mMethodGetPaths = mStorageManager.getClass().getMethod("getVolumePaths");
//            paths = (String[]) mMethodGetPaths.invoke(mStorageManager);
//        } catch (Exception e) {
//            // TODO Auto-generated catch block
//            e.printStackTrace();
//        }
//        System.out.println("zhxing33 path = "+paths[0]);
//        System.out.println("zhxing33 path = "+paths[1]);

        Intent intent = new Intent(this,DownloadService.class);
        startService(intent);//連接服務
        bindService(intent,connection,BIND_AUTO_CREATE);
        //綁定服務,通過connection進行主線程和子線程之間的交互
        if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!=
                PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);

        }
    }

    @Override
    public void onClick(View view) {
        if(downloadBinder == null){//未綁定的情況
            return ;
        }
        switch (view.getId()){
            case R.id.start_download:
                String url = "https://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";
                downloadBinder.startDownload(url);
                break;
            case R.id.pause_download:
                downloadBinder.pauseDownload();
                break;
            case R.id.cancel_download:
                downloadBinder.cancelDownload();
                break;
                default:
                    break;
        }

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode){
            case 1:
                if(grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED){
                    Toast.makeText(this,"拒絕權限無法使用程序",Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            default:

        }
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }
}

1.onCreate()方法中值得注意的是,服務的啟動和綁定,

Intent intent = new Intent(this,DownloadService.class); startService(intent);//啟動服務
啟動服務只需要一個intent來展現MainActivity和服務之間的關系即可

bindService(intent,connection,BIND_AUTO_CREATE);
綁定服務,使用參數connection對象中的service對象來達到在活動中操作服務中方法的目的。

這里有一個疑問,為什么需要啟動服務,直接綁定服務不就行了嗎?書上說是為了讓DownloadService一直在后台運行,我認為是不成立的,因為后面又把該活動綁定了,所以DownloadService並不會一直在后台運行,他會隨着活動的結束而結束,
所以書上說啟動和綁定都需要就讓人很費解了。

注:
1.通過startservice開啟的服務.一旦服務開啟, 這個服務和開啟他的調用者之間就沒有任何的關系了. 
調用者不可以訪問 service里面的方法. 調用者如果被系統回收了或者調用了ondestroy方法, service還會繼續存在  
2.通過bindService開啟的服務,服務開啟之后,調用者和服務之間 還存在着聯系 , 
一旦調用者掛掉了.service也會跟着掛掉 .


2.
private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder service) { //活動和服務綁定的時候執行 downloadBinder = (DownloadService.DownloadBinder) service; } @Override public void onServiceDisconnected(ComponentName componentName) { //活動和服務斷開連接時執行  } };
運用匿名類,繼承ServiceConnection類,重寫的onServiceConnected方法在活動和服務綁定的時候執行,其中把service賦值為downloadBinder是為了能在活動中通過downloadBinder調用服務中的方法(onClick()中就有調用)。

package com.example.zhudashi.servicebestpractice;

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.util.Log;
import android.widget.Toast;

import java.io.File;

public class DownloadService extends Service {

    private DownloadTask downloadTask;
    private String downloadUrl;

    //匿名類,繼承downloadlistener接口,UI操作
    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,"pause",Toast.LENGTH_SHORT).show();

        }

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

        }
    };

    private DownloadBinder mBinder = new DownloadBinder();//用來連接主線程和子線程的聯結器,繼承至Binder。


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


    class DownloadBinder extends Binder {

        public void startDownload(String url){//啟動下載
            if(downloadTask == null){
                downloadUrl = url;
                downloadTask = new DownloadTask(listener);//開啟異步類(下載和ui更新在里面處理)
                downloadTask.execute(downloadUrl);//用於啟動該異步類
                startForeground(1,getNotification("Downloading...",0));//前台服務開啟,1是通知id,類似notify()方法的第一個參數
                //第二個參數是構建出來的Notification對象。
                //調用該方法會讓該service變成一個前台服務。
                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();
//                    String directory = System.getenv("EXTERNAL_STORAGE");
                    File file = new File(directory+fileName);
                    System.out.println("zhixing2 path = "+directory+fileName);
                    if(file.exists()){
                        file.delete();
                        System.out.println("zhixng4");
                   }
                getNotificationManager().cancel(1);//取消通知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){
        //用來構建一個通知,title是該通知的id。
        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){
            builder.setContentText(progress+"%");
            builder.setProgress(100,progress,false);
        }
        return builder.build();//生成一個通知
    }
}

1.

 @Override
    public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. return mBinder; }
繼承了service就需要重寫onBind()方法,onBind()方法返回的對象mBinder就是用來連接活動和服務的IBinder類型的對象,返回到了MainActivity活動中,在活動和服務綁定時執行onServiceConnected()方法賦值給了downloadBinder以保證
可以在MainActivity活動中操作該服務。、


2.這里又用到一個匿名類,實現DownloadListener接口,里面有五個方法,分別實現以下功能:
void onProgress(int progress);//下載通知
void onSuccess();//下載成功通知和提示
void onFailed();//下載失敗通知和提示
void onPaused();//下載暫停提示
void onCanceled();//下載取消的提示,關閉前台通知。

3.
public void startDownload(String url){//啟動下載
    if(downloadTask == null){
downloadUrl = url;
downloadTask = new DownloadTask(listener);//開啟異步類(下載和ui更新在里面處理)
downloadTask.execute(downloadUrl);//用於啟動該異步類
startForeground(1,getNotification("Downloading...",0));//前台服務開啟,1是通知id,類似notify()方法的第一個參數
//第二個參數是構建出來的Notification對象。
//調用該方法會讓該service變成一個前台服務。
Toast.makeText(DownloadService.this,"Downloading...",Toast.LENGTH_SHORT).show();
}
}
downloadTask.execute(downloadUrl);
這條語句可以理解為開啟了子線程,里面有一些耗時的操作以及一些UI操作,該類具體怎么運作,可以看下嘛的代碼




package com.example.zhudashi.servicebestpractice;

import android.os.AsyncTask;
import android.os.Environment;
import android.os.storage.StorageManager;

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

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

/**
 * Created by zhudashi on 2018/2/14.
 */

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[] paths = null;
            //通過調用類的實例mStorageManager的getClass()獲取StorageManager類對應的Class對象
            //getMethod("getVolumePaths")返回StorageManager類對應的Class對象的getVolumePaths方法,這里不帶參數

            String directory = Environment.getExternalStoragePublicDirectory(
                    Environment.DIRECTORY_DOWNLOADS).getPath();
//            String directory = System.getenv("EXTERNAL_STORAGE");
                file = new File(directory+filename);
                System.out.println("zhixing file = "+file.getPath());
                if(file.exists()){
                downloadedLength = file.length();
            }
            long contentLength = getContentLength(downloadUrl);
                System.out.println("zhixing3 length="+downloadedLength);
                //獲取的是要下載文件的總長度
            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();
                    //此時response.body().contentLength()獲取的是還需要下載的字節長度。
                    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) {
        //后台任務調用publishProgress()方法后調用該方法,主線程中運行,進行UI操作
        int progress = values[0];
        if(progress > lastProgress){//這次的下載進度和上次的下載進去進行比較
            listener.onProgress(progress);
            lastProgress = progress;
        }
    }


    @Override
    protected void onPostExecute(Integer status) {
        //當后台任務執行完畢通過return語句進行返回的時侯,這個方法會被調用,返回數據會傳到該
        //方法中,可以利用返回數據進行一些UI操作。
        //根據參數傳入的下載狀態進行回調。
        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;
    }


}

1.該類繼承AsyncTask異步類,是為了方便我們在子線程中對UI進行操作,其具體用法書上347頁有詳細的解答。

簡單來說我們把耗時的操作(下載操作)放在doInBackground()(可以簡單的認為在執行完downloadTask.execute(downloadUrl);后執行該函數中的語句)中實現,該函數中的代碼全部在子線程中運行。

onProgressUpdate()方法(在執行publishprogress()方法后執行該方法)中實現一些UI操作,因為其代碼在主線程中運行。

onPostExecute()用來接收doInBackground()中的返回數據,通過不同的返回數據執行不同的UI操作。doInBackground()返回數據時,會在返回數據前先執行finally|{}中的語句。

 

 

2.

  String filename = downloadUrl.substring(downloadUrl.lastIndexOf("/"));  

  //讀取最后一個/后面的字符串,即下載文件的名字

  String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();

 //得到內置sd卡的路徑(這里強調內置sd卡)/storage/emulated/0/Download/。

file = new File(directory+filename);


http://blog.csdn.net/qq_34471736/article/details/54964385
http://blog.csdn.net/tobacco5648/article/details/70314621
//獲取內置和外置sd卡路徑的方法

我獲取的外置sd卡路徑是: /storage/0403-0201 不同的sd卡可能不一樣,不知道為什么就是存不進去,測試了很多遍。





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM