一.服务
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; } }