- 浏览: 67967 次
- 性别:
- 来自: 深圳
最新评论
-
andy_叶:
<div class="quote_title ...
android源码中混淆编译 -
hold_on:
有 android proguard 语法文档吗?google ...
android源码中混淆编译
前不久接到个任务,在我们的app里面添加更新模块,在之前的版本中,我们的更新都是直接通过浏览器下载apk包来安装更新的,我想各位很大一部分应用的更新方法都是这样,因为它简单、方便,但是他也有许多不好的地方,比如需要用户跳转到浏览器页面、下载不可控、网络不好的情况的下失败无法续传,退出浏览器就无法接着下了等。。
于是我们这个更新模块的需求就来了
1.下载后台进行,退出我们应用下载任务依旧能继续执行操作
2.下载文件支持断点续传
3.下载任务支持没有安装sdcard时也可下载更新
4.notify栏提示操作
对几个需求稍作分析,解决方法如下:
1.下载更新的线程放到一个service中去,service的好处是不易被系统回收,而且也容易操作。我们需要先在AndroidMainfest.xml文件中去注册这个service
- <service android:name="com.dj.app.UpdateService"/>
<service android:name="com.dj.app.UpdateService"/>
2.断点续传,请求头中有个重要的参数range,代表的意思的我要取的数据的范围,这个需要服务器支持,默认情况都是支持的。我的思路是下载前先获取文件包大小,然后检查是否已经有下载好的部分了,没有就从头开始,有就接着下。
- request.addHeader("Range", "bytes=" + downLength + "-");
request.addHeader("Range", "bytes=" + downLength + "-");
为了支持断点续传,我将下载好的文件版本号、文件长度都以prefrence的形似保存下来,下次更新如果还是这个版本就接着下,如果又有更新的了就删掉重下。
- private void checkTemFile() {
- existTemFileVersionCode = preferences().getInt(
- UPDATE_FILE_VERSIONCODE, 0);
- if (newestVersionCode == existTemFileVersionCode
- || newestVersionCode == TEST) {
- File temFile = new File(context.getFilesDir(),
- Integer.valueOf(newestVersionCode) + ".apk");
- if (!temFile.exists()) {
- saveLogFile(newestVersionCode, 0);
- }
- } else {
- deleteApkFile(existTemFileVersionCode);
- saveLogFile(newestVersionCode, 0);
- }
- }
private void checkTemFile() { existTemFileVersionCode = preferences().getInt( UPDATE_FILE_VERSIONCODE, 0); if (newestVersionCode == existTemFileVersionCode || newestVersionCode == TEST) { File temFile = new File(context.getFilesDir(), Integer.valueOf(newestVersionCode) + ".apk"); if (!temFile.exists()) { saveLogFile(newestVersionCode, 0); } } else { deleteApkFile(existTemFileVersionCode); saveLogFile(newestVersionCode, 0); } }
3.无sdcard时也能下载,那只能将apk包下载到系统内存中
- context.getFilesDir();
context.getFilesDir();
这样创建的文件在/data/data/应用包名/files
伪代码:
- if (downLength > 0) {//接着上次的下载
- outStream = context.openFileOutput(Integer.valueOf(newestVersionCode) + ".apk",
- Context.MODE_APPEND+ Context.MODE_WORLD_READABLE);
- } else {//从头开始下载
- outStream = context.openFileOutput(Integer.valueOf(newestVersionCode) + ".apk",Context.MODE_WORLD_READABLE);
- }
if (downLength > 0) {//接着上次的下载 outStream = context.openFileOutput(Integer.valueOf(newestVersionCode) + ".apk", Context.MODE_APPEND+ Context.MODE_WORLD_READABLE); } else {//从头开始下载 outStream = context.openFileOutput(Integer.valueOf(newestVersionCode) + ".apk",Context.MODE_WORLD_READABLE); }
4.notify,我设定了一个更新notify的线程专门去观察下载线程的进度,每一分钟更新一次notify中的进度条。
- new Thread() {
- public void run() {
- try {
- boolean notFinish = true;
- while (notFinish) {
- Thread.sleep(1000);
- notFinish = false;
- if (downloadThread == null) {
- break;
- }
- if (!downloadThread.downFinish) {
- notFinish = true;
- }
- downloadThread.showNotification();
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- stopSelf();
- }
- };
- }.start();
new Thread() { public void run() { try { boolean notFinish = true; while (notFinish) { Thread.sleep(1000); notFinish = false; if (downloadThread == null) { break; } if (!downloadThread.downFinish) { notFinish = true; } downloadThread.showNotification(); } } catch (Exception e) { e.printStackTrace(); } finally { stopSelf(); } }; }.start();
downloadThread就是我的下载线程了因为要对不同进度时有不同的控制,这个可以通过notification.contentIntent来进行设定,比如100%的时候,我想要用户点击通知栏,即可进行安装,则应该这样做
- notification.tickerText = "下载完成";
- notification.when = System.currentTimeMillis();
- Intent notificationIntent = new Intent();
- notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- notificationIntent
- .setAction(android.content.Intent.ACTION_VIEW);
- String type = "application/vnd.android.package-archive";
- notificationIntent.setDataAndType(
- Uri.parse("file:///data/data/"
- + context.getPackageName()
- + "/files/"
- + (Integer.valueOf(newestVersionCode)
- .toString()) + ".apk"), type);
- notification.contentView = rv;
- notification.flags = Notification.FLAG_AUTO_CANCEL;
- notification.contentView
- .setProgressBar(
- R.id.update_notification_progressbar, 100,
- p, false);
- notification.contentView.setTextViewText(
- R.id.update_notification_progresstext, p + "%");
- notification.contentView.setTextViewText(
- R.id.update_notification_title, "下载完成,点击安装");
- PendingIntent contentIntent = PendingIntent.getActivity(
- context, 0, notificationIntent, 0);
- notification.contentIntent = contentIntent;
notification.tickerText = "下载完成"; notification.when = System.currentTimeMillis(); Intent notificationIntent = new Intent(); notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); notificationIntent .setAction(android.content.Intent.ACTION_VIEW); String type = "application/vnd.android.package-archive"; notificationIntent.setDataAndType( Uri.parse("file:///data/data/" + context.getPackageName() + "/files/" + (Integer.valueOf(newestVersionCode) .toString()) + ".apk"), type); notification.contentView = rv; notification.flags = Notification.FLAG_AUTO_CANCEL; notification.contentView .setProgressBar( R.id.update_notification_progressbar, 100, p, false); notification.contentView.setTextViewText( R.id.update_notification_progresstext, p + "%"); notification.contentView.setTextViewText( R.id.update_notification_title, "下载完成,点击安装"); PendingIntent contentIntent = PendingIntent.getActivity( context, 0, notificationIntent, 0); notification.contentIntent = contentIntent;
下面我将下载线程完整的代码贴出来
- class DownloadThread extends Thread {
- private static final String UPDATE_FILE_VERSIONCODE = "updateTemFileVersionCode";
- private static final String UPDATE_FILE_LENGTH = "updateTemFileLength";
- private static final String TEST_UPDATE_FILE_LENGTH = "testupdatefilelength";
- private static final int BUFFER_SIZE = 1024;
- private static final int NETWORK_CONNECTION_TIMEOUT = 15000;
- private static final int NETWORK_SO_TIMEOUT = 15000;
- private int newestVersionCode, existTemFileVersionCode;
- private String downUrl;
- private int fileLength = Integer.MAX_VALUE;
- private int downLength;
- private boolean downFinish;
- private static final int CHECK_FAILED = -1;
- private static final int CHECK_SUCCESS = 0;
- private static final int CHECK_RUNNING = 1;
- private int checkStatus;
- private boolean isChecking = false;
- private Context context;
- private boolean isPercentZeroRunning = false;
- private boolean stop;
- private Object block = new Object();
- private boolean receiverRegistered = false;
- private NotificationManager mNM;
- private RemoteViews rv;
- public DownloadThread(Context context, String downUrl, int versionCode) {
- super("DownloadThread");
- this.downUrl = downUrl;
- this.newestVersionCode = versionCode;
- this.context = context;
- this.mNM = (NotificationManager) context
- .getSystemService(Context.NOTIFICATION_SERVICE);
- this.rv = new RemoteViews(context.getPackageName(), R.layout.notify);
- this.downFinish = false;
- }
- private void checkTemFile() {
- existTemFileVersionCode = preferences().getInt(
- UPDATE_FILE_VERSIONCODE, 0);
- if (newestVersionCode == existTemFileVersionCode
- || newestVersionCode == TEST) {
- File temFile = new File(context.getFilesDir(),
- Integer.valueOf(newestVersionCode) + ".apk");
- if (!temFile.exists()) {
- saveLogFile(newestVersionCode, 0);
- }
- } else {
- deleteApkFile(existTemFileVersionCode);
- saveLogFile(newestVersionCode, 0);
- }
- }
- private void deleteApkFile(int existVersionCode) {
- File temFile = new File(context.getFilesDir(),
- Integer.valueOf(existVersionCode) + ".apk");
- if (temFile.exists()) {
- temFile.delete();
- }
- }
- private SharedPreferences preferences() {
- return context.getSharedPreferences(context.getPackageName(),
- Context.MODE_WORLD_READABLE | Context.MODE_WORLD_WRITEABLE);
- }
- private void saveLogFile(int versionCode, int downloadLength) {
- SharedPreferences.Editor edit = preferences().edit();
- if (versionCode == TEST) {
- edit.putInt(TEST_UPDATE_FILE_LENGTH, downloadLength);
- } else {
- edit.putInt(UPDATE_FILE_VERSIONCODE, versionCode);
- edit.putInt(UPDATE_FILE_LENGTH, downloadLength);
- }
- edit.commit();
- }
- @Override
- public void run() {
- checkTemFile();
- this.stop = false;
- while (!downFinish) {
- Log.i(TAG, "download thread start : while()");
- if (newestVersionCode == TEST) {
- downLength = preferences().getInt(TEST_UPDATE_FILE_LENGTH,
- 0);
- } else {
- downLength = preferences().getInt(UPDATE_FILE_LENGTH, 0);
- }
- InputStream is = null;
- FileOutputStream outStream = null;
- try {
- // check the network
- ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
- NetworkInfo ni = cm.getActiveNetworkInfo();
- boolean con = ni == null ? false : ni
- .isConnectedOrConnecting();
- synchronized (block) {
- if (!con) {
- context.registerReceiver(
- receiver,
- new IntentFilter(
- ConnectivityManager.CONNECTIVITY_ACTION));
- receiverRegistered = true;
- try {
- Log.i(TAG, "network is not ok : block.wait()");
- block.wait();
- } catch (InterruptedException e1) {
- }
- }
- }
- if (fileLength == Integer.MAX_VALUE) {
- URL url = new URL(downUrl);
- HttpURLConnection conn = (HttpURLConnection) url
- .openConnection();
- if (conn.getResponseCode() / 100 == 2
- && conn.getContentLength() != -1) {
- fileLength = conn.getContentLength();
- Log.i(TAG, "getContentLength == " + fileLength);
- } else {
- Thread.sleep(5000);
- Log.i(TAG, "getContentLength failed : retry in 5 second later");
- continue;
- }
- }
- HttpClient httpClient = new DefaultHttpClient();
- HttpGet request = new HttpGet(downUrl);
- request.addHeader("Range", "bytes=" + downLength + "-");
- if (downLength < fileLength) {
- HttpHost proxy = HttpBase.globalProxy();
- HttpParams httpParams = request.getParams();
- HttpConnectionParams.setConnectionTimeout(httpParams,
- NETWORK_CONNECTION_TIMEOUT);
- HttpConnectionParams.setSoTimeout(httpParams,
- NETWORK_SO_TIMEOUT);
- ConnRouteParams.setDefaultProxy(request.getParams(),
- proxy);
- HttpResponse response = httpClient.execute(request);
- Log.i(TAG, "getContent's response status == "
- + response.getStatusLine().getStatusCode());
- if (response.getStatusLine().getStatusCode() / 100 != 2) {
- continue;
- }
- HttpEntity entity = response.getEntity();
- is = entity.getContent();
- byte[] buffer = new byte[BUFFER_SIZE];
- int offset = 0;
- if (downLength > 0) {
- outStream = context
- .openFileOutput(
- Integer.valueOf(newestVersionCode)
- + ".apk",
- Context.MODE_APPEND
- + Context.MODE_WORLD_READABLE);
- } else {
- outStream = context
- .openFileOutput(
- Integer.valueOf(newestVersionCode)
- + ".apk",
- Context.MODE_WORLD_READABLE);
- }
- while ((offset = is.read(buffer, 0, BUFFER_SIZE)) != -1
- && !stop) {
- outStream.write(buffer, 0, offset);
- downLength += offset;
- }
- }
- if (downLength == fileLength) {
- File apkFile = new File(context.getFilesDir(),
- Integer.valueOf(newestVersionCode) + ".apk");
- if (isApkFileOK(apkFile)) {
- checkStatus = CHECK_SUCCESS;
- } else {
- deleteApkFile(newestVersionCode);
- saveLogFile(existTemFileVersionCode, 0);
- checkStatus = CHECK_FAILED;
- }
- this.downFinish = true;
- }
- } catch (Exception e) {
- } finally {
- saveLogFile(newestVersionCode, downLength);
- if (receiverRegistered) {
- context.unregisterReceiver(receiver);
- receiverRegistered = false;
- }
- if (stop || downFinish) {
- break;
- }
- try {
- if (outStream != null) {
- outStream.close();
- }
- if (is != null) {
- is.close();
- }
- } catch (IOException e) {
- }
- }
- }
- }
- @Override
- public void interrupt() {
- synchronized (block) {
- Log.i(TAG, "block.notify()");
- block.notify();
- }
- }
- private void cancel() {
- stop = true;
- mNM.cancel(MOOD_NOTIFICATIONS);
- }
- private boolean isApkFileOK(File file) {
- checkStatus = CHECK_RUNNING;
- // first check the file header
- /*if (file.isDirectory() || !file.canRead() || file.length() < 4) {
- return false;
- }
- DataInputStream in = null;
- try {
- in = new DataInputStream(new BufferedInputStream(
- new FileInputStream(file)));
- int test = in.readInt();
- if (test != 0x504b0304)
- return false;
- } catch (IOException e) {
- return false;
- } finally {
- try {
- in.close();
- } catch (IOException e) {
- }
- }*/
- // second unZip file to check(without saving)
- boolean result = unzip(file);
- isChecking = false;
- return result;
- }
- private boolean unzip(File unZipFile) {
- boolean succeed = true;
- ZipInputStream zin = null;
- ZipEntry entry = null;
- try {
- zin = new ZipInputStream(new FileInputStream(unZipFile));
- boolean first = true;
- while (true) {
- if ((entry = zin.getNextEntry()) == null) {
- if (first)
- succeed = false;
- break;
- }
- first = false;
- if (entry.isDirectory()) {
- zin.closeEntry();
- continue;
- }
- if (!entry.isDirectory()) {
- byte[] b = new byte[1024];
- @SuppressWarnings("unused")
- int len = 0;
- while ((len = zin.read(b)) != -1) {
- }
- zin.closeEntry();
- }
- }
- } catch (IOException e) {
- succeed = false;
- } finally {
- if (null != zin) {
- try {
- zin.close();
- } catch (IOException e) {
- }
- }
- }
- return succeed;
- }
- public void showNotification() {
- float result = (float) downLength / (float) fileLength;
- int p = (int) (result * 100);
- if (p == 0 && isPercentZeroRunning || p == 100 && isChecking) {
- return;
- } else if (p != 0) {
- isPercentZeroRunning = false;
- }
- Notification notification = new Notification(R.drawable.icon, null,
- 0);
- if (p == 100) {
- if (checkStatus == CHECK_RUNNING) {
- notification.tickerText = "开始检查下载文件";
- notification.when = System.currentTimeMillis();
- notification.flags = notification.flags
- | Notification.FLAG_ONGOING_EVENT
- | Notification.FLAG_NO_CLEAR;
- notification.contentView = rv;
- notification.contentView.setProgressBar(
- R.id.update_notification_progressbar, 100, p, true);
- notification.contentView.setTextViewText(
- R.id.update_notification_title, "正在检查下载文件...");
- PendingIntent contentIntent = PendingIntent.getActivity(
- context, 0, null, 0);
- notification.contentIntent = contentIntent;
- isChecking = true;
- } else if (checkStatus == CHECK_FAILED) {
- notification.tickerText = "文件验证失败!";
- notification.when = System.currentTimeMillis();
- notification.flags = notification.flags
- | Notification.FLAG_AUTO_CANCEL;
- notification.contentView = rv;
- notification.contentView.setProgressBar(
- R.id.update_notification_progressbar, 100, p, false);
- notification.contentView.setTextViewText(
- R.id.update_notification_title, "文件验证失败,请重新下载");
- Intent notificationIntent = new Intent(context,
- UpdateService.class);
- notificationIntent.setAction(ACTION_STOP);
- PendingIntent contentIntent = PendingIntent.getService(
- context, 0, notificationIntent, 0);
- notification.contentIntent = contentIntent;
- } else {
- notification.tickerText = "下载完成";
- notification.when = System.currentTimeMillis();
- Intent notificationIntent = new Intent();
- notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- notificationIntent
- .setAction(android.content.Intent.ACTION_VIEW);
- String type = "application/vnd.android.package-archive";
- notificationIntent.setDataAndType(
- Uri.parse("file:///data/data/"
- + context.getPackageName()
- + "/files/"
- + (Integer.valueOf(newestVersionCode)
- .toString()) + ".apk"), type);
- notification.contentView = rv;
- notification.flags = Notification.FLAG_AUTO_CANCEL;
- notification.contentView
- .setProgressBar(
- R.id.update_notification_progressbar, 100,
- p, false);
- notification.contentView.setTextViewText(
- R.id.update_notification_progresstext, p + "%");
- notification.contentView.setTextViewText(
- R.id.update_notification_title, "下载完成,点击安装");
- PendingIntent contentIntent = PendingIntent.getActivity(
- context, 0, notificationIntent, 0);
- notification.contentIntent = contentIntent;
- }
- } else if (p == 0) {
- notification.tickerText = "准备下载";
- notification.when = System.currentTimeMillis();
- Intent notificationIntent = new Intent(context,
- UpdateService.class);
- notificationIntent.setAction(ACTION_STOP);
- notification.flags = notification.flags
- | Notification.FLAG_ONGOING_EVENT;
- notification.contentView = rv;
- notification.contentView.setProgressBar(
- R.id.update_notification_progressbar, 100, p, true);
- notification.contentView.setTextViewText(
- R.id.update_notification_title, "正在准备下载(点击取消)");
- PendingIntent contentIntent = PendingIntent.getService(context,
- 0, notificationIntent, 0);
- notification.contentIntent = contentIntent;
- isPercentZeroRunning = true;
- } else {
- notification.tickerText = "开始下载";
- notification.when = System.currentTimeMillis();
- Intent notificationIntent = new Intent(context,
- UpdateService.class);
- notificationIntent.setAction(ACTION_STOP);
- notification.contentView = rv;
- notification.flags = notification.flags
- | Notification.FLAG_ONGOING_EVENT;
- notification.contentView.setProgressBar(
- R.id.update_notification_progressbar, 100, p, false);
- notification.contentView.setTextViewText(
- R.id.update_notification_progresstext, p + "%");
- notification.contentView.setTextViewText(
- R.id.update_notification_title, "正在下载(点击取消)");
- PendingIntent contentIntent = PendingIntent.getService(context,
- 0, notificationIntent, 0);
- notification.contentIntent = contentIntent;
- }
- mNM.notify(MOOD_NOTIFICATIONS, notification);
- }
- }
class DownloadThread extends Thread { private static final String UPDATE_FILE_VERSIONCODE = "updateTemFileVersionCode"; private static final String UPDATE_FILE_LENGTH = "updateTemFileLength"; private static final String TEST_UPDATE_FILE_LENGTH = "testupdatefilelength"; private static final int BUFFER_SIZE = 1024; private static final int NETWORK_CONNECTION_TIMEOUT = 15000; private static final int NETWORK_SO_TIMEOUT = 15000; private int newestVersionCode, existTemFileVersionCode; private String downUrl; private int fileLength = Integer.MAX_VALUE; private int downLength; private boolean downFinish; private static final int CHECK_FAILED = -1; private static final int CHECK_SUCCESS = 0; private static final int CHECK_RUNNING = 1; private int checkStatus; private boolean isChecking = false; private Context context; private boolean isPercentZeroRunning = false; private boolean stop; private Object block = new Object(); private boolean receiverRegistered = false; private NotificationManager mNM; private RemoteViews rv; public DownloadThread(Context context, String downUrl, int versionCode) { super("DownloadThread"); this.downUrl = downUrl; this.newestVersionCode = versionCode; this.context = context; this.mNM = (NotificationManager) context .getSystemService(Context.NOTIFICATION_SERVICE); this.rv = new RemoteViews(context.getPackageName(), R.layout.notify); this.downFinish = false; } private void checkTemFile() { existTemFileVersionCode = preferences().getInt( UPDATE_FILE_VERSIONCODE, 0); if (newestVersionCode == existTemFileVersionCode || newestVersionCode == TEST) { File temFile = new File(context.getFilesDir(), Integer.valueOf(newestVersionCode) + ".apk"); if (!temFile.exists()) { saveLogFile(newestVersionCode, 0); } } else { deleteApkFile(existTemFileVersionCode); saveLogFile(newestVersionCode, 0); } } private void deleteApkFile(int existVersionCode) { File temFile = new File(context.getFilesDir(), Integer.valueOf(existVersionCode) + ".apk"); if (temFile.exists()) { temFile.delete(); } } private SharedPreferences preferences() { return context.getSharedPreferences(context.getPackageName(), Context.MODE_WORLD_READABLE | Context.MODE_WORLD_WRITEABLE); } private void saveLogFile(int versionCode, int downloadLength) { SharedPreferences.Editor edit = preferences().edit(); if (versionCode == TEST) { edit.putInt(TEST_UPDATE_FILE_LENGTH, downloadLength); } else { edit.putInt(UPDATE_FILE_VERSIONCODE, versionCode); edit.putInt(UPDATE_FILE_LENGTH, downloadLength); } edit.commit(); } @Override public void run() { checkTemFile(); this.stop = false; while (!downFinish) { Log.i(TAG, "download thread start : while()"); if (newestVersionCode == TEST) { downLength = preferences().getInt(TEST_UPDATE_FILE_LENGTH, 0); } else { downLength = preferences().getInt(UPDATE_FILE_LENGTH, 0); } InputStream is = null; FileOutputStream outStream = null; try { // check the network ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); NetworkInfo ni = cm.getActiveNetworkInfo(); boolean con = ni == null ? false : ni .isConnectedOrConnecting(); synchronized (block) { if (!con) { context.registerReceiver( receiver, new IntentFilter( ConnectivityManager.CONNECTIVITY_ACTION)); receiverRegistered = true; try { Log.i(TAG, "network is not ok : block.wait()"); block.wait(); } catch (InterruptedException e1) { } } } if (fileLength == Integer.MAX_VALUE) { URL url = new URL(downUrl); HttpURLConnection conn = (HttpURLConnection) url .openConnection(); if (conn.getResponseCode() / 100 == 2 && conn.getContentLength() != -1) { fileLength = conn.getContentLength(); Log.i(TAG, "getContentLength == " + fileLength); } else { Thread.sleep(5000); Log.i(TAG, "getContentLength failed : retry in 5 second later"); continue; } } HttpClient httpClient = new DefaultHttpClient(); HttpGet request = new HttpGet(downUrl); request.addHeader("Range", "bytes=" + downLength + "-"); if (downLength < fileLength) { HttpHost proxy = HttpBase.globalProxy(); HttpParams httpParams = request.getParams(); HttpConnectionParams.setConnectionTimeout(httpParams, NETWORK_CONNECTION_TIMEOUT); HttpConnectionParams.setSoTimeout(httpParams, NETWORK_SO_TIMEOUT); ConnRouteParams.setDefaultProxy(request.getParams(), proxy); HttpResponse response = httpClient.execute(request); Log.i(TAG, "getContent's response status == " + response.getStatusLine().getStatusCode()); if (response.getStatusLine().getStatusCode() / 100 != 2) { continue; } HttpEntity entity = response.getEntity(); is = entity.getContent(); byte[] buffer = new byte[BUFFER_SIZE]; int offset = 0; if (downLength > 0) { outStream = context .openFileOutput( Integer.valueOf(newestVersionCode) + ".apk", Context.MODE_APPEND + Context.MODE_WORLD_READABLE); } else { outStream = context .openFileOutput( Integer.valueOf(newestVersionCode) + ".apk", Context.MODE_WORLD_READABLE); } while ((offset = is.read(buffer, 0, BUFFER_SIZE)) != -1 && !stop) { outStream.write(buffer, 0, offset); downLength += offset; } } if (downLength == fileLength) { File apkFile = new File(context.getFilesDir(), Integer.valueOf(newestVersionCode) + ".apk"); if (isApkFileOK(apkFile)) { checkStatus = CHECK_SUCCESS; } else { deleteApkFile(newestVersionCode); saveLogFile(existTemFileVersionCode, 0); checkStatus = CHECK_FAILED; } this.downFinish = true; } } catch (Exception e) { } finally { saveLogFile(newestVersionCode, downLength); if (receiverRegistered) { context.unregisterReceiver(receiver); receiverRegistered = false; } if (stop || downFinish) { break; } try { if (outStream != null) { outStream.close(); } if (is != null) { is.close(); } } catch (IOException e) { } } } } @Override public void interrupt() { synchronized (block) { Log.i(TAG, "block.notify()"); block.notify(); } } private void cancel() { stop = true; mNM.cancel(MOOD_NOTIFICATIONS); } private boolean isApkFileOK(File file) { checkStatus = CHECK_RUNNING; // first check the file header /*if (file.isDirectory() || !file.canRead() || file.length() < 4) { return false; } DataInputStream in = null; try { in = new DataInputStream(new BufferedInputStream( new FileInputStream(file))); int test = in.readInt(); if (test != 0x504b0304) return false; } catch (IOException e) { return false; } finally { try { in.close(); } catch (IOException e) { } }*/ // second unZip file to check(without saving) boolean result = unzip(file); isChecking = false; return result; } private boolean unzip(File unZipFile) { boolean succeed = true; ZipInputStream zin = null; ZipEntry entry = null; try { zin = new ZipInputStream(new FileInputStream(unZipFile)); boolean first = true; while (true) { if ((entry = zin.getNextEntry()) == null) { if (first) succeed = false; break; } first = false; if (entry.isDirectory()) { zin.closeEntry(); continue; } if (!entry.isDirectory()) { byte[] b = new byte[1024]; @SuppressWarnings("unused") int len = 0; while ((len = zin.read(b)) != -1) { } zin.closeEntry(); } } } catch (IOException e) { succeed = false; } finally { if (null != zin) { try { zin.close(); } catch (IOException e) { } } } return succeed; } public void showNotification() { float result = (float) downLength / (float) fileLength; int p = (int) (result * 100); if (p == 0 && isPercentZeroRunning || p == 100 && isChecking) { return; } else if (p != 0) { isPercentZeroRunning = false; } Notification notification = new Notification(R.drawable.icon, null, 0); if (p == 100) { if (checkStatus == CHECK_RUNNING) { notification.tickerText = "开始检查下载文件"; notification.when = System.currentTimeMillis(); notification.flags = notification.flags | Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR; notification.contentView = rv; notification.contentView.setProgressBar( R.id.update_notification_progressbar, 100, p, true); notification.contentView.setTextViewText( R.id.update_notification_title, "正在检查下载文件..."); PendingIntent contentIntent = PendingIntent.getActivity( context, 0, null, 0); notification.contentIntent = contentIntent; isChecking = true; } else if (checkStatus == CHECK_FAILED) { notification.tickerText = "文件验证失败!"; notification.when = System.currentTimeMillis(); notification.flags = notification.flags | Notification.FLAG_AUTO_CANCEL; notification.contentView = rv; notification.contentView.setProgressBar( R.id.update_notification_progressbar, 100, p, false); notification.contentView.setTextViewText( R.id.update_notification_title, "文件验证失败,请重新下载"); Intent notificationIntent = new Intent(context, UpdateService.class); notificationIntent.setAction(ACTION_STOP); PendingIntent contentIntent = PendingIntent.getService( context, 0, notificationIntent, 0); notification.contentIntent = contentIntent; } else { notification.tickerText = "下载完成"; notification.when = System.currentTimeMillis(); Intent notificationIntent = new Intent(); notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); notificationIntent .setAction(android.content.Intent.ACTION_VIEW); String type = "application/vnd.android.package-archive"; notificationIntent.setDataAndType( Uri.parse("file:///data/data/" + context.getPackageName() + "/files/" + (Integer.valueOf(newestVersionCode) .toString()) + ".apk"), type); notification.contentView = rv; notification.flags = Notification.FLAG_AUTO_CANCEL; notification.contentView .setProgressBar( R.id.update_notification_progressbar, 100, p, false); notification.contentView.setTextViewText( R.id.update_notification_progresstext, p + "%"); notification.contentView.setTextViewText( R.id.update_notification_title, "下载完成,点击安装"); PendingIntent contentIntent = PendingIntent.getActivity( context, 0, notificationIntent, 0); notification.contentIntent = contentIntent; } } else if (p == 0) { notification.tickerText = "准备下载"; notification.when = System.currentTimeMillis(); Intent notificationIntent = new Intent(context, UpdateService.class); notificationIntent.setAction(ACTION_STOP); notification.flags = notification.flags | Notification.FLAG_ONGOING_EVENT; notification.contentView = rv; notification.contentView.setProgressBar( R.id.update_notification_progressbar, 100, p, true); notification.contentView.setTextViewText( R.id.update_notification_title, "正在准备下载(点击取消)"); PendingIntent contentIntent = PendingIntent.getService(context, 0, notificationIntent, 0); notification.contentIntent = contentIntent; isPercentZeroRunning = true; } else { notification.tickerText = "开始下载"; notification.when = System.currentTimeMillis(); Intent notificationIntent = new Intent(context, UpdateService.class); notificationIntent.setAction(ACTION_STOP); notification.contentView = rv; notification.flags = notification.flags | Notification.FLAG_ONGOING_EVENT; notification.contentView.setProgressBar( R.id.update_notification_progressbar, 100, p, false); notification.contentView.setTextViewText( R.id.update_notification_progresstext, p + "%"); notification.contentView.setTextViewText( R.id.update_notification_title, "正在下载(点击取消)"); PendingIntent contentIntent = PendingIntent.getService(context, 0, notificationIntent, 0); notification.contentIntent = contentIntent; } mNM.notify(MOOD_NOTIFICATIONS, notification); } }
可以看到,我的下载是包裹在一个while循环中的,假如没有下载完成,我会一直重复这个循环,可以注意到,我在取数据的时候有个标志位stop
- while ((offset = is.read(buffer, 0, BUFFER_SIZE)) != -1
- && !stop) {
- outStream.write(buffer, 0, offset);
- downLength += offset;
- }
while ((offset = is.read(buffer, 0, BUFFER_SIZE)) != -1 && !stop) { outStream.write(buffer, 0, offset); downLength += offset; }
有了这个就可以在外面控制强制停止下载。
在下载开始阶段我最先做的时就是检查网络情况,如果没有网络,我就使用一个block让这个线程阻塞掉,有人会问那什么时候恢复呢?我在service里面加了个广播
- private final BroadcastReceiver receiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent
- .getAction())) {
- NetworkInfo info = (NetworkInfo) intent
- .getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
- boolean hasConnectivity = (info != null && info.isConnected()) ? true
- : false;
- if (hasConnectivity && downloadThread != null) {
- downloadThread.interrupt();
- }
- }
- }
- };
private final BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent .getAction())) { NetworkInfo info = (NetworkInfo) intent .getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); boolean hasConnectivity = (info != null && info.isConnected()) ? true : false; if (hasConnectivity && downloadThread != null) { downloadThread.interrupt(); } } } };
可以监听系统网络情况,如果连上了,就调用interrupt()来唤醒线程。这样做的好处时,在没有网络时,这个线程不会无限循环的去获取数据。
最后在这个版本发布后因为代码一些bug导致,如果网络数据获取有问题,则用户下载下来的安装包就会解析错误,而且这是个死胡同,除非去手动下载个好的安装包来装,否则我们软件会一直提示,一直完成,但是一直装不上。。。哎,我们用户可是百万级啊,这个bug很致命,还好当时做了备用方案,可以由服务器控制客户端更新方法,于是改为以前的更新。
这也是我在上面的代码中下载完成时加入了最后一步,校验安装包的过程。我们都知道apk就是一个zip文件,通常对一个zip文件的校验,最简单的是校验文件头是不是0x504b0304,但是这只是文件格式的判断,加入是文件内容字节损坏还是查不出来,只能通过去unzip这个文件来捕获异常。在上面unzip(File unZipFile)方法中,我尝试去解压软件,对ZipInputStream流我没做任何处理,仅仅是看这个解压过程是否正常,以此判断这个zip文件是否正常,暂时也没想到更好的办法。
- private boolean unzip(File unZipFile) {
- boolean succeed = true;
- ZipInputStream zin = null;
- ZipEntry entry = null;
- try {
- zin = new ZipInputStream(new FileInputStream(unZipFile));
- boolean first = true;
- while (true) {
- if ((entry = zin.getNextEntry()) == null) {
- if (first)
- succeed = false;
- break;
- }
- first = false;
- if (entry.isDirectory()) {
- zin.closeEntry();
- continue;
- }
- if (!entry.isDirectory()) {
- byte[] b = new byte[1024];
- @SuppressWarnings("unused")
- int len = 0;
- while ((len = zin.read(b)) != -1) {
- }
- zin.closeEntry();
- }
- }
- } catch (IOException e) {
- succeed = false;
- } finally {
- if (null != zin) {
- try {
- zin.close();
- } catch (IOException e) {
- }
- }
- }
- return succeed;
- }
private boolean unzip(File unZipFile) { boolean succeed = true; ZipInputStream zin = null; ZipEntry entry = null; try { zin = new ZipInputStream(new FileInputStream(unZipFile)); boolean first = true; while (true) { if ((entry = zin.getNextEntry()) == null) { if (first) succeed = false; break; } first = false; if (entry.isDirectory()) { zin.closeEntry(); continue; } if (!entry.isDirectory()) { byte[] b = new byte[1024]; @SuppressWarnings("unused") int len = 0; while ((len = zin.read(b)) != -1) { } zin.closeEntry(); } } } catch (IOException e) { succeed = false; } finally { if (null != zin) { try { zin.close(); } catch (IOException e) { } } } return succeed; }
发表评论
-
解包system.img成HIT archive data格式的包
2014-04-26 11:30 3801昨天解包了一天,HIT archive data格式的sy ... -
打包system.img成HIT archive data格式的包
2014-04-26 11:29 738有些人看了上一篇的解 ... -
system.img解包打包的方法
2013-05-10 18:27 0操作系统:ubuntu10.10可 ... -
apk反编译和重新打包
2013-04-18 18:11 28234相信每位玩机的人对APK ... -
PackageManager
2013-04-02 16:52 1209PackageManager分析(5) ... -
android源码中混淆编译
2013-03-27 15:11 16971.在需要混淆的工程目录下(package/apps/下的 ... -
如何通过反射方法获取com.android.internal.os.PkgUsageStats
2012-11-05 17:45 1923通过com.android.internal.os.P ... -
android 4.0.3最新源码下载编译
2012-09-29 13:57 838首先,开发环境,google建议在ubuntu10.0.4下, ... -
不错的资源网站
2012-09-19 16:38 755最近在爱库网上发现了很多不错的Icon资源站点, Web2.0 ... -
Android下使用Http协议实现多线程断点续传下载
2012-08-30 14:49 37560.使用多线程下载会提升文件下载的速度,那么多线程下载文件的过 ... -
Android UI 单线程模型的编程原则以及AsyncTask 原理
2012-08-21 17:57 881导读:oInBackground方法和 ... -
Android 解析后台返回为Json数据实例教程
2012-08-21 16:46 8138大家好,今天给大家分享下Android解析Json的例子,我这 ...
相关推荐
STM32单片机通过ESP8266WiFi模块与Android APP实现数据传输软件例程源码,包含STM32单片机的源代码和手机APP的源代码,仅供学习及设计参考。
app连接蓝牙模块2.0进行互相通讯,使用androidstudio编写,页面相对简单,但是功能完善,蓝牙必须先配对再在app中连接
Github已开源:...详细内容也可看我的文章:https://mp.csdn.net/mp_blog/creation/editor/124478948《Android Studio设计APP实现与51单片机通过WIFI模块(ESP8266-01S)通讯控制LED灯亮灭的设计源码【详解】》
基于Android的焦点新闻APP的设计与实现(源码 + 说明文档 + 演示视频) 第4章 系统总体设计 17 4.1 概述 17 4.2 设计原则 17 4.3 Android应用程序结构剖析 17 4.4 系统功能结构图 23 第5章 系统的实现 25 5.1 软件...
基于Android的BlueTooth开发手机蓝牙和蓝牙模块通讯,
此次新闻app应用程序开发主要利用java语言在android框架中对各种组件包括TextView、EditView、ListView进行系统性、框架性、整合性的学习,在编写app过程中,解决日常学习中遇到的一些单一性问题,同时通过编程实践...
这个APP是通过JAVA代码来编写的,实际上就是一个通过Android Studio的编译生成的APK文件。小车蓝牙和手机端成功完成配对,使用手机APP上的键盘控制给小车发送指令,小车上的芯片收到了指令后进行处理,最后小车能够...
小红书APP首页各模块测试用例汇总。首页各模块涵盖一至三级目录,共70条具体的测试用例的编写,步骤详细具体,内容描述完整,适合小红书各版本首页的测试。
3 2.3.2 硬件接口 3 2.3.3 软件接口 4 2.4 系统功能需求 4 2.5 分析模型 4 2.5.1 数据流图 4 2.5.2 用例图 5 3 设计报告 8 3.1 设计概述 8 3.2 系统的概要设计与分析 8 3.2.1 系统功能设计 8 3.2.2 子系统与模块 9 ...
基于Android的个人理财APP的设计与实现(源码 + 说明文档 + 演示视频) 第4章 系统总体设计 18 4.1 概述 18 4.2 设计原则 18 4.3 Android应用程序结构剖析 18 4.4 系统功能结构图 24 4.5 系统流程图设计 24 第5章 ...
基于Android的背单词app 实战大作业 (源码 + 说明文档 + 演示视频) 第5章 系统的实现 28 5.1 软件界面设计 28 5.2 信息展示界面 28 5.3 电视竞猜详情模块界面 29 5.4 积分查询模块界面 30 5.5信息修改详情模块...
基于Android的围棋学习管理互动的app (源码 + 说明文档 + 演示视频) 第4章 系统总体设计 18 4.1 概述 18 4.2 设计原则 18 4.3 Android应用程序结构剖析 18 4.4 系统功能结构图 24 4.5 系统流程图设计 24 第5章 系统...
本系统应用单片机技术、蓝牙通信技术和Android手机APP开发技术,设计了一种以Android智能手机作为指令传送和数据接收终端,获取实时温湿度信息的系统。 1.蓝牙采用HC-05模块 2.提供了硬件端与手机端的源码。
基于Android的课程提醒签到APP ( 源码 + 说明文档 + 演示视频) 第4章 系统总体设计 18 4.1 概述 18 4.2 设计原则 18 4.3 Android应用程序结构剖析 18 4.4 系统功能结构图 24 第5章 系统的实现 26 5.1 软件界面设计 ...
目前Android在全世界市场上大约有75%的占有率,国人Android手机的持有比例更甚,甚至达到90%以上。因此搞计算机的一听说手机应用开发,一个个都像着了魔似的,既然有那么多人对它感兴趣,那咱也跟着玩一玩。但是,本...
提供集成开发环境(IDE),如Visual Studio、Eclipse、Android Studio和Sublime Text等,这些工具集成了文本编辑器,支持语法高亮、自动补全、代码片段管理和版本控制等功能,有助于开发者高效编写和维护代码。...
某公司的培训教材 一、在Android内核源代码工程(kernel)中编写硬件驱动程序.............................4 二、在Android系统中增加C...六、在Android系统中编写APP通过应用程序框架层访问硬件服务.................28
Android编写的实用查询APP:车辆违章查询程序源代码,根据用户输入的车辆信息(车牌号、车架号、发动机号等),可查询违章信息,根据默认查询地城市id, 初始化查询项目,可以显示隐藏行驶证图示,根据城市的配置设置...
git.diff 文件,包含功能实现的所有提交。(Android 9.0) 0基础也能实现Android 控GPIO 包含:编写控制GPIO脚本,内置到系统,在rc中创建运行脚本...自行编写AndroidStudio Demo 更改系统属性 即可触发脚本,上拉GPIO
这是用作使用最新框架功能编写Android应用程序的基础的模板。 特征 多模块项目设置 易于使用的基于多模块。 使图书馆始终保持最新状态 未来的工作 单元测试 使用Espresso进行UI测试 执照 Copyright 2021 Alejandro ...