Android学习之路——基于Android的“下载文件“实例

我们来完成一个基于Android的下载实例:
首先新建一个项目ServiceBestPractice项目,在app/build.gradle文件中添加一个OkHtttp依赖:

dependencies {
implementation 'com.squareup.okhttp3:okhttp:3.4.1'

以便后面编写网络相关的功能。
新建一个DownloadListener接口来定义一个回调接口,对下载过程的各种状态进行监听和回调,如下:

public interface DownloadListener {
    void onPregress(int progress);  //通知当前进度
    void onSuccess();  //通知下载成功事件
    void onFailed();   //通知下载成功事件
    void onPaused();   //通知下载暂停事件
    void onCanceled();   //通知下载取消事件
}

通过AsyncTask来编写下载功能,新建一个DownloadTask继承自AsyncTask,如下:

//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];//获取下载的URL地址,
          //把获取的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();
          }
          long contentLength = getContentLength(downloadUrl);//计算字节数的长度
          //如果文件字节长度为0,则下载失败!
          if (contentLength==0){
              return TYPE_FAILED;
          }
          //文件长度等于已下载的文件长度,则下载成功
          else if (contentLength == downloadedLength) {
              return TYPE_SUCCESS;
          }
          //通过OkHttp来发送一条网络请求,添加的header用于告诉服务器从那个字节开始下载
          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 (IOException e) {
              e.printStackTrace();
          }
      }
      return TYPE_FAILED;
  }

  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.body().close();
          return contentLength;
      }
      return 0;
  }
  //界面上更新当前的下载进度
  @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();
              break;
              default:
                  break;
      }
  }
  public void pauseDownload() {
      isPaused = true;
  }
  public void cancelDownload(){
      isCanceled = true;
  }
}

在代码中对相应代码都进行了注释,这里主要了解以下我们编写的doInBackground()方法,从参数中获取下载的URL地址,将文件下载到SD卡的Download目录,文件存在则读取已下载的字节数与文件字节数对比来验证下载状态,从而完成断点续传,建立一个服务器来保证下载任务一致在后台运行。新建一个DownloadSevice,如下:

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("Download...",progress));
            Log.d("DownloadService","该界面可以接受到下载进度的数据么?"+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 Failed", -1));
            Toast.makeText(DownloadService.this, "Download Failed", Toast.LENGTH_SHORT);

        }

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

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

    private DownloadBinder mBinder = new DownloadBinder();

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

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

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

        }
    }

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

    private Notification getNotification(String title, int progress) {
        Log.d("NOtification","This is " +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();
    }
}

在这里创建DownloadListerner匿名实例,在实例中实现五个方法分别为:onProgress()、onSuccess()、onFailed()、onPaused()、onCanceled(),并在onProgress中调用getNOtification方法构建用于显示下载进度的通知,并调用notify()去触发这个通知

构建一个前端的部分:界面UI为activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android"
    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;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            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 cancelDownload = (Button)findViewById(R.id.cancel_download);
        startDownload.setOnClickListener(this);
        pauseDownload.setOnClickListener(this);
        cancelDownload.setOnClickListener(this);
        Intent intent = new Intent(this,DownloadService.class);
        startService(intent);//启动服务
        Log.d("MainActivity","服务已启动");
        bindService(intent,connection,BIND_AUTO_CREATE);//绑定服务
        Log.d("MainActivity","服务已被绑定");
        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 = "https:raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";
                downloadBinder.startDownload(url);
                Log.d("MainActivity","下载文件目标网络地址已确定");
                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) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case 1:
                if (grantResults.length>0&&grantResults[0]!=PackageManager.PERMISSION_GRANTED){
                    Toast.makeText(this,"拒绝权限将无法访问",Toast.LENGTH_SHORT);
                    finish();
                }
                break;
                default:
        }
    }

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

在AndroidManifest中添加读写文件和网络访问的权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>

OK

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值