Android基于IIS的APK下载(四)数据下载

在《Android基于IIS的APK下载(三)用JSON传输更新数据》一文中已经从服务器中拿到了更新数据,并且呈现到了UI中,结合前面的文章及效果图(参见下图),可以看到UI中的更新列表一行一行的呈现,而每一行的末尾有一个行为按钮,对应着不同的行为,这个行为要如何实现呢?Android基于IIS的APK下载(四)数据下载

我们再看一下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;
}


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":"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下载(四)数据下载

完整代码在此处下载https://github.com/sparkleDai/ApkUpdate

Android基于IIS的APK下载(四)数据下载

上一篇:Android基于IIS的APK下载(五)IIS的配置


下一篇:对小程序wx.request() 进行封装,使其支持公共前置操作和公共响应操作