欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android 服务下载示例

程序员文章站 2022-05-03 13:32:02
最近重读了下《第一行代码》,看到《第一行代码》 的一个小项目,特写本文梳理下流程。首先终结一句话,在android 的多线程处理中,尽量做到在子线程中进行耗时操作,在主线程中更新界面ui。好了,下面...

最近重读了下《第一行代码》,看到《第一行代码》 的一个小项目,特写本文梳理下流程。首先终结一句话,在android 的多线程处理中,尽量做到在子线程中进行耗时操作,在主线程中更新界面ui。好了,下面开始写这个项目。

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

public interface downloadlistener {
    // 通知下载进度
    void onprogress(int progress);
    // 通知下载成功
    void onsuccess();
    // 通知下载失败
    void onfailed();
    // 通知下载暂停
    void onpaused();
    //通知下载失败
    void oncanceled();
}

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

(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 文件中的 dependencies 中添加okhttp 依赖

compile '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 中的代码,创建三个按钮。启动 暂停 取消


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文件中声明使用的权限,还有别忘了服务也是需要声明的。

这个项目的总结就到这里就结束了。。。