編輯:關於Android編程
原理:
在發出請求協議的時候加上參數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類中有說明主線程和子線程之間的關系以及參數的傳值
今天在維護公司的一個APP的時候,有如下場景。彈出一個AlertDialog的時候,在系統語言是中文的時候,如下所示:彈出一個AlertDialog的時候,在系統語言是E
在Android設備上面,快速高效的顯示圖片是極為重要的。過去的幾年裡,我們在如何高效的存儲圖像這方面遇到了很多問題。圖片太大,但是手機的內存卻很小。每一個像素的R、G、
Android Studio 打包及引用 aar1、 簡述在比較大的 Android 項目的開發中,我們經常會遇到工程、jar 包等等之間相互引用的方式。一般我們通過在
效果圖知識點分析效果圖來看不復雜內容並沒多少,值得介紹一下的知識點也就下面幾個吧- 列表標題懸停- 左右列表滑動時聯動- 添加商品時的拋物線動畫- 底部彈出購物車清單-