Android基於IIS的APK下載(四)數據下載

在《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) {
				_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) {
			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 _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() = 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;
}

REQUEST_KEYS.java

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":"https://192.168.1.5:9000/TestApk.apk"
	},
  	{
		"Name":"TestApk2",
		"FeaturePackage":"com.example.apkupdate",
		"Version":1.1.1.9,
		"Url":"https://192.168.1.5:9000/TestApk2.apk"
	},
	{
		"Name":"TestApk3",
		"FeaturePackage":"com.example.apkupdate3",
		"Version":2.1.1.0,
		"Url":"https://192.168.1.5:9000/TestApk3.apk"
	},
	{
		"Name":"TestApk4",
		"FeaturePackage":"com.example.apkupdate3",
		"Version":2.1.1.3,
		"Url":"https://192.168.1.5:9000/TestApk4.apk"
	}
	]
	
}

現在數據下載已經實現瞭,還剩最後一關,IIS的配置。請參看下文Android基於IIS的APK下載(五)IIS的配置

轉載請註明出處 Android基於IIS的APK下載(四)數據下載

完整代碼在此處下載https://github.com/sparkleDai/ApkUpdate

發佈留言