編輯:關於Android編程
在《Android基於IIS的APK下載(三)用JSON傳輸更新數據》一文中已經從服務器中拿到了更新數據,並且呈現到了UI中,結合前面的文章及效果圖(參見下圖),可以看到UI中的更新列表一行一行的呈現,而每一行的末尾有一個行為按鈕,對應著不同的行為,這個行為要如何實現呢?
我們再看一下UpdateItemsAdapter中getView的部分代碼
updateItem.SetBehavior(isNewVersion ? UPDATE_BEHAVIORS.UPDATE : UPDATE_BEHAVIORS.NO_UPDATE); behavior_button.setEnabled(isNewVersion); behavior_button.setText(updateItem.GetBehavior()); behavior_button.setTag(updateItem); behavior_button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ExecuteBehavior(behavior_button); } });
private void ExecuteBehavior(final Button behavior_button) { try { UpdateItem updateItem = (UpdateItem) behavior_button.getTag(); if (updateItem == null) { return; } if (updateItem.GetBehavior() == UPDATE_BEHAVIORS.INSTALL) { if (updateItem.GetSavePath() == null || updateItem.GetSavePath().length() <= 0) { return; } InstallApk(updateItem.GetSavePath()); return; } else if (updateItem.GetBehavior() == UPDATE_BEHAVIORS.NO_UPDATE) { return; } final String url = updateItem.GetUrl(); final String savePath = FetchSavePath(url); final Handler downloadHandler =InitDownloadHandler(behavior_button); String aysncDownloadThreadName = RequestSp.DownLoadFileAsync(url, savePath, downloadHandler); if (aysncDownloadThreadName != null && aysncDownloadThreadName.length() > 0) { _aysncDownloadThreadNames.add(aysncDownloadThreadName); } } catch (Exception e) { behavior_button.setEnabled(true); } } private Handler InitDownloadHandler(final Button behavior_button) { Handler _downloadHandler = new Handler() { @Override public void handleMessage(Message msg) { UpdateItem updateItem = (UpdateItem) behavior_button .getTag(); switch (msg.what) { case REQUEST_MESSAGES.DOWNLOAD_START: { behavior_button.setEnabled(false); break; } case REQUEST_MESSAGES.DOWNLOAD_PERCENT: { Bundle bundle = msg.getData(); float downloadPercent = bundle .getFloat(REQUEST_KEYS.DOWNLOAD_PERCENT); behavior_button.setText(String.format("%1$.2f", downloadPercent) + "%"); break; } case REQUEST_MESSAGES.DOWNLOAD_COMPLETED: { Bundle bundle = msg.getData(); String savePath = bundle .getString(REQUEST_KEYS.DOWNLOAD_SAVE_PATH); behavior_button.setEnabled(true); behavior_button .setText(UPDATE_BEHAVIORS.INSTALL); if (updateItem != null) { updateItem.SetBehavior(UPDATE_BEHAVIORS.INSTALL); updateItem.SetSavePath(savePath); } break; } case REQUEST_MESSAGES.DOWNLOAD_EXCEPTION: { behavior_button.setEnabled(true); String info = "Download " + updateItem.GetUrl() + " Fail"; MessageBoxSp.Show(_context, info); break; } default: { behavior_button.setEnabled(true); String info = "Download " + updateItem.GetUrl() + " Fail"; MessageBoxSp.Show(_context, info); break; } } behavior_button.setTag(updateItem); } }; return _downloadHandler; } private String FetchSavePath(String url) { String saveDir = Environment.getExternalStorageDirectory() + "/download/"; File saveDirfile = new File(saveDir); if (!saveDirfile.exists()) { saveDirfile.mkdirs(); } int fileNameStart = url.lastIndexOf("/"); String fileName = url.substring(fileNameStart + 1); return saveDir + fileName; } private void InstallApk(String filePath) { IntentSp.StartActivity(_context, Uri.fromFile(new File(filePath)), "application/vnd.android.package-archive", false); }
注:
1、從behavior_button的tag中獲取updateItem,然後獲取相應的行為進行操作。
2、如果是INSTALL行為,將會調用InstallApk。如果不是INSTALL行為,而是NO_UPDATE行為,則不執行任何動作。如果這兩個動作都不是,則是UPDATE行為,即認為是要下載數據。所以會提取URL,並根據URL獲取相應的savePath。
3、在數據下載時,每一個下載都會開啟一個線程,並不斷更新下載數據的百分比。由於要在線程中更新UI,所以要用到handler來處理。在InitDownloadHandler中實現了下載的handler.
4、由於每一個下載都會開啟一個線程,所以在RequestSp.DownLoadFileAsync中返回了線程的名字(采用UUID來命名以保證唯一性),並將該名字記錄起來,在UpdateItemsAdapter釋放的時候(即在finalize函數中),關閉線程,以更好的控制下載線程。下面是finalize的代碼。
private List_aysncDownloadThreadNames=null; public UpdateItemsAdapter(List updateItems, Context context) { _updateItems = updateItems; _context = context; _aysncDownloadThreadNames=new ArrayList (); } @Override protected void finalize() throws Throwable { // TODO Auto-generated method stub super.finalize(); if (_aysncDownloadThreadNames == null || _aysncDownloadThreadNames.size() <= 0) { return; } while (_aysncDownloadThreadNames.size() > 0) { String asyncDownloadThreadName = _aysncDownloadThreadNames.get(0); RequestSp.AbortAsyncDownload(asyncDownloadThreadName); _aysncDownloadThreadNames.remove(0); } }
package com.kitsp.httpsp; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.UUID; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import android.os.Bundle; import android.os.Handler; import android.os.Message; public class RequestSp { private final static int HTTP_200 = 200; private static HashMap_asyncDownloadFlags = new HashMap (); public static InputStream Get(String url) throws Exception { HttpEntity httpEntity = GetHttpEntity(url); if (httpEntity == null) { return null; } return httpEntity.getContent(); } public static HttpEntity GetHttpEntity(String url) throws Exception { HttpGet httpGet = new HttpGet(url); HttpClient httpClient = new DefaultHttpClient(); HttpResponse httpResp = httpClient.execute(httpGet); if (httpResp.getStatusLine().getStatusCode() == HTTP_200) { //Get back data. // String result = EntityUtils.toString(httpResp.getEntity(), // "UTF-8"); // return result; return httpResp.getEntity(); } else { return null; } } public static boolean DownLoadFile(String httpUrl, String savePath) { final File file = new File(savePath); try { URL url = new URL(httpUrl); try { HttpURLConnection conn = (HttpURLConnection) url .openConnection(); if (conn.getResponseCode() >= 400) { return false; } InputStream is = conn.getInputStream(); FileOutputStream fos = new FileOutputStream(file); long length = conn.getContentLength(); byte[] buf = new byte[1024]; conn.connect(); int readCount = 0; while (true) { if (is == null) { break; } readCount = is.read(buf); if (readCount <= 0) { break; } fos.write(buf, 0, readCount); } conn.disconnect(); fos.close(); is.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); return false; } } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); return false; } return true; } /** * * @param httpUrl * @param savePath * @param handler * :Async handler * @return Handler:Control thread in outer. */ public static String DownLoadFileAsync(final String httpUrl, final String savePath, final Handler handler) { if (handler == null) { return null; } final String threadName = UUID.randomUUID().toString(); Thread downloadThread = new Thread(new Runnable() { @Override public void run() { DownloadDataAsync(httpUrl, savePath, handler, threadName); } }); downloadThread.setName(threadName); _asyncDownloadFlags.put(threadName, true); downloadThread.start(); return threadName; } public static void AbortAsyncDownload(String asyncDownloadThreadName) { if (asyncDownloadThreadName == null || asyncDownloadThreadName.length() <= 0) { return; } _asyncDownloadFlags.remove(asyncDownloadThreadName); } private static void DownloadDataAsync(String httpUrl, final String savePath, final Handler handler, final String threadName) { File file = new File(savePath); HttpURLConnection conn; try { final URL url = new URL(httpUrl); conn = (HttpURLConnection) url.openConnection(); if (conn.getResponseCode() >= 400) { handler.sendEmptyMessage(REQUEST_MESSAGES.DOWNLOAD_EXCEPTION); return; } InputStream is = conn.getInputStream(); FileOutputStream fos = new FileOutputStream(file); long totalCount = conn.getContentLength(); byte[] buf = new byte[1024]; conn.connect(); int readCount = 0; int downloadedCount = 0; float percent = 0; Message msg = null; Bundle bundle = null; handler.sendEmptyMessage(REQUEST_MESSAGES.DOWNLOAD_START); while (true) { if(_asyncDownloadFlags.isEmpty()){ break; } if(!_asyncDownloadFlags.get(threadName)){ break; } if (is == null) { break; } readCount = is.read(buf); downloadedCount += readCount; percent = (float) (downloadedCount * 1.0 / totalCount * 100); msg = new Message(); msg.what = REQUEST_MESSAGES.DOWNLOAD_PERCENT; bundle = new Bundle(); bundle.putFloat(REQUEST_KEYS.DOWNLOAD_PERCENT, percent); msg.setData(bundle); handler.sendMessage(msg); if (readCount <= 0) { break; } fos.write(buf, 0, readCount); } conn.disconnect(); fos.close(); is.close(); msg = new Message(); msg.what = REQUEST_MESSAGES.DOWNLOAD_COMPLETED; bundle = new Bundle(); bundle.putString(REQUEST_KEYS.DOWNLOAD_SAVE_PATH, savePath); msg.setData(bundle); handler.sendMessage(msg); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); handler.sendEmptyMessage(REQUEST_MESSAGES.DOWNLOAD_EXCEPTION); return; } } }
2、在AbortAsyncDownload中會根據線程的名字移除相應的項。這樣在該項移除後,線程就無法獲取到該標志,從而結束。當然如果要確保線程安全,這裡的_asyncDownloadFlags以及前文的_aysncDownloadThreadNames需要使用線程安全的對象來代替,不然有可能會引發競態等不可預料的結果。
REQUEST_MESSAGES.java
package com.kitsp.httpsp; public class REQUEST_MESSAGES { public final static int DOWNLOAD_START=1001; public final static int DOWNLOAD_PERCENT=1002; public final static int DOWNLOAD_COMPLETED=1003; public final static int DOWNLOAD_EXCEPTION=1004; public final static int DOWNLOAD_ABORT=1005; }
package com.kitsp.httpsp; public class REQUEST_KEYS { public final static String DOWNLOAD_PERCENT="DOWNLOAD_PERCENT"; public final static String DOWNLOAD_SAVE_PATH="DOWNLOAD_SAVE_PATH"; public final static String DOWNLOAD_CONTROL="DOWNLOAD_CONTROL"; }
package com.kitsp.contentsp; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.Uri; public class IntentSp { /** * * @param activity * @param isSaveActivityToHistory * true:save activity to history.System may back to the activity * when other activity finish. false:no save. */ public static void RestartActivity(Activity activity, boolean isSaveActivityToHistory) { if (activity == null) { return; } Intent intent = new Intent(); String packageName = activity.getPackageName(); String className = activity.getLocalClassName(); String componentClassName = packageName + "." + className; if (className != null && className.split(".").length > 0) { componentClassName = className; } ComponentName componentName = new ComponentName(packageName, componentClassName); intent.setComponent(componentName); if (!isSaveActivityToHistory) { intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); } activity.startActivity(intent); activity.finish(); return; } /** * * @param context * @param cls * @param isSaveActivityToHistory * true:save activity to history.System may back to the activity * when other activity finish. false:no save. */ public static void StartActivity(Context context, Class> cls, boolean isSaveActivityToHistory) { if (context == null || cls == null) { return; } Intent intent = new Intent(); if (!isSaveActivityToHistory) { intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); } intent.setClass(context, cls); context.startActivity(intent); } /** * * @param context * @param action * @param isSaveActivityToHistory * true:save activity to history.System may back to the activity * when other activity finish. false:no save. */ public static void StartActivity(Context context, String action, boolean isSaveActivityToHistory) { if (context == null || action == null) { return; } Intent intent = new Intent(action); if (!isSaveActivityToHistory) { intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); } context.startActivity(intent); } /** * * @param context * @param packageName * @param className * @param isSaveActivityToHistory * true:save activity to history.System may back to the activity * when other activity finish. false:no save. */ public static void StartActivity(Context context, String packageName, String className, boolean isSaveActivityToHistory) { if (context == null) { return; } if (packageName == null || packageName == "") { return; } if (className == null || className == "") { return; } Intent intent = new Intent(); if (!isSaveActivityToHistory) { intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); } ComponentName cn = new ComponentName(packageName, className); if (cn != null) { intent.setComponent(cn); context.startActivity(intent); } } public static void StartActivity(Context context, Uri data, String type, boolean isSaveActivityToHistory) { if (context == null) { return; } if(data==null) { return; } if(type==null||type.length()<=0) { return; } Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(data, type); if (!isSaveActivityToHistory) { intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); } context.startActivity(intent); } }
附上JSON的數據格式
{ "Items":[ { "Name":"TestApk", "FeaturePackage":"com.example.apkupdate", "Version":2.1.1.8, "Url":"http://192.168.1.5:9000/TestApk.apk" }, { "Name":"TestApk2", "FeaturePackage":"com.example.apkupdate", "Version":1.1.1.9, "Url":"http://192.168.1.5:9000/TestApk2.apk" }, { "Name":"TestApk3", "FeaturePackage":"com.example.apkupdate3", "Version":2.1.1.0, "Url":"http://192.168.1.5:9000/TestApk3.apk" }, { "Name":"TestApk4", "FeaturePackage":"com.example.apkupdate3", "Version":2.1.1.3, "Url":"http://192.168.1.5:9000/TestApk4.apk" } ] }
轉載請注明出處 Android基於IIS的APK下載(四)數據下載
完整代碼在此處下載https://github.com/sparkleDai/ApkUpdate
相關源碼framework/base/core/java/andorid/os/Handler.javaframework/base/core/java/andorid/
一、前言Android 實現衛星式菜單也叫弧形菜單,主要要做的工作如下:1.動畫的處理2.自定義ViewGroup來實現衛星式菜單View(1)自定義屬性 &n
1 功能描述類似王者榮耀,按下的技能如果是需要預判的或者是可以選擇單一目標,產生一個搖桿,在地形上顯示輔助的UI提示。存在以下幾種情況:1.扇形范圍技能 2.方
package com.gc.adapterviewflipperdemo; /** * 功能:自動播放的圖片庫 * @author Android將軍 */ /*