首页 > 程序开发 > 移动开发 > Android >

android 多线程断点下载实现

2012-09-27

原理:在发出请求协议的时候加上参数Range,可以定位下载文件的开始位置和结束位置,下载数据的时候,记录每条线程已经下载的数据量和线程id,现在文件的url作为下载文件的一个标识,对下载信息做持久化,可以存入数据库...

原理:

在发出请求协议的时候加上参数Range,可以定位下载文件的开始位置和结束位置,下载数据的时候,记录每条线程已经下载的数据量和线程id,现在文件的url作为下载文件的一个标识,对下载信息做持久化,可以存入数据库,文件,网络等,本例一sqlite数据库做的,当开始下载文件的时候,检查数据库没有没这个下载地址的下载信息,没有的话,就开始下载,如果有这个下载地址的信息,就计算他的线程数,如果之前下载的线程数与当前下载任务的线程数一致的话.重新开始下载.如果有下载信息,并且线程数一样的话,发送请求的时候可以使用上面的Range参数,他的对应值就是开始下载的位置和结束下载的位置,如:conn.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition);如果想尽快的读懂下面的代码,最好知道实现本例的原理,下面代码实现

主页面activity,注意主线程(ui线程)的画面数据有子线程提供的实现

[java]
package com.itcast.activity;

import java.io.File;
import java.io.IOException;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.itcast.download.DownloadPropressListener;
import com.itcast.download.FileDownLoader;
import com.itcast.download.R;

public class MainActivity extends Activity {
private EditText downloadPath;
private ProgressBar progressBar;
private TextView resultText;
private Button download;
private Button stop;
private FileDownLoader downLoader;
// 当handler被创建时,会关联到创建他的当前线程的消息队列,该类用以往消息队列发送消息,消息由当前的线程内部进行处理,
// 消息的运行代码是运行在ui线程里的,r如果想让handler在处理消息的时候执行自己想要的代码,可以重写它的handleMessage方法
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {// 判断消息id
case 1:// 消息代號
int downloadSize = msg.getData().getInt("downloadSize");// 获取当前所有线程下载的数据量
progressBar.setProgress(downloadSize);// 设置进度条的进度值
float num = (float) progressBar.getProgress() / (float) progressBar.getMax();// 下载数据量的百分比
int result = (int) (num * 100);
resultText.setText(result + "%");// 用TextView显示下载的百分比
if (progressBar.getProgress() == progressBar.getMax()) {// 如果现在完成,提示下载完成
Toast.makeText(MainActivity.this, R.string.success, Toast.LENGTH_SHORT).show();
download.setEnabled(true);// 下载完成后下载按钮处于活动妆太浓
stop.setEnabled(false);// 停止按钮不能被点击,变灰
}
break;
case -1:// 消息id-1
Toast.makeText(MainActivity.this, R.string.error, Toast.LENGTH_SHORT).show();// 如果下载过程中出现错误
break;
}

}
};

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
downloadPath = (EditText) findViewById(R.id.urlPath);
resultText = (TextView) findViewById(R.id.result);
progressBar = (ProgressBar) findViewById(R.id.progress);
download = (Button) findViewById(R.id.down);
stop = (Button) findViewById(R.id.stop);
ButtonClickListener buttonListener = new ButtonClickListener();// 点击事件的处理类
download.setOnClickListener(buttonListener);
stop.setOnClickListener(buttonListener);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}

/**
* 主线程(ui线程) Responsive(应用程序响应)<br>
* <br>
* 1,在正常情况下,Android程序会在一条单线程里运行。如果Activity要处理一件比较耗时的工作,应该交给子线程完成,
* 否侧会因为主线程长时间等待被阻塞 , 后面的用户输入事件因没能在5秒内响应,导致应用出现ANR对话框(ANR(Application No
* Response) 对话框) <br>
* <br>
* 2,若果用了子线程,该方法编译没错,但是运行起来会有错,因为这个方法是执行的代码是另外开的子线程中,所有的任务都在子线程中完成 ,
* 这个代码只是走了一遍代码, 可能不到一秒就跑完了,但是真正的下载任务还在子线程中进行,根据java规范,一旦方法运行完了,那么他的属性也就消失了,
* 这样子线程中就没法在使用创建线程的方法传入的参数了,为了方便使用,应该把两个参数设置为final,这样传递的就是在是对象的引用了,而是直接传值<br>
* <br>
* 3,因为用了子线程,而主线程(ui线程)中的控件只能由主线程来控制,如实用其他的线程来改变由主线程控制的空间的话,值是改变了,
* 但是它值得改变不会影响到画面的改变,所以要想在子线程中控制主线程的控件的变化,还是得主线程来控制,这就要用到了handler类,<br>
* 当handler类被创建的时候 ,会关联到创建他的当前线程的消息队列<br>
*
* @param path
* 下载文件的路径
* @param saveFileDir
* 下载文件后文件的保存路径
* @throws IOException
*/
public void download(final String path, final File saveFileDir) throws IOException {
new Thread(new Runnable() {

@Override
public void run() {
try {
downLoader = new FileDownLoader(MainActivity.this, path, saveFileDir, 3);
progressBar.setMax(downLoader.getFileSize());// 设置进度条的最大滚动值为文件的最大值
downLoader.startThread();// 初始值为false,自定义的开始线程的方法,在开始线程之前判断属性值exit,true为停止现在;false为正常下载
downLoader.downLoad(new DownloadPropressListener() {

@Override
public void onDownloadSize(int downloadSize) {// 实时获取文件已经下载的长度
// TODO Auto-generated method stub
Message msg = new Message();
msg.what = 1;// 设置一个发送消息的id,避免不知道是那个消息
msg.getData().putInt("downloadSize", downloadSize);
handler.sendMessage(msg);
}
});
} catch (Exception e) {
Message msg = new Message();
msg.what = -1;// 设置消息的id,
handler.sendMessage(msg);// 发送空消息
// handler.obtainMessage(-1).sendToTarget();与上面3行代码功能一样
e.printStackTrace();
}
}
}).start();

}

/**
* 停止线程
*/
public void stop() {
if (downLoader != null) {
downLoader.stopThread();// 停止线程,让线程的exit属性值为true
Toast.makeText(MainActivity.this, "线程已停止下载", Toast.LENGTH_SHORT).show();
stop.setEnabled(false);// 停止按钮不可操作
download.setEnabled(true);// 下载按钮可错做
} else {// 如果线程未开启,或者未开始下载的时候
download.setEnabled(true);
Toast.makeText(MainActivity.this, "线程未开启", Toast.LENGTH_SHORT).show();
}
}

public class ButtonClickListener implements View.OnClickListener {

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.down:
try {
String urlPath = downloadPath.getText().toString();// 获取TextView的值,就是下载路径
if (Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {// 判断sd卡是否存在
download(urlPath, Environment.getExternalStorageDirectory());// 开始下载数据
stop.setEnabled(true);// 停止按钮可用
download.setEnabled(false);// 下载按钮不可用
} else {
Toast.makeText(MainActivity.this, R.string.sdCarderror, Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
e.printStackTrace();
}
break;
case R.id.stop:
stop();
break;

default:
break;
}
}

}
}

下载文件的线程,基于上面的原理

[java]
package com.itcast.download;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

import android.util.Log;

/**
* 下载数据的线程
*
* @author w
*
*/
public class DownloadThread extends Thread {
private static final String TAG = "DownloadThread";
private File saveFile;// 下载文件后的保存文件位置
private URL url;// 下载滴孩子
private int dataLength;// 线程开始下载的位置
private int threadId;// 线程地
private boolean finish = false;// 是否已经下载完成
private FileDownLoader downLoader;// 下载工具类
private int downLength;// 已经下载文件的长度
private boolean exit;// 控制线程停止下载

/**
*
* @param downLoader
* 自定义的文件下载器类
* @param url
* 文件下载链接
* @param saveFile
* 下载文件后的保存文件
* @param dataLength
* 每个线程的下载数据量
* @param downLength
* 线程已下载数据长度
* @param threadId
* 线程id
*/
public DownloadThread(FileDownLoader downLoader, URL url, File saveFile, int dataLength, Integer downLength, Integer threadId) {
this.url = url;
this.saveFile = saveFile;
this.dataLength = dataLength;
this.threadId = threadId;
this.downLoader = downLoader;
this.downLength = downLength;
}

@Override
public void run() {
if (downLength < dataLength) {// 如果是為完成下載
try {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(5 * 1000);// 设置超时时间
conn.setRequestMethod("GET");
conn.setRequestProperty("Charser", "UTF-8");// // 发送http协议的参数
conn.setRequestProperty("Referer", url.toString());// 发送http协议的参数
int startPosition = dataLength * (threadId - 1) + downLength;// 计算当前线程开始下载文件的位置,要加上之前下载数据量
int endPosition = dataLength * threadId - 1;// 当前线程结束的位置
conn.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition);// 设置当前线程的下载开始位置和结束位置
InputStream inputStream = conn.getInputStream();
byte[] buffer = new byte[1024];
int len = 0;
Log.i(TAG, "thread " + this.threadId + " start download from position " + startPosition);
RandomAccessFile accessFile = new RandomAccessFile(this.saveFile, "rwd");// 在本机上生成一个和下载问价一样大小的文件
accessFile.seek(startPosition);// 设置写入该文件的开始位置
//this.exit != true用于判断线程是否停止,如果停止了,就不更新数据库的下载信息了
while ((len = inputStream.read(buffer)) != -1 && this.exit != true) {
downLength += len;// 累加下载的数据量
downLoader.update(this.threadId, downLength);// 更新当前线程已经下载的数据量
downLoader.append(len);// 累计下载文件大小
}
accessFile.close();
inputStream.close();
this.finish = true;// 将该线程标志位下载完成
Log.i(TAG, "thread " + this.threadId + " download finish");
} catch (IOException e) {
// TODO Auto-generated catch block
this.downLength = -1;// 如果出现异常,让下载的数据量设为-1,在调用改线程类构造器后可做下载判断
e.printStackTrace();
Log.i(TAG, "thread " + this.threadId + " download error:" + e.toString());

}
}
}

/**
* 当前线程是否完成下载
*
* @return
*/
public boolean isFinish() {
return this.finish;
}

/**
* 下载的数据量
*
* @return
*/
public int getDownLength() {
return this.downLength;
}

/**
* 停止线程
*/
public void stopThread() {
this.exit = true;
}

/**
* 获取线程状态:false开启 true停止
*
* @return
*/
public boolean getStopThread() {
return this.exit;
}

/**
* 开启线程
*/
public void startThread() {
this.exit = false;
}
}

下载器管理类

[java]
package com.itcast.download;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.util.Log;

import com.itcast.service.FileService;

/**
* 文件下载器
*
* @author w
*
*/
public class FileDownLoader {
public static final String TAG = "FileDownLoader";
private Context context;
private FileService fileService;
private int downloadSize;// 已下载文件数据的 长度
private int fileSize;// 原始文件长度,下载文件的长度
private DownloadThread[] threads;
private File saveFile;// 保存到本地的文件
private Map<Integer, Integer> data;// 保存每个线程的id和每个线程已经下载的数据量
private int dataLength;// 每个线程下载数据的长度
private String downloadUrl;// 下载路径

public FileDownLoader(Context context, String downloadUrl, File saveFileDir, int threadCount) throws IOException {
this.context = context;
this.downloadUrl = downloadUrl;
this.data = new HashMap<Integer, Integer>();
this.fileService = new FileService(this.context);
if (!saveFileDir.exists()) {
saveFileDir.mkdirs();
}
URL url = new URL(this.downloadUrl);
this.threads = new DownloadThread[threadCount];
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(5 * 1000);// 请求超时
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);// 连接超时,不到跟上面有什么区别,
conn.setRequestProperty(
"Accept",
"image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
conn.setRequestProperty("Accept-Language", "zh-CN");
conn.setRequestProperty("Referer", this.downloadUrl);
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty(
"User-Agent",
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn.setRequestProperty("Connection", "Keep-Alive");
// 上面的http请求头部可不要,除了Referer,上面的参数主要是为了获取下载文件的大小
conn.connect();// 连接请求的地址,可不用该方法,后面的只要使用到该连接的url连接的connection对象,就会内部的自己调用该方法
if (conn.getResponseCode() == 200) {// 连接是否有相应
this.fileSize = conn.getContentLength();// 根据相应获取文件的要下载文件的大小
if (this.fileSize == 0) {// 如果为0的话要不是连接出了问题,要不就是文件大小为0,为0下了有何用,还用下载么,
throw new RuntimeException("文件大小未知:unknown file size");
}
String fileName = getFileName(conn);// 获取文件名称
// this.saveFile = new
// File(Environment.getExternalStorageDirectory().getAbsolutePath(),
// fileName);// 创建一个本地的问价对象
this.saveFile = new File(saveFileDir, fileName);// 创建一个本地的问价对象
Map<Integer, Integer> dataLog = this.fileService.getData(this.downloadUrl);// 获取该下载路径下载文件的记录
if (dataLog.size() > 0) {// 如果存在下载记录,将下载记录放到data里记录
for (Integer i : dataLog.keySet()) {
this.data.put(i, dataLog.get(i));
}
}
if (this.data != null && (this.data.size() == this.threads.length)) {// 有下载记录,并且本次下载和之前下载的线程数一样
for (int i = 0; i < this.threads.length; i++) {
this.downloadSize += this.data.get(i + 1);// 计算所有线程已经已经下载的数据量
Log.i(TAG, "Thread: " + (i + 1) + " 已经下载 " + this.data.get(i + 1));
}
Log.i(TAG, "当前总下载: " + this.downloadSize);

}
// 计算每条线程下载数据的长度
this.dataLength = this.fileSize % threadCount == 0 ? this.fileSize / threadCount : this.fileSize / threadCount + 1;// 计算每个线程要下载的数据量
} else {
throw new RuntimeException("服务器没有响应:server no respone");
}
}

/**
* 获取文件名称
*
* @param conn
* @return
*/
private String getFileName(HttpURLConnection conn) {
String fileName = this.downloadUrl.substring(this.downloadUrl.lastIndexOf("/") + 1);

return fileName;
}

/**
* 开始下载文件
*
* @param listener
* 监听下载文件数据量的变化,如果不需要知道实时下载的数据量可以设置为null;
* @return 已现在文件的大小
* @throws Exception
*/
public int downLoad(DownloadPropressListener listener) throws Exception {
try {
RandomAccessFile randOutFile = new RandomAccessFile(this.saveFile, "rwd");// 本机随机生成一个文件
if (this.fileSize > 0) {
randOutFile.setLength(this.fileSize);// 设置随机生成的文件与下载文件一样大小
}
randOutFile.close();// 关闭这个随机文件
URL url = new URL(this.downloadUrl);
if (this.data != null && this.data.size() != this.threads.length) {// 如果之前下载的线程总线程数和重新开下载的线程数不一样,就没法使用之前下载的数据,所以清空之前的数据
this.data.clear();// 清空数据
for (int i = 0; i < this.threads.length; i++) {
this.data.put(i + 1, 0);// 初始化每条线程已经下载的数据长度为0
}
}
for (int i = 0; i < this.threads.length; i++) {// 开启线程下载
int downLength = this.data.get(i + 1);
if (downLength < this.dataLength && this.downloadSize < this.fileSize) {// 判断文件是否已经下载完成和是否可以继续下载
this.threads[i] = new DownloadThread(this, url, this.saveFile, this.dataLength, this.data.get(i + 1), i + 1);
this.threads[i].setPriority(7);// 线程里的优先级,无关紧要
this.threads[i].start();// 开启线程
} else {
this.threads[i] = null;
}
}
this.fileService.save(downloadUrl, this.data);// 保存每个线程下载的数据量
boolean isFinish = true;// 作为文件是够下载完成的判断,true为未下载完成,或者下载出现异常
while (isFinish) {// 循环判断线程是否下载完成
Thread.sleep(500);
isFinish = false;// 设定所有线程一完成下载
for (int i = 0; i < this.threads.length; i++) {
if (this.threads[i] != null && !this.threads[i].isFinish()) {// 当前线程未完成下载
isFinish = true;// 如果没有完成下载,那么标志位true,继续下载,知道完成为止
if (this.threads[i].getDownLength() == -1) {// 如果是下载失败,从新下载
this.threads[i] = new DownloadThread(this, url, this.saveFile, this.dataLength, this.data.get(i + 1),
i + 1);
this.threads[i].setPriority(7);// 线程里的优先级,无关紧要
this.threads[i].start();// 开启线程
}
}
}

if (listener != null) {
listener.onDownloadSize(this.downloadSize);// 通知文件下载的长度
}
}
if (this.downloadSize == this.fileSize) {// 如果文件下载完成,清空该地址的下载记录
this.fileService.delete(this.downloadUrl);
}
} catch (Exception e) {
Log.i(TAG, e.toString());
e.printStackTrace();
throw new Exception("file download fail");
}
return this.downloadSize;
}

/**
* 更新线程下载的数据量,注意synchronized关键字,因为sqlite助手列是单列模式的,这个列子会开启好多个线程,
* 这些线程可能会同时调用这个方法, 而单例的操作者只能有一个, 所以别的线程在调用这个方法,助手类就会报database not open<br>
* 的错,加上synchronized关键字,避免同时调用,<br>
* 1,当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。
* 另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。<br>
* 2,当一个线程访问object的一个synchronized(this)同步代码块时,
* 另一个线程仍然可以访问该object中的非synchronized(this)同步代码块<br>
* 3,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(
* this)同步代码块的访问将被阻塞。<br>
* 4,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,
* 其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。然后另一线程获得对象锁,其他的暂时等待<br>
*
* @param threadId
* @param downLength
*/
public synchronized void update(int threadId, int downLength) {
this.data.put(threadId, downLength);
this.fileService.update(this.downloadUrl, this.data);
}

/**
* 累计文件下载长度,注意synchronized关键字
*
* @param len
*/
public synchronized void append(int len) {
this.downloadSize += len;

}

/**
* 获取文件的总大小
*/
public int getFileSize() {
return this.fileSize;
}

/**
* 停止线程下载
*/
public void stopThread() {
for (int i = 0; i < this.threads.length; i++) {
if (this.threads[i] != null) {
this.threads[i].stopThread();
}
}
}

/**
* 线程开始下载
*/
public void startThread() {
for (int i = 0; i < this.threads.length; i++) {
if (this.threads[i] != null) {
this.threads[i].startThread();
}
}
}
}

sqlite助手类

[java]
package com.itcast.util;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

public class DBHelper extends SQLiteOpenHelper {
private static final String BABASENAME = "dowmload.db";
private static final int DATABASEVERSION = 1;

public DBHelper(Context context, CursorFactory factory, int version) {
super(context, BABASENAME, null, DATABASEVERSION);
}

public DBHelper(Context context) {
super(context, BABASENAME, null, DATABASEVERSION);
}

@Override
public void onCreate(SQLiteDatabase db) {
/**
* downpath 下载路径 threadid 现在线程id downlength已经下载的长度
*/
db.execSQL("create table if not exists filedownlog(id integer primary key autoincrement, downpath varchar(80), threadid integer, downlength integer)");
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table if exists filedownlog");
onCreate(db);
}

}

下载文件是对数据库数据的操作类,注意更新方法的调用方法,要考虑到并发的可能性,用到了synchronized关键字

[java]
package com.itcast.service;

import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.itcast.util.DBHelper;

public class FileService {
private DBHelper dbHelper;

public FileService(Context context) {
this.dbHelper = new DBHelper(context);
}

/**
* 根据线程下载的url地址,来取得下载该资源的每个线程的已经下载的数据量
*
* @param path
* 下载文件的地址
* @return 下载该地址文件的所有线程id和每个线程已经下载的数据
*/
public Map<Integer, Integer> getData(String path) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.rawQuery("select threadid,downlength from filedownlog where downpath=?", new String[] { path });
Map<Integer, Integer> data = new HashMap<Integer, Integer>();
while (cursor.moveToNext()) {
Integer threadid = cursor.getInt(cursor.getColumnIndex("threadid"));
Integer downlength = cursor.getInt(cursor.getColumnIndex("downlength"));
data.put(threadid, downlength);
}
cursor.close();
db.close();
return data;
}

/**
* 保存下载资源的每个线程id,以及每个线程已经下载的数据量
*
* @param path
* 下载地址
* @param data
* 线程id和已下载的数据量
*/
public void save(String path, Map<Integer, Integer> data) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction();
try {
for (Integer i : data.keySet()) {
db.execSQL("insert into filedownlog(downpath,threadid,downlength) values(?,?,?)",
new Object[] { path, i, data.get(i) });
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
db.close();
}

/**
* 更新下载该path地址文件的每个线程的下载数据量
*
* @param path
* 文件下载地址
* @param data
* 下载该文件的线程id和下载量
*/
public void update(String path, Map<Integer, Integer> data) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction();
try {
for (Integer i : data.keySet()) {
db.execSQL("update filedownlog set downlength=? where downpath=? and threadid=?", new Object[] { data.get(i),
path, i });
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
db.close();
}

/**
* 如果文件下载完了 根据下载路径删除下载该地址文件的所有线程和每个线程下载的数据量信息
*
* @param path
*/
public void delete(String path) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.execSQL("delete from filedownlog where downpath=?", new Object[] { path });
db.close();
}
}
本例只要知识点,上面的业务实现原理是必须的,还有就是子线程的数据控制ui线程的ui数据更新显示,要用到handler类,和message类,

handler的功能:当handler被创建时,会关联到创建他的当前线程的消息队列,该类用以往消息队列发送消息,消息由当前的线程内部进行处理,消息的运行代码是运行在ui线程里的,r如果想让handler在处理消息的时候执行自己想要的代码,可以重写它的handleMessage方法

还有就是主线程不能运行需要长时间才能完成的任务.比如下载等什么的,一般需要长时间完成的任务都要另开线程,本例的主activity类中有说明主线程和子线程之间的关系以及参数的传值

相关文章
最新文章
热点推荐