Android使用Service从服务器上下载一个安装包的全过程

前言:在这里完成了一个完整的从服务器上下载数据的实例!

一、我使用的时OkHttp进行网络通信,所以首先在gradle的dependencies中添加如下依赖:

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

二、定义一个进行回调的接口,因为在下载过程中我们会遇到下载成功、失败、暂停、取消等问题,所以先写好这个接口,到时候可以直接进行调用:

/**
 * 定义一个回调接口,用于对下载过程中的各种状态进行监听和回调
 */

public interface DownloadListener {
    void onProgress(int progress);//通知当前的下载进度
    void onSuccess();//通知下载成功
    void onFailed();//。。。。失败
    void onPaused();//。。。。。暂停
    void onCanceled();//。。。。取消
}

三、接口写好后,我们就可以编写下载功能的代码了,代码中有详细的注释!新建一个DownloadTask继承自AsyncTask,

/**
 * 下载任务,第一个泛型参数String是指传一个字符串参数给后台,也就是Url;
 * 第二个泛型参数Integer,是指使用整型数据作为进度显示单位,也就是显示下载进度条
 * 第三个泛型参数是Integer,表示使用整型数据反馈结果
 */
public class DownloadTask extends AsyncTask<String,Integer,Integer> {
    //4个整型常量用于表示下载的状态
    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;

    //在构造函数中传入一个刚刚定义的DownLoadListener参数,待会用这个参数进行下载状态的回调
    public DownloadTask(DownloadListener listener){
        this.listener=listener;
    }

    //doInBackground方法用于在后台执行具体的下载逻辑
    @Override
    protected Integer doInBackground(String... params) {
        InputStream is=null;
        RandomAccessFile savedFile=null;
        File file=null;
        try {
            long downloadedLength=0;//记录已下载的文件的长度
            //获取到下载的URL地址
            String downloadUrl=params[0];//下载地址
            //根据URL解析出下载的文件名
            String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/"));
            //将文件下载到Environment.DIRECTORY_DOWNLOADS目录下,也就是SD卡的Download目录
            String directory= Environment.getExternalStoragePublicDirectory(Environment
                    .DIRECTORY_DOWNLOADS).getPath();
            file=new File(directory+fileName);
            //判断目录中是否已经存在要下载的文件
              if (file.exists()){
                  //如果已经存在,则读取已下载的字节数,这样就可以在后面使用断点续传的功能
                downloadedLength=file.length();
              }

            //使用get。。方法来获取待下载文件的总长度
            long contentLength=getContentLength(downloadUrl);
            //若文件长度=0,则文件有问题,直接返回下载失败
            if (contentLength==0){
                return TYPE_FAILED;
                //若文件长度=已经下载的文件的长度
            }else if (contentLength==downloadedLength){
                //已下载的字节和文件总字节相同,则下载成功
                return TYPE_SUCCESS;
            }

            //建立网络链接
            OkHttpClient client=new OkHttpClient();
            //要发起一个http请求,首先创建一个Request对象
            Request request=new Request.Builder()
                    //断点下载,这里添加了一个header,指定从那个字节开始下载,因为已经下载过的就不要下载了
            .addHeader("RANGE","bytes="+downloadedLength+"-")
                    .url(downloadUrl)
                    .build();
           //调用Okhttp的newCall方法返回根据request请求,服务器返回的数据
            Response response=client.newCall(request).execute();

            /**
             * 采用java流的方式,不断从网络上读取数据,不断写入到本地
             * 一直到文件全部下载完为止,在整个过程中,我们还要判断用户有没有
             * 触发暂停、取消等操作,有的话通过TYPE的响应方式返回,
             * 没有的话则实时计算当前的下载进度
             */
            if (response!=null){
                //用inputstream的实例得到服务器返回的数据的详细内容
                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=total+len;
                        savedFile.write(b,0,len);
                        //计算下载的百分比
                        int progress= (int) ((total+downloadedLength )*100/contentLength);
                        //调用 publishProgress进行通知
                        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){
            //如果下一次的下载进度大于上一次的,则通过progress通知一下下载进度
            listener.onProgress(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;
           default:
               break;
       }
    }

    public void pauseDownload(){
        isPaused=true;
    }

    public void cancelDownload(){
        isCanceled=true;
    }

    private long getContentLength(String downloadUrl) throws IOException{
        //建立网络链接
        OkHttpClient client=new OkHttpClient();
        //要发起一个http请求,首先创建一个Request对象
        Request request=new Request.Builder()
                .url(downloadUrl)
                .build();
        //调用Okhttp的newCall方法返回根据request请求,服务器返回的数据
        Response response=client.newCall(request).execute();
        if (response!=null&&response.isSuccessful()){
            //用contentLength接收服务器返回的详细数据
            long contentLength=response.body().contentLength();
            response.body().close();
            return contentLength;
        }
        return 0;
    }
}

四、这样我们就完成了具体的下载功能,为了保证下载能一直在后台进行,我们还需要创建一个下载服务!代码如下:

/**
 * 为了保证下载任务可以一直在后台运行,我们创建一个下载的服务
 */
public class DownloadService extends Service {

    private DownloadTask downloadTask;

    private String downloadUrl;

    //创建 DownloadListener的匿名类实例,并在类中实现了5个方法
     private DownloadListener listener=new DownloadListener() {
         @Override
         public void onProgress(int progress) {
             //调用getNotification方法构建了一个用于显示下载进度的通知
             //然后调用NotificationManager()的notify方法去触发这个通知
             //这样就可以在状态栏看到下载进度了
             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();

         }
     };


    /**
     * 下面的内容是为了让服务(service)与活动(Activity)进行通信
     */
    private DownloadBinder mBinder=new DownloadBinder();

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

    //创建DownloadBinder
    class DownloadBinder extends Binder{
        //开始下载
        public void startDownload(String url){
            if (downloadTask==null){
                downloadUrl=url;
                //创建一个downloadTask实例,把DownloadListener作为参数传入
                downloadTask=new DownloadTask(listener);
                //调用execute方法开始下载,方法中传入下载的url
                downloadTask.execute(downloadUrl);
                //前台显示下载
                startForeground(1,getNotification("Downloading...",0));
                Toast.makeText(DownloadService.this,"Downloading...",Toast.LENGTH_SHORT).show();
            }
        }

        //暂停下载
        public void pauseDownload(){
            //暂停下载,直接调用DownloadTask的pauseDownload方法
            if (downloadTask!=null){
                downloadTask.pauseDownload();
            }

        }
        //取消下载
        public void cancelDownload(){
            if (downloadTask!=null){
                downloadTask.cancelDownload();
            }
            if (downloadUrl!=null){
                //取消下载时需要将文件删除,并将通知关闭
                //根据URL解析出下载的文件名
                String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/"));
                //将文件下载到Environment.DIRECTORY_DOWNLOADS目录下,也就是SD卡的Download目录
                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();
    }
}

五、现在下载的服务也已经写好了,后端的工作基本完成了,接下来写前端界面如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/start_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="开始下载"/>


    <Button
        android:id="@+id/pause_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="暂停下载"/>

    <Button
        android:id="@+id/cancel_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="取消下载"/>




</LinearLayout>

六、界面写了四个按钮,非常清晰,下面开始写MainActivity的代码如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private DownloadService.DownloadBinder downloadBinder;

    //创建了一个ServiceConnection匿名类
    private ServiceConnection connection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //获取到DownloadBinder的实例,用这个实例在活动中调用服务提供的各种方法
            downloadBinder= (DownloadService.DownloadBinder) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @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 canelDownload= (Button) findViewById(R.id.cancel_download);

        startDownload.setOnClickListener(this);
        pauseDownload.setOnClickListener(this);
        canelDownload.setOnClickListener(this);

        //开启下载后台服务,启动服务可以让服务在后台一直运行
        Intent intent=new Intent(this,DownloadService.class);
        startService(intent);
        bindService(intent,connection,BIND_AUTO_CREATE);//绑定服务,可以让服务与活动进行通信

        //WRITE_EXTERNAL_STORAGE的权限申请,文件是要下载到SD卡的Download目录下的,没有权限,无法工作
        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 v) {
        if (downloadBinder==null){
            return;
        }
        switch (v.getId()){
            case R.id.start_download:
                String url="http://123.206.81.238:8080/xiaomatext/test.apk";
                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,  String[] permissions, 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:
                break;
        }
    }

    //一定要记得写下代码对服务进行接触绑定,不然可能会造成内存泄漏
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }
}

七、最后我们要在Manifest文件中加上权限如下:

<uses-permission android:name="android.permisssion.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
好了,到此,我们就完成了一个从服务器上下载一个安装包的实例了!运行一下就OK了,当然也可以下载别的东西,自己再动手尝试一下就行了。



猜你喜欢

转载自blog.csdn.net/magicmhd/article/details/80861333