1 添加网络库

在 build.gradle 中添加 okhttp3 库:

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

2 定义监听器

定义下载监听器,监听下载过程中的各种情况:

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

3 定义异步下载任务

public class DownloadTask extends AsyncTask<String, Integer, DownloadTask.DownloadResult> {    private static final String TAG = "DownloadTask";    /**     * 下载结果     */    public enum DownloadResult {        //下载成功        SUCCESS,        //下载失败        FAILED,        //下载被暂停        PAUSED,        //下载被取消        CANCELED    }    //是否取消下载    private boolean isCanceled = false;    //是否暂停下载    private boolean isPaused = false;    //上一次的下载进度    private int lastProgress;    private DownloadListener listener;    public DownloadTask(DownloadListener listener) {        this.listener = listener;    }    /**     * 下载     *     * @param params     * @return     */    @Override    protected DownloadResult doInBackground(String... params) {        InputStream inputStream = null;        RandomAccessFile downloadedFile = null;        File file = null;        try {            long downloadedLength = 0;//文件已下载的大小            String url = params[0];//获取下载地址            String fileName = url.substring(url.lastIndexOf("/"));            //指定 SD 卡的 Download 目录            String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();            file = new File(directory + fileName);            if (file.exists()) {//文件已存在,则读取已下载的文件字节数(用于断点续传)                downloadedLength = file.length();            }            long contentLength = getContentLength(url);//获取待下载文件的总长度            Log.d(TAG, "doInBackground: 待下载文件的总长度:" + contentLength);            Log.d(TAG, "doInBackground: 已下载文件的总长度:" + downloadedLength);            if (contentLength == 0) {//下载失败                return DownloadResult.FAILED;            } else if (contentLength == downloadedLength) {//下载成功                return DownloadResult.SUCCESS;            }            OkHttpClient client = buildHttpClient();            Request request = new Request.Builder()                    //指定从那个字节开始下载,即【断点下载】                    .addHeader("RANGE", "bytes=" + downloadedLength + "-")                    .url(url)                    .build();            Response response = client.newCall(request).execute();            if (response != null) {                inputStream = response.body().byteStream();                downloadedFile = new RandomAccessFile(file, "rw");                downloadedFile.seek(downloadedLength);//跳转到未下载的字节节点                byte[] bytes = new byte[1024];                int total = 0;//下载的总字节数                int readLength;//读取的字节数                while ((readLength = inputStream.read(bytes)) != -1) {                    //在下载的过程中,判断下载是否被取消或者被暂停                    if (isCanceled) {//取消                        return DownloadResult.CANCELED;                    } else if (isPaused) {//暂停                        return DownloadResult.PAUSED;                    } else {                        total += readLength;                        downloadedFile.write(bytes, 0, readLength);                        //计算已下载的进度(单位:百分比)                        int progress = (int) ((total + downloadedLength) * 100 / contentLength);                        //发布下载进度                        publishProgress(progress);                    }                }                response.body().close();                return DownloadResult.SUCCESS;            }        } catch (IOException e) {            e.printStackTrace();        } finally {            try {                if (inputStream != null) {                    inputStream.close();                }                if (downloadedFile != null) {                    downloadedFile.close();                }                if (isCanceled && file != null) {                    file.delete();                }            } catch (IOException e) {                e.printStackTrace();            }        }        return DownloadResult.FAILED;    }    @NonNull    private OkHttpClient buildHttpClient() {        return new OkHttpClient.Builder().retryOnConnectionFailure(true).connectTimeout(30, TimeUnit.SECONDS).build();    }    /**     * 更新当前的下载进度     *     * @param values     */    @Override    protected void onProgressUpdate(Integer... values) {        int progress = values[0];        if (progress > lastProgress) {            listener.onProgress(progress);            lastProgress = progress;        }    }    /**     * 根据下载结果调用相应的回调方法     *     * @param result     */    @Override    protected void onPostExecute(DownloadResult result) {        switch (result) {            case SUCCESS:                listener.onSuccess();                break;            case FAILED:                listener.onFailed();                break;            case PAUSED:                listener.onPaused();                break;            case CANCELED:                listener.onCanceled();                break;            default:                break;        }    }    /**     * 暂停下载     */    public void pause() {        isPaused = true;    }    /**     * 取消下载     */    public void cancel() {        isCanceled = true;    }    /**     * 获取待下载文件的总长度     *     * @param url 下载文件地址     * @return     */    private long getContentLength(String url) throws IOException {        Log.d(TAG, "getContentLength:url:" + url);        OkHttpClient client = buildHttpClient();        Request request = new Request.Builder().url(url).build();        Response response = client.newCall(request).execute();        if (response != null && response.isSuccessful()) {            long contentLength = response.body().contentLength();            response.close();            return contentLength;        } else {            return 0;        }    }}

DownloadTask 类做了这些工作:

1、继承 AsyncTask 类并传入三个参数:

extends AsyncTask<String, Integer, DownloadTask.DownloadResult>
参数 说明
String 表示传递到后台服务的参数类型,这里参数指的是文件下载 URL 地址。
Integer 表示使用整型数据作为下载进度的显示单位。
DownloadResult 使用 DownloadResult 枚举类型来定义下载结果。

2、定义了枚举类型的下载结果。
3、重写了这些方法:
* doInBackground - 执行下载操作。
* onProgressUpdate - 更新当前的下载进度。
* onPostExecute - 根据下载结果调用相应的回调方法。

在 doInBackground 中,根据已下载的文件字节总数与要下载的字节总数做比较,让程序直接跳过已下载的字节,所以支持断点续传哦O(∩_∩)O哈哈~

4 定义下载服务

public class DownloadService extends Service {    private DownloadTask task;    private String url;    public static final int NOTIFICATION_ID = 1;    private DownloadListener listener = new DownloadListener() {        @Override        public void onProgress(int progress) {            getNotificationManager().notify(NOTIFICATION_ID, buildNotification("下载中……", progress));        }        @Override        public void onSuccess() {            task = null;            //关闭前台服务            stopForeground(true);            //通知下载成功            getNotificationManager().notify(NOTIFICATION_ID, buildNotification("下载成功", -1));            Toast.makeText(DownloadService.this, "下载成功", Toast.LENGTH_SHORT).show();        }        @Override        public void onFailed() {            task = null;            //关闭前台服务            stopForeground(true);            //通知下载失败            getNotificationManager().notify(NOTIFICATION_ID, buildNotification("下载失败", -1));            Toast.makeText(DownloadService.this, "下载失败", Toast.LENGTH_SHORT).show();        }        @Override        public void onPaused() {            task = null;            Toast.makeText(DownloadService.this, "暂停下载", Toast.LENGTH_SHORT).show();        }        @Override        public void onCanceled() {            task = null;            //关闭前台服务            stopForeground(true);            //通知下载取消            Toast.makeText(DownloadService.this, "取消下载", Toast.LENGTH_SHORT).show();        }    };    private NotificationManager getNotificationManager() {        return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);    }    /**     * 构建【下载进度】通知     *     * @param title    通知标题     * @param progress 下载进度     */    private Notification buildNotification(String title, int progress) {        Intent intent = new Intent(this, MainActivity.class);        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);        builder.setSmallIcon(R.mipmap.ic_launcher);        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));        builder.setContentTitle(title);        int requestCode = 0;        int flags = PendingIntent.FLAG_UPDATE_CURRENT;        builder.setContentIntent(PendingIntent.getActivity(this, requestCode, intent, flags));        if (progress > 0) {            builder.setContentText(progress + "%");            builder.setProgress(100, progress, false);        }        return builder.build();    }    public DownloadService() {    }    @Override    public IBinder onBind(Intent intent) {        return new DownloadBinder();    }    /**     * 通过 Binder 让服务与活动之间实现通信     */    protected class DownloadBinder extends Binder {        /**         * 开始下载         *         * @param url         */        public void start(String url) {            if (task == null) {                DownloadService.this.url = url;                task = new DownloadTask(listener);                task.execute(url);                startForeground(NOTIFICATION_ID, buildNotification("开始下载……", 0));                Toast.makeText(DownloadService.this, "开始下载……", Toast.LENGTH_SHORT).show();            }        }        /**         * 暂停下载         */        public void pause() {            if (task != null) {                task.pause();            }        }        /**         * 取消下载         */        public void cancel() {            if (task != null) {                task.cancel();                return;            }            if (url == null) {                return;            }            //删除已下载文件            String fileName = url.substring(url.lastIndexOf("/"));            String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();            File file = new File(directory + fileName);            if (file.exists()) {                file.delete();            }            //关闭通知            getNotificationManager().cancel(NOTIFICATION_ID);            stopForeground(true);            Toast.makeText(DownloadService.this, "被取消", Toast.LENGTH_SHORT).show();        }    }}

这个大类做了以下工作:
1. 通过匿名类创建了 DownloadListener 实例。
2. 构建【下载进度】通知。
3. 通过 Binder 让服务与活动之间实现通信。

这里的 setProgress 定义如下:

public Builder setProgress(int max, int progress, boolean indeterminate)
参数 说明
max 最大进度。
progress 当前进度。
indeterminate 是否使用模糊进度条。

5 定义控制按钮

布局文件:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <Button        android:id="@+id/start"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="开始下载" />    <Button        android:id="@+id/pause"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="暂停下载" />    <Button        android:id="@+id/cancel"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="取消下载" />LinearLayout>

这里,我们定义了【开始】、【暂停】与【取消】按钮。

6 定义活动类

public class MainActivity extends AppCompatActivity implements View.OnClickListener {    public static final int REQUEST_CODE = 1;    private DownloadService.DownloadBinder binder;    private ServiceConnection connection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            binder = (DownloadService.DownloadBinder) service;        }        @Override        public void onServiceDisconnected(ComponentName name) {        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        findViewById(R.id.start).setOnClickListener(this);        findViewById(R.id.pause).setOnClickListener(this);        findViewById(R.id.cancel).setOnClickListener(this);        Intent intent = new Intent(this, DownloadService.class);        //启动服务,让【下载】服务持续在后台运行        startService(intent);        //绑定服务,让活动与服务之间实现通信        bindService(intent, connection, BIND_AUTO_CREATE);        //申请权限        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},                    REQUEST_CODE);        }    }    @Override    public void onClick(View v) {        if (binder == null) {            return;        }        switch (v.getId()) {            case R.id.start:                String url = "http://mirrors.shu.edu.cn/apache/maven/maven-3/3.5.4/binaries/apache-maven-3.5.4-bin.zip";                binder.start(url);                break;            case R.id.pause:                binder.pause();                break;            case R.id.cancel:                binder.cancel();                break;            default:                break;        }    }    @Override    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {        switch (requestCode) {            case REQUEST_CODE:                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);    }}

活动类中做了这些事:
1. 建立与服务的绑定关系。
2. 为按钮定义点击事件。
3. 申请权限。

注意: 在 onDestroy() 中要记得解绑服务哦。

在代码中,我们把 Maven 作为下载演示路径。O(∩_∩)O哈哈~

7 声明权限与服务

AndroidManifest.xml

<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: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>

8 运行

第一次运行会弹出权限申请:

点击 ALLOW 后,就可以点击相应的按钮进行下载控制啦。点击【开始】按钮后,可以在通知中看到下载进度:


一个支持断点续传的 Android 下载服务就完成啦,是不是很有成就感呀O(∩_∩)O哈哈~

更多相关文章

  1. 一款常用的 Squid 日志分析工具
  2. GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
  3. RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
  4. Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
  5. 【Android】自定义progressBar样式
  6. 解决android工程引用多个jar包导致的文件重复的错误
  7. Android和java创建xml文件和解析xml文件剖析
  8. Android之创建文件、目录
  9. android 拷贝文件到其他目录下

随机推荐

  1. 什么是线程?什么是进程?为什么要有线程?有什
  2. Java 中锁之间的对比
  3. Java 中线程池包含哪些状态?
  4. 首申百度联盟、Google Adsense,均败
  5. synchronized 锁的升级原理是什么?
  6. 博客网站显示框相对浏览器固定位置显示
  7. 如何创建、启动 Java 线程?
  8. 什么是死锁?
  9. 如何停止一个线程池?
  10. 什么是活锁和饥饿?及示例