前言:最近重读了下“第一行代码”,看到“第一行代码”的一个小项目,特写这篇博客梳理下流程。这个项目实现了使用OKHttp 断点下载大文件,通过服务在下载的过程中暂停和取消并更新通知消息,下面看下效果图:

首先总结一句话,在Android的多线程处理中,尽量做到在子线程中进行耗时操作,在主线程中更新界面UI。好了,下面开始写这个项目。

一。首先创建一个回调接口,用于对下载过程中的各种状态进行监听和回调,代码如下:

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

    好吧,这里插一点题外话,回调函数。主要说一下回调函数的机制

    (1)。设置接口,定义回调函数

public interface MyListener {    void onClick();}

    (2)。提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者。 

public class Realize {    private MyListener myListener;    public void setMyListener(MyListener myListener) {        this.myListener = myListener;    }    public void doSth() {        myListener.onClick();    }}

    (3)。当特定的时间或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。

public class Test {    public static void main(String[] args) {        Realize realize = new Realize();        realize.setMyListener(new MyListener() {            @Override            public void onClick() {                System.out.println("特定事件或条件发生了");            }        });        realize.doSth();    }}

    上面就是一个回调函数的流程,仔细理解下,就能理解回调的实现了,下面回到正题。

二。使用AsyncTask实现下载功能的处理。  

    emmm,这里还要插入一下的AsyncTask的解释.AsyncTask的英文一种轻量级的异步任务类,这个类可以执行后台操作,并在用户界面上发布结果,而不必处理线程和处理程序.OK,先举个例子:

public class DownloadTask extends AsyncTask {    /**     * 刚开始执行的时候调用,可以用于进行一些界面上的初始化操作,比如说显示一个进度条对话框     */    @Override    protected void onPreExecute() {        super.onPreExecute();    }    /**     * 这里的代码会在子线程中执行,可以执行耗时操作     *     * @param voids     * @return     */    @Override    protected Boolean doInBackground(Void... voids) {        //  反馈当前任务的进度,执行完这个方法会调用onProgressUpdate 方法        publishProgress(50);        return null;    }    /**     * 根据返回的数据更新UI     *     * @param values     */    @Override    protected void onProgressUpdate(Integer... values) {        super.onProgressUpdate(values);    }        /**     * 后台任务执行完毕通过return语句进行返回时  也可以根据返回的数据更新UI     *     * @param aBoolean     */    @Override    protected void onPostExecute(Boolean aBoolean) {        super.onPostExecute(aBoolean);    }}

     简单的来说,使用AsyncTask的诀窍就是,在doInBackground()方法中执行具体的耗时任务,在onProgressUpdate()方法中进行UI操作,在onPostExecute()方法中执行一些任务的收尾工作。如果想要启动这个任务,只需编写以下代码即可:

                 new DownloadTask()。execute();

    继续回到正题(这样讲着讲着会不会歪楼呀)先在app / build.gradle文件中的依赖中添加OKHttp依赖

        编译'com.squareup.okhttp3:okhttp:3.4.1'

    下面编写下载功能,新建一个DownloadFileTask继承AsyncTask。

//   第一个参数  传给后台参数   第二个 使用整型数据作为进度显示单位   第三个  使用整型数据反馈执行结果public class DownloadFileTask extends AsyncTask {    // 下载成功    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 isCancelled = false;    // 是否暂停    private boolean isPaused = false;    // 当前进度    private int lastProgress;    /**     * 带监听的构造函数     *     * @param listener     */    public DownloadFileTask(DownloadListener listener) {        this.listener = listener;    }    /**     * 在后台执行具体的下载逻辑  是在子线程里面 可以执行耗时操作     */    @Override    protected Integer doInBackground(String... strings) {        // 文件输入流        InputStream is = null;        RandomAccessFile accessFile = null;        File file = null;        // 记录已下载的文件长度        long downloadedLength = 0;        // 获取下载的URL地址        String downloadUrl = strings[0];        // 从URL下载地址中截取下载的文件名        String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));        // 获取SD卡的Download 目录        String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();        //  得到要保存的文件        file = new File(directory + fileName);        // 如果文件已经存在  获取文件的长度        if (file.exists()) {            downloadedLength = file.length();        }        try {            //   获取待下载文件的字节长度            long contentLength = getContentLength(downloadUrl);            //  如果待下载文件的字节长度为0 说明待下载文件有问题            if (contentLength == 0) {                return TYPE_FAILED;            } else if (contentLength == downloadedLength) {                //   已下载字节和文件总字节相等 说明已经下载完了                return TYPE_SUCCESS;            }            //  获取OkHttpClient 对象            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();                // 获取随机读取文件类  可以随机读取一个文件中指定位置的数据                accessFile = new RandomAccessFile(file, "rw");                // 跳过已下载的字节                accessFile.seek(downloadedLength);                //指定每次读取文件缓存区的大小为1KB                byte[] b = new byte[1024];                int total = 0;                int len;                //   每次读取的字节长度                while ((len = is.read(b)) != -1) {                    if (isCancelled) {                        return TYPE_CANCELED;                    } else if (isPaused) {                        return TYPE_PAUSED;                    } else {                        // 读取的全部字节的长度                        total += len;                        // 写入每次读取的字节长度                        accessFile.write(b, 0, len);                        // 计算已下载的百分比                        int progress = (int) ((total + downloadedLength) * 100 / contentLength);                        // 更新进度条                        publishProgress(progress);                    }                }                // 关闭连接  返回成功                response.body().close();                return TYPE_SUCCESS;            }        } catch (IOException e) {            e.printStackTrace();        } finally {            try {                // 关闭输入流                if (is != null) {                    is.close();                }                // 关闭文件                if (accessFile != null) {                    accessFile.close();                }                Log.d("TAG", "这里永远都会执行 ");                // 如果是取消的  就删除掉文件                if (isCancelled && file != null) {                    file.delete();                }            } catch (IOException e) {                e.printStackTrace();            }        }        return null;    }    /**     * 获取下载文件的长度     *     * @param downloadUrl     * @return     * @throws IOException     */    private long getContentLength(String downloadUrl) throws IOException {        // 获取OkHttpClient        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;    }    /**     * 在界面上更新当前的下载进度     *     * @param values     */    @Override    protected void onProgressUpdate(Integer... values) {        super.onProgressUpdate(values);        int progress = values[0];        if (progress > lastProgress) {            listener.onProgress(progress);            lastProgress = progress;        }    }    /**     * 用于通知最后的下载结果     *     * @param integer     */    @Override    protected void onPostExecute(Integer integer) {        super.onPostExecute(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;        }    }    /**     * 暂停下载     */    public void pauseDownload() {        isPaused = true;    }    /**     * 取消下载     */    public void cancelDownload() {        isCancelled = true;    }}

 

三。创建一个下载的服务 

 

    没错,还得简单的介绍下服务(这么墨迹的吗)。一般我们都调用startService()方法来启动服务,并调用stopService()方法来停止这个服务,但这样启动服务后,活动无法干预到服务到底执行了怎样的逻辑。这时候就要用onBind()方法了,对服务进行绑定之后,就可以调用服务里的Binder提供的方法了.OK,下面举个个子子。首先创建一个MyService,代码如下:

public class MyService extends Service {    private static final String TAG = "MyService";    private DownloadBinder mBinder = new DownloadBinder();    // 创建 DownloadBinder 实例  随便定义了两个方法    class DownloadBinder extends Binder {        public void startDownload() {            Log.d(TAG, "startDownload: executed");        }        public int getProgress() {            Log.d(TAG, "getProgress: executed");            return 0;        }    }    /**     * 返回这个DownloadBinder 实例     *     * @param intent     * @return     */    @Override    public IBinder onBind(Intent intent) {        return mBinder;    }    }

    然后在活动中绑定服务,实现活动去指挥服务去干什么。

public class FirstActivity extends AppCompatActivity {    private Button btnBind;    private Button btnUnBind;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_first);        btnBind = (Button) findViewById(R.id.btnBind);        btnUnBind = (Button) findViewById(R.id.btnUnBind);        btnBind.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                // 绑定服务                Intent intent = new Intent(FirstActivity.this, MyService.class);                bindService(intent, connection, BIND_AUTO_CREATE);            }        });        btnUnBind.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                //  解绑服务                unbindService(connection);            }        });    }    private MyService.DownloadBinder downloadBinder;    ServiceConnection connection = new ServiceConnection() {        /**         * 活动与服务绑定成功后         * @param name         * @param service         */        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            // 向下转型获得DownloadBinder 实例 就能调用DownloadBinder的方法 进而控制服务的逻辑            downloadBinder = (MyService.DownloadBinder) service;            downloadBinder.startDownload();            downloadBinder.getProgress();        }        /**         * 活动与服务解绑后         * @param name         */        @Override        public void onServiceDisconnected(ComponentName name) {        }    };}

    好的,了解到活动如何去控制服务以后,下面正式写下载的服务,代码如下:    

public class DownloadService extends Service {    // 下载的异步操作类    private DownloadFileTask downloadFileTask;    // 下载地址    private String downloadUrl;    private static final String TAG = "DownloadService";    // 下载状态的回调    private DownloadListener listener = new DownloadListener() {        /**         *  更新下载进度状态         * @param progress         */        @Override        public void onProgress(int progress) {            Log.d(TAG, "onProgress: -------" + progress);            getNotificationManager().notify(1, getNotification("Downloading", progress));        }        /**         * 下载成功         */        @Override        public void onSuccess() {            Log.d(TAG, "onSuccess: -------------");            downloadFileTask = null;            //下载成功时将前台服务通知关闭,并创建一个下载成功的通知            stopForeground(true);            getNotificationManager().notify(1, getNotification("Download Success", -1));            Toast.makeText(DownloadService.this, "Download Success", Toast.LENGTH_SHORT).show();        }        /**         * 下载失败         */        @Override        public void onFailed() {            Log.d(TAG, "onFailed: -------------");            downloadFileTask = null;            // 下载失败时将前台服务通知关闭,并创建一个下载失败的通知            stopForeground(true);            getNotificationManager().notify(1, getNotification("Download Failed", -1));            Toast.makeText(DownloadService.this, "Download Failed", Toast.LENGTH_SHORT).show();        }        /**         * 下载暂停         */        @Override        public void onPaused() {            Log.d(TAG, "onPaused: -------------");            downloadFileTask = null;            Toast.makeText(DownloadService.this, "Download Paused", Toast.LENGTH_SHORT).show();        }        /**         * 下载取消         */        @Override        public void onCanceled() {            Log.d(TAG, "onCanceled: -------------");            downloadFileTask = null;            stopForeground(true);            Toast.makeText(DownloadService.this, "Download Canceled", Toast.LENGTH_SHORT).show();        }    };    DownloadBinder mBinder = new DownloadBinder();    /**     * 返回这个DownloadBinder 实例     *     * @param intent     * @return     */    @Override    public IBinder onBind(Intent intent) {        return mBinder;    }    // 创建 DownloadBinder 实例     class DownloadBinder extends Binder {        // 开始下载        public void startDownload(String url) {            Log.d(TAG, "startDownload--------: 开始下载");            if (downloadFileTask == null) {                downloadUrl = url;                downloadFileTask = new DownloadFileTask(listener);                downloadFileTask.execute(downloadUrl);                startForeground(1, getNotification("DownLoading", 0));                Toast.makeText(DownloadService.this, "Downloading", Toast.LENGTH_SHORT).show();            }        }        // 暂停下载        public void pauseDownload() {            Log.d(TAG, "pauseDownload--------: 暂停下载");            if (downloadFileTask != null) {                downloadFileTask.pauseDownload();            }        }        // 取消下载        public void cancelDownload() {            Log.d(TAG, "cancelDownload--------: 取消下载---" + downloadFileTask);            if (downloadFileTask != null) {                downloadFileTask.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();                }            }        }    }    /**     * 获取通知栏管理器     *     * @return     */    public NotificationManager getNotificationManager() {        return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);    }    /**     * 设置通知栏的样式 并获取通知栏的实例     *     * @param title     * @param progress     * @return     */    private Notification getNotification(String title, int progress) {        Intent[] intents = new Intent[]{(new Intent(this, DownloadActivity.class))};        PendingIntent pi = PendingIntent.getActivities(this, 0, intents, 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.先实现下界面,修改xml中的代码,创建三个按钮。启动暂停取消

<?xml version="1.0" encoding="utf-8"?>    

    2.在活动中绑定服务,让活动与服务进行通信,因为要对文件下载,还要动态的申请写文件的权限,代码如下:

public class DownloadActivity extends AppCompatActivity implements View.OnClickListener {    // 开始下载按钮    private Button btnStart;    // 暂停下载按钮    private Button btnPause;    // 取消下载按钮    private Button btnCancel;    // 服务    private Intent intent;    // 下载操作的实例    private DownloadService.DownloadBinder downloadBinder;    private ServiceConnection serviceConnection = new ServiceConnection() {        /**         * 活动与服务绑定成功后         * @param name         * @param service         */        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            downloadBinder = (DownloadService.DownloadBinder) service;        }        /**         * 活动与服务解绑后         * @param name         */        @Override        public void onServiceDisconnected(ComponentName name) {        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_download);        btnStart = (Button) findViewById(R.id.btnStart);        btnPause = (Button) findViewById(R.id.btnPause);        btnCancel = (Button) findViewById(R.id.btnCancel);        btnStart.setOnClickListener(this);        btnPause.setOnClickListener(this);        btnCancel.setOnClickListener(this);        // 启动服务并绑定        intent = new Intent(this, DownloadService.class);        startService(intent);        bindService(intent, serviceConnection, BIND_AUTO_CREATE);        // 获取写的权限        if (ContextCompat.checkSelfPermission(DownloadActivity.this,                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {            ActivityCompat.requestPermissions(DownloadActivity.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.btnStart:                String url = "https://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";                downloadBinder.startDownload(url);                break;            case R.id.btnPause:                downloadBinder.pauseDownload();                break;            case R.id.btnCancel:                downloadBinder.cancelDownload();                break;        }    }    /**     * 申请权限的返回结果     *     * @param requestCode     * @param permissions     * @param grantResults     */    @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).show();                    finish();                }                break;        }    }    /**     * 结束活动的时候关闭服务     */    @Override    protected void onDestroy() {        super.onDestroy();        unbindService(serviceConnection);        stopService(intent);    }}

最后在AndroidManifest.xml中文件中声明使用的权限,还有别忘了服务也是需要声明的。

      这个项目的总结就到这里就结束了......,最后附上源代码 源码下载地址

 

 

更多相关文章

  1. 安全篇 - 隐式配置 KeyStore 签名信息
  2. 一个关于android游戏下载静默安装功能的思路以及实现
  3. 在Android(安卓)studio里面使用AIDL
  4. 巧用apache httpcore-nio实现android推送服务
  5. android apk包反编译、破解心得
  6. 服务器端向Android客户端的推送
  7. Android(安卓)MVP设计架构:Model层数据传递到View层
  8. Android(安卓)程序读写Office文件
  9. android解析xml文件的方式之SAX方式

随机推荐

  1. 如何获取knockoutjs可观察数组的下一个元
  2. ABP(现代ASP.NET样板开发框架)系列之21、
  3. Javascript正则表达式对象和美元符号
  4. javascript-cropper插件翻译笔记
  5. javascript 构造函数中的属性与原型上属
  6. 使用Node.js初始化和配置AWS
  7. 深入浅出 Ajax 读书摘记2——【Ajax请求
  8. Javascript学习:案例7--对象属性和方法的
  9. css选择在IE中不起作用
  10. 注入html行模板的最佳方法