- 浏览: 69283 次
- 性别:
- 来自: 深圳
最新评论
-
andy_叶:
hold_on 写道有 android proguard 语法 ...
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 3848昨天解包了一天,HIT archive data格式的sy ... -
打包system.img成HIT archive data格式的包
2014-04-26 11:29 795有些人看了上一篇的解 ... -
system.img解包打包的方法
2013-05-10 18:27 0操作系统:ubuntu10.10可 ... -
apk反编译和重新打包
2013-04-18 18:11 28316相信每位玩机的人对APK ... -
PackageManager
2013-04-02 16:52 1290PackageManager分析(5) ... -
android源码中混淆编译
2013-03-27 15:11 17501.在需要混淆的工程目录下(package/apps/下的 ... -
如何通过反射方法获取com.android.internal.os.PkgUsageStats
2012-11-05 17:45 1984通过com.android.internal.os.P ... -
android 4.0.3最新源码下载编译
2012-09-29 13:57 928首先,开发环境,google建议在ubuntu10.0.4下, ... -
不错的资源网站
2012-09-19 16:38 832最近在爱库网上发现了很多不错的Icon资源站点, Web2.0 ... -
Android下使用Http协议实现多线程断点续传下载
2012-08-30 14:49 38630.使用多线程下载会提升文件下载的速度,那么多线程下载文件的过 ... -
Android UI 单线程模型的编程原则以及AsyncTask 原理
2012-08-21 17:57 1008导读:oInBackground方法和 ... -
Android 解析后台返回为Json数据实例教程
2012-08-21 16:46 8201大家好,今天给大家分享下Android解析Json的例子,我这 ...
相关推荐
该压缩包文件“STM32单片机通过ESP8266WiFi模块与Android APP实现数据传输软件例程源码.zip”提供了一个完整的系统,使STM32单片机能够通过ESP8266 WiFi模块与Android应用程序进行数据通信。这个系统的核心在于STM32...
在Android平台上,与WIFI模块进行数据传输是一个常见的任务,特别是在物联网(IoT)设备的交互中。本项目中,开发者被要求创建一个Android应用程序,用于配置HLK-RM04 WIFI模块,进行数据读取、参数设置以及系统时间...
智能家居Android APP源码的分析和学习涵盖了Android开发的许多方面,对于希望深入理解Android系统和智能家居系统集成的开发者来说,这是一个宝贵的资源。通过研究源码,开发者可以了解如何构建一个完整的智能家居...
Github已开源:...详细内容也可看我的文章:https://mp.csdn.net/mp_blog/creation/editor/124478948《Android Studio设计APP实现与51单片机通过WIFI模块(ESP8266-01S)通讯控制LED灯亮灭的设计源码【详解】》
在Android开发领域,构建一个电商平台App的购物车模块是一项关键任务。购物车模块是用户与应用交互的重要环节,它需要实现一系列复杂的功能来提供顺畅的购物体验。在"Android-Car电商平台App购物车模块功能"这个项目...
Android Studio是Google推出的一款强大的Android应用程序开发工具,它基于IntelliJ IDEA,提供了高效的代码编辑、构建、调试和分析功能,是Android开发者首选的集成开发环境(IDE)。在这个项目中,我们看到的是一个...
本教程主要介绍了Android APP开发的基本步骤,从SDK下载、开发环境搭建、代码编写、APP打包等步骤一一讲解,为读者提供了一个简明的Android APP开发入门教程。 一、准备工作 在开始Android APP开发之前,需要准备...
基于Android的焦点新闻APP的设计与实现(源码 + 说明文档 + 演示视频) 第4章 系统总体设计 17 4.1 概述 17 4.2 设计原则 17 4.3 Android应用程序结构剖析 17 4.4 系统功能结构图 23 第5章 系统的实现 25 5.1 软件...
"ChameleonMiniApp变色龙硬件Android APP"是一款专为Android设备设计的应用程序,它能够与硬件设备通过OTG(On-The-Go)技术进行连接,实现设备间的交互。这款APP未经编译,用户需要自行在手机上进行安装。下面我们...
根据提供的文件信息,我们可以推断出这是一段与Android应用构建相关的脚本代码。下面将对这段代码进行...当然,实际操作过程中还可能遇到各种具体问题,需要不断学习和实践才能熟练掌握。希望这些内容对你有所帮助!
本文将深入探讨鸿蒙App与Android App之间的联系,帮助开发者理解两者间的共通之处以及差异性。 首先,我们要知道鸿蒙OS和Android系统都是基于Linux内核构建的,这为两者之间建立联系提供了基础。鸿蒙OS采用了微内核...
总的来说,这个《天气预报app》项目涵盖了Android应用开发的多个关键方面,如UI设计、网络请求(获取天气数据)、数据解析(处理JSON数据)、以及与系统服务的交互(获取位置信息)。开发者可以通过这个项目学习如何...
11. **蓝牙问题处理**:在实际开发中,可能会遇到连接不稳定、数据丢失等问题,需要考虑重试机制和错误处理。此外,不同Android版本的蓝牙API可能存在差异,需适配不同系统版本。 通过以上知识点,开发者可以构建一...
标题中的“自己从零编写的Android记事本APP软件”表明了这是一个个人开发的项目,开发者从基础开始,没有依赖现成的框架或模板,完全通过自己的编程技能创建了一个Android平台上的记事本应用程序。这涉及到Android...
1. **Android应用开发**:APP是为Android操作系统定制的,使用Java或Kotlin等语言编写,遵循Android SDK和Android Studio的开发环境。 2. **蓝牙通信**:APP具备蓝牙功能,能够与具有蓝牙功能的硬件设备(如STM32微...
《Android APP安全评估工具——Drozer详解》 在当今移动互联网时代,Android应用程序的安全性愈发受到关注。作为开发者和安全研究人员,确保APP免受恶意攻击是至关重要的任务。Drozer,一款专为Android应用安全评估...
【Android 新闻App源码详解】 在Android平台上开发新闻应用是一项常见的任务,它涉及到许多核心技术和组件的集成。...学习和分析这样的源码,对于Android开发者来说,是提升技能和解决问题的重要途径。
总结来说,Android Camera模块的运行依赖于JNI来桥接Java和原生代码,Mediaserver进程中的CameraService负责处理相机相关的操作。Binder机制则确保了JAVA应用进程与CameraService进程间的高效、安全通信,使得应用...
本教程将详细讲解如何在Android项目中实现一个单独的APP自启动模块。 首先,我们要明白在Android系统中,自启动机制是受到一定限制的,尤其是从Android 6.0(API级别23)开始,系统引入了运行时权限管理,对应用的...