• Android总结七(Service、多线程断点续传下载)


    一.服务

      1.什么是服务?

        Service是一个可以长期在后台运行, 没有界面的组件.

        它可以被其他组件绑定, 可以在进程之间通信.

      2.创建Service

        定义类继承Service, 实现回调函数.

        在清单文件中声明<service>

      3.启动服务,停止服务

        在其他组件中可以调用startService()方法启动一个服务, 可以调用stopService()方法停止一个服务

        在服务中可以使用stopSelf()方法停止服务。如果stopSelf()方法不传任何参数, 就是立即停止, 无论是否还有其他未执行结束的, 都会立即停止;传入startId则是等到所有其他的start()执行结束后再停止服务。

      4.耗时操作

        如果需要在服务中做耗时的操作, 那么也需要开启新的线程.

        如果希望服务长期运行, 即使在内存不足的时候也不要被杀, 那么可以设置为前台服务. startForeground()

      5.绑定服务

        a.应用内部绑定

          i.定义接口, 声明要提供的方法

          ii.Service中定义一个类继承Binder(IBinder的子类), 实现自定义接口, 实现业务方法, 在onBind()方法中返回该类对象

          iii.Activity中调用bindService()绑定服务, 传递一个ServiceConnection用来接收onBind()方法返回的IBinder, 强转为接口类型, 即可调用方法

        b.应用之间绑定

          i.使用AIDL定义接口, 刷新工程, Eclipse会自动生成一个Java接口

          ii.Service中定义一个类继承Stub类, 实现抽象方法

          iii.Activity中收到IBinder之后调用Stub.asInterface()方法把IBinder转为接口类型

        c.aidl中使用自定义类型

          aidl中默认只支持基本数据类型和String以及CharSequences

          如果需要使用自定义类型, 那么该类需要实现Parcelable, 需要实现writeToParcel()方法, 需要定义CREATOR对象

          再根据这个类定义一个同名的aidl

          在aidl接口中使用自定义类型作为参数的时候需要加上in

    二.多线程断点续传下载

      1.多线程下载

        先获取服务器文件大小(Content-Length)

        根据Content-Length计算每个线程负责的范围

        通过请求头Range设置每个线程负责的一部分数据

        多个线程都下载结束后, 把文件合并

      2.断点续传

        每个线程启动之前, 计算开始位置时, 用原起始位置加上上次已完成的文件长度

        把FileAsyncHttpResponseHandler中的FileOutputStream改为追加模式

      3.示例代码:

    activity_main.xml

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
    
        <ImageButton
            android:id="@+id/iv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:onClick="download"
            android:src="@android:drawable/stat_sys_download" />
    
        <EditText
            android:id="@+id/et_url"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@id/iv"
            android:inputType="textUri" />
    
        <ProgressBar
            android:id="@+id/pb_download"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_below="@+id/iv" 
            android:layout_marginLeft="4dp"
            android:layout_marginRight="4dp"/>
    
        <TextView
            android:id="@+id/tv_percent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignLeft="@+id/pb_download"
            android:layout_below="@+id/pb_download"
            android:text="0" />
    
        <TextView
            android:id="@+id/tv_total"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignRight="@+id/pb_download"
            android:layout_below="@+id/pb_download"
            android:text="12345/67891" />
    
    </RelativeLayout>

    MainActivity.java

    package com.gnnuit.download;
    
    import java.io.File;
    
    import android.os.Bundle;
    import android.os.Environment;
    import android.view.View;
    import android.widget.EditText;
    import android.widget.ImageButton;
    import android.widget.ProgressBar;
    import android.widget.TextView;
    import android.app.Activity;
    
    public class MainActivity extends Activity {
    
        private EditText et;
        private ProgressBar downloadPb;
        private TextView progressTv;
        private TextView percentTv;
        private DownloadTask task;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            et = (EditText) findViewById(R.id.et_url);
            downloadPb = (ProgressBar) findViewById(R.id.pb_download);
            progressTv = (TextView) findViewById(R.id.tv_total);
            percentTv = (TextView) findViewById(R.id.tv_percent);
        }
    
        public void download(View v) {
            ImageButton ib = (ImageButton) v;
    
            if (task == null) {
                String targetUrl = et.getText().toString().trim();
                File localFile = new File(Environment.getExternalStorageDirectory(), targetUrl.substring(targetUrl.lastIndexOf("/") + 1));
                task = new DownloadTask(targetUrl, localFile, 3, this, downloadPb, percentTv, progressTv);
                task.execute();
                ib.setImageResource(android.R.drawable.ic_media_pause);
            } else {
                task.stop();
                task = null;
                ib.setImageResource(android.R.drawable.ic_media_play);
            }
        }
    
    }

    DownloadTask.java

    package com.gnnuit.download;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.util.ArrayList;
    
    import org.apache.http.Header;
    import com.loopj.android.http.AsyncHttpClient;
    import com.loopj.android.http.AsyncHttpResponseHandler;
    import android.content.Context;
    import android.widget.ProgressBar;
    import android.widget.TextView;
    
    public class DownloadTask {
        private String targetUrl;
        private File localFile;
        private int threadAmount;
        private Context context;
    
        private long contentLength;
        private long threadLength;
        private ArrayList<File> cacheList = new ArrayList<File>();
        private ProgressBar downloadPb;
        private TextView percentTv;
        private TextView progressTv;
        private ArrayList<MyFileHandler> handlerList = new ArrayList<MyFileHandler>();
    
        /**
         * 创建下载任务
         * 
         * @param targetUrl
         *            请求下载文件的目标地址
         * @param localFile
         *            保存下载文件的本地路径
         * @param threadAmount
         *            开启下载的线程数量
         * @param context
         *            上下文
         * @param downloadPb
         * @param progressTv
         * @param percentTv
         */
        public DownloadTask(String targetUrl, File localFile, int threadAmount, Context context, ProgressBar downloadPb, TextView percentTv, TextView progressTv) {
            super();
            this.targetUrl = targetUrl;
            this.localFile = localFile;
            this.threadAmount = threadAmount;
            this.context = context;
            this.downloadPb = downloadPb;
            this.percentTv = percentTv;
            this.progressTv = progressTv;
        }
    
        public void execute() {
            new AsyncHttpClient().get(targetUrl, new AsyncHttpResponseHandler() {// 根据URL向服务器发起一个请求, 获取响应头
    
                        public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
                            caculateLength(headers);// 获取文件总长度, 计算每个线程负责的长度
                            beginDownload();// 开启多个线程下载
                        }
    
                        public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
                        }
    
                    });
        }
    
        private void caculateLength(Header[] headers) {
            for (Header header : headers) {
                System.out.println(header.getName() + ":" + header.getValue());
                if ("Content-Length".equals(header.getName())) {
                    contentLength = Long.parseLong(header.getValue());// 从相应头中获取文件总大小
                }
            }
            threadLength = (contentLength + threadAmount - 1) / threadAmount;// 计算每个线程负责多少
            downloadPb.setMax((int) contentLength);// 设置进度条的最大刻度为文件总大小
        }
    
        private void beginDownload() {
            for (int i = 0; i < threadAmount; i++) {// 定义循环, 每次循环启动1个线程下载
                File cacheFile = new File(context.getCacheDir(), localFile.getName() + ".temp" + i);// 定义临时文件的路径
                cacheList.add(cacheFile);// 把文件装入集合
    
                long begin = i * threadLength + cacheFile.length();// 计算开始位置
                long end = i * threadLength + threadLength - 1;// 计算结束位置
                System.out.println(i + ":" + begin + "-" + end);
                if (begin > end) {
                    continue;
                }
    
                MyFileHandler fileHandle = new MyFileHandler(cacheFile) {// 发送请求, 下载数据, 存储到临时文件
                    private long ms;
    
                    public void onSuccess(int statusCode, Header[] headers, File file) {
                        System.out.println(Thread.currentThread().getName() + ": " + file + " 下载完成");
                        checkOtherThread(); // 检查其他线程是否下载完毕, 如果都完了, 合并文件
                    }
    
                    public void onFailure(int statusCode, Header[] headers, Throwable throwable, File file) {
                    }
    
                    public void onProgress(int bytesWritten, int totalSize) { // 运行频率非常高
                        if (System.currentTimeMillis() - ms > 100) {
                            ms = System.currentTimeMillis();
                            updateProgress();
                        }
                    }
                };
                handlerList.add(fileHandle);
    
                AsyncHttpClient client = new AsyncHttpClient();
                client.addHeader("Range", "bytes=" + begin + "-" + end);// 设置请求数据的范围
                client.get(targetUrl, fileHandle);
            }
        }
    
        private void updateProgress() {
            long finishLength = getFinishLength();
            if (finishLength == 0)
                return;
            downloadPb.setProgress((int) finishLength);
            percentTv.setText(finishLength * 1000 / contentLength / 10f + "%");
            progressTv.setText(finishLength + "/" + contentLength);
        }
    
        private long getFinishLength() {
            long finishLength = 0;
            for (File cacheFile : cacheList) {// 遍历所有的临时文件
                finishLength += cacheFile.length();// 统计总长度
            }
            return finishLength;
        }
    
        private void checkOtherThread() {
            long finishLength = getFinishLength();
            if (finishLength == contentLength) {// 如果文件长度和ContentLength相等,代表下载完成
                merge();// 合并所有的临时文件
            }
        }
    
        private void merge() {
            try {
                FileOutputStream out = new FileOutputStream(localFile);
                for (File cacheFile : cacheList) {
                    FileInputStream in = new FileInputStream(cacheFile);
                    int length = 0;
                    byte[] buffer = new byte[8192];
                    while ((length = in.read(buffer)) != -1) {
                        out.write(buffer, 0, length);
                    }
                    in.close();
                    cacheFile.delete();
                }
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public void stop() {
            for (MyFileHandler fileHandler : handlerList) {
                fileHandler.cancel();
            }
        }
    
    }

    MyFileHandler.java

    package com.gnnuit.download;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    
    import org.apache.http.Header;
    import org.apache.http.HttpEntity;
    
    import android.content.Context;
    
    import com.loopj.android.http.FileAsyncHttpResponseHandler;
    
    public abstract class MyFileHandler extends FileAsyncHttpResponseHandler {
        private boolean isCancel=false;
    
        public MyFileHandler(Context context) {
            super(context);
        }
    
        public MyFileHandler(File file) {
            super(file);
        }
    
        @Override
        public void onFailure(int statusCode, Header[] headers, Throwable throwable, File file) {
        }
    
        @Override
        public void onSuccess(int statusCode, Header[] headers, File file) {
        }
        
        @Override
        protected byte[] getResponseData(HttpEntity entity) throws IOException {
            if (entity != null) {
                InputStream instream = entity.getContent();
                long contentLength = entity.getContentLength();
                FileOutputStream buffer = new FileOutputStream(getTargetFile(), true);
                if (instream != null) {
                    try {
                        byte[] tmp = new byte[BUFFER_SIZE];
                        int l, count = 0;
                        // do not send messages if request has been cancelled
                        while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) {
                            if (isCancel)
                                break;
                            count += l;
                            buffer.write(tmp, 0, l);
                            sendProgressMessage(count, (int) contentLength);
                        }
                    } finally {
                        instream.close();
                        buffer.flush();
                        buffer.close();
                    }
                }
            }
            return null;
        }
        
        public void cancel() {
            isCancel = true;
        }
    
    }

      

  • 相关阅读:
    macbook使用美化工具在屏幕展示出常查信息
    Mac 下 Docker 运行较慢的原因分析及个人见解
    Json Web Token
    Mac开发环境部署
    python3.6_多进程_multiprocessing.pool_concurrent.futures_ProcessPoolExecutor_对比
    apache2_nginx_反向代理设置_https_不验证后端证书_只允许访问首页
    awstats_yum_dnf_centos8_nginx_X-Forwarded-For
    Django_uwsgi_nginx_centos_笔记
    python3_pandas.HDFStore_h5py_HDF5_的笔记
    echarts_highcharts_笔记_风向箭头
  • 原文地址:https://www.cnblogs.com/FlySheep/p/3852608.html
Copyright © 2020-2023  润新知