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卡可能不一样,不知道为什么就是存不进去,测试了很多遍。