Android四大组件Servier(下)

郭神书中的一个关于下载的小demo
学习下

1.在项目中添加okhttp
项目中添加依赖

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

接下来需要定义一个回调接口,用于对下载过程中的各种状态进行监听和回调,新建一个DownloadListence接口,代码如下所示:

/**
 * 下载过程中的回调接口
 * Created by fxr on 2017/12/20.
 */

public interface DownloadListener {
    void onProgress(int progress);
    void onSuccess();
    void onFailed();
    void onPause();
    void onCanceled();
}

可以看到,这里我们一共定义了5个回调方法,
onPregress()方法用于通知当前的下载进度;
onSuccess()方法用于通知下载成功;
onFailed()方法用于通知下载失败时间;
onPaused()方法用于通知下载暂停时间;
onCanceled()方法用于通知下载取消事件。
回调接口定义好以后,下载我们就可以开始编写下载功能了。这里我们准备使用AsyncTask来进行实现。
新建DownloadTask继承子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_PAUSE=2;
    public static final int TYPE_CANCELED=3;

    private DownloadListener listener;
    private boolean isCanceled =false;
    private boolean isPause=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(isPause){
                        return  TYPE_PAUSE;
                    }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) {
        super.onPostExecute(status);
        switch (status){
            case TYPE_SUCCESS:
                listener.onSuccess();
                break;
            case TYPE_FAILED:
                listener.onFailed();
                break;
            case TYPE_PAUSE:
                listener.onPause();
                break;
            case TYPE_CANCELED:
                listener.onCanceled();
                break;
            default:
                break;
        }
    }
    public void pauseDownload(){
        isPause=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;

    }
}

这段代码比较长了,我们需要一步步的进行分析。首先看一下AsyncTask中的3个泛型参数,
第一个参数泛型参数指定为String,表示在执行AsyncTask的时候需要传入一个字符串参数给后台任务;
第二个泛型参数指定Integer,表示使用整数数据来作为进度显示大newi;
第三个泛型参数指定为Integer,则表示整数数据来反馈数据结果。

接下爱来我们定义了4个整型常量用于表示下载的状态,
TYPE_SUCCESS表示下载成功
TYPE_FAILED表示下载失败
TYPE_PAUSED表示暂停下载
TYPE_CANCELED表示取消下载

然后在DownloadTask的构造函数中要求传入一个刚刚定义的DownloadListence参数,我们待会就会将相爱在的状态通过这个参数进行回调。
重写
doInBackground():方法用于在后台执行具体的下载逻辑
onProgressUpdate():方法用于在界面上更新目前的下载进度
onPostExecute():用于通知最终的下载结果

那么先看下doInBackground()方法,首先我们从参数中获取到下载的url地址,并根据URL地址解析出下载的文件名称,然后指定将文件下载到Environment.DIRECTORY_DOWNLOADS目录,也就是SD卡的Download目录,我们还要判断一下,Download目录中是不是已经存在要下载的文件了,如果已经存在的话则读取已下载的字节数,这样就可以在后面使用断点续传的功能。接下来显示调用getContentLength()方法来获取待下载文件的总长度,如果文件长度等于0则说明文件有问题,直接返回TYPE_FAILED,如果文件长度等于已经下载文件长度,那就说明已经下载完了,直接返回TYPE_SUCCESS即可。紧接着使用okhttp来发送一个网络请求,需要注意的是,这里在请求中添加一个header,用于告诉服务器我们想要从那个字节开始下载,已经下载的就不用重写下载。接下来读取服务器的数据,并使用java的文件流方式。在这个过程中,我们还要判断用户有没有出发暂停或者取消的操作,如果有的话,则返回TYPE_PAUSED或者TYPE_CANCELED来中断下载,如果没有的话,则试试计算当前的下载速度,然后调用publishDownload()或者cancelDownload()方法既可以更改变量的值。
接下来看一下onProgressUpdate()方法,这个方法就简单的多,他首先从参数中获取到当前的下载进度,然后和上一次的下载进度进行对比,如果有变化的话则调用DownloadListener的onProgress()方法来通知下载进度更新
最后是onPostExecute()方法,也是非常简单,就是根据参数中传入的下载状态进行回调。下载成功就调用DownloadListener的onSuccess()方法,下载失败就调用onFailed()方法,暂停下载就嗲用onPause方法,取消下载,就调用onCanceled方法。
这样,我们的下载功能就完成了,下面为了保证DownloadTask可以一直在后台运行,我们还需要创建一个下载服务
代码如下所示:

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);

        }

        @Override
        public void onFailed() {
        downloadTask=null;
        //下载失败时将前台服务关闭,并创建一个下载失败的通知
            stopForeground(true);
            getNotificationManager().notify(1,getNotification("Download onFailed",-1));
            Toast.makeText(DownloadService.this,"Download onFailed",Toast.LENGTH_SHORT);
        }

        @Override
        public void onPause() {
            downloadTask=null;
            Toast.makeText(DownloadService.this,"onPause",Toast.LENGTH_SHORT);

        }

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

        }
    };
    public DownloadService() {
    }
    private DownloadBiner mBiner=new DownloadBiner();

    @Override
    public IBinder onBind(Intent intent) {
        return mBiner;
    }
    class DownloadBiner 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的匿名类实例。并在匿名方法中实现了,方法,在onProgress()方法中,我们调用getNotification()方法构建了一个用于显示下载进度的通知,然后调用NotifitationManager的notify()方法去触发这个通知,这个样就可以在下拉状态栏中是实时看到当前下载的进度,在onSuccess()方法中,我们首先要将正在下载的前台通知关闭,然后去创建一个新的通知,用于告诉用户下载成功,
接下来为了要让DownloadServier可以和活动进行通信,我们有创建了一个DownloadBinder。
DownloadBinder中提供了,startDownload(),pauseDownload(),和cancleDownload()这三个方法,顾名思义,他们分别是开始下载,暂停下载,和取消下载。在startDownload()方法中,我们创建了一个DownloadTask实例,把刚才的DownloadListener作为参数传入,然后调用execute()方法开启下载,并将下载文件的url地址传入execute()方法中,同时,为了让这个下载服务成为一个前台服务,我们调用startForeground()方法,这样就会生成前台服务

现在下载的服务已经成功实现,后端的工作基本完成,那么接下来,我们开始写前端
修改布局

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.fxr.servicebestpractice.MainActivity">

    <Button
        android:id="@+id/start_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start Download"
        />
    <Button
        android:id="@+id/pause_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Pause Download"
        />
    <Button
        android:id="@+id/cancel_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Cancel Download"
        />

</LinearLayout>

mainActivity

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private DownloadService.DownloadBiner downloadBinder;
    private ServiceConnection connection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder =(DownloadService.DownloadBiner)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 cacelDownload=(Button)findViewById(R.id.cancel_download);
        startDownload.setOnClickListener(this);
        pauseDownload.setOnClickListener(this);
        cacelDownload.setOnClickListener(this);
        Intent intent=new Intent(this,DownloadService.class);
        startService(intent);
        bindService(intent,connection,BIND_AUTO_CREATE);


    }

    @Override
    public void onClick(View v) {
        if(downloadBinder==null){
            return;
        }
        switch (v.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;
        }
    }
}

可以看到,这里我们首先创建一个SerConnection的匿名类
然后onServiceConnected()方法中获取到,DownloadBinder的实例,有了这个实例,我们就可以在活动中调用服务提供的各种方法了,
接下来看一下oncreate()方法,分别调用startService()和bindService()方法来启动和绑定服务。这一点至关重要,因为启动服务可以保证DownloadService一直在后台运行,绑定服务则可以让,Main和DownloadService进行通信,
声明权限

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.fxr.servicebestpractice">
    <uses-permission
        android:name="android.permission.INTERNET"
        />
    <uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <service
            android:name=".DownloadService"
            android:enabled="true"
            android:exported="true">
        </service>
    </application>

</manifest>

运行一下ok
这里写图片描述

到这里吧,晚上回家在复习下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值