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

}

  

原文地址:https://www.cnblogs.com/FlySheep/p/3852608.html