在《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); } });
代码中可以看到,updateItem有设置行为的动作,而这个行为是根据是否有新版本来设置的。之后该行为会呈现到behavior_button中,并且将updateItem设置到behavior_button的tag中,还设置了单击事件,事件里面调用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<String> _aysncDownloadThreadNames=null; public UpdateItemsAdapter(List<UpdateItem> updateItems, Context context) { _updateItems = updateItems; _context = context; _aysncDownloadThreadNames=new ArrayList<String>(); } @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); } }
RequestSp.java
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<String, Boolean> _asyncDownloadFlags = new HashMap<String, Boolean>(); 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; } } }
1、每调用一次DownLoadFileAsync,就会启支一个线程,并且生成一个UUID作为线程的名字,记录到_asyncDownloadFlags中,将对应的标志设轩为true。该标志控制着线程的运行。
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"; }
前面在InstallApk中还调用了IntentSp中的方法,这是封装到一个包里的,代码附上。
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" } ] }
现在数据下载已经实现了,还剩最后一关,IIS的配置。请参看下文Android基于IIS的APK下载(五)IIS的配置
转载请注明出处 Android基于IIS的APK下载(四)数据下载