`
andy_叶
  • 浏览: 69282 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

编写Android app更新模块遇到的问题分析与总结

阅读更多

前不久接到个任务,在我们的app里面添加更新模块,在之前的版本中,我们的更新都是直接通过浏览器下载apk包来安装更新的,我想各位很大一部分应用的更新方法都是这样,因为它简单、方便,但是他也有许多不好的地方,比如需要用户跳转到浏览器页面、下载不可控、网络不好的情况的下失败无法续传,退出浏览器就无法接着下了等。。

 

     于是我们这个更新模块的需求就来了

1.下载后台进行,退出我们应用下载任务依旧能继续执行操作

2.下载文件支持断点续传

3.下载任务支持没有安装sdcard时也可下载更新

4.notify栏提示操作

 

   对几个需求稍作分析,解决方法如下:

1.下载更新的线程放到一个service中去,service的好处是不易被系统回收,而且也容易操作。我们需要先在AndroidMainfest.xml文件中去注册这个service

 

  1. <service android:name="com.dj.app.UpdateService"/>  
<service android:name="com.dj.app.UpdateService"/>


2.断点续传,请求头中有个重要的参数range,代表的意思的我要取的数据的范围,这个需要服务器支持,默认情况都是支持的。我的思路是下载前先获取文件包大小,然后检查是否已经有下载好的部分了,没有就从头开始,有就接着下。

 

 

  1. request.addHeader("Range""bytes=" + downLength + "-");  
request.addHeader("Range", "bytes=" + downLength + "-");

 

为了支持断点续传,我将下载好的文件版本号、文件长度都以prefrence的形似保存下来,下次更新如果还是这个版本就接着下,如果又有更新的了就删掉重下。

 

  1. private void checkTemFile() {  
  2.             existTemFileVersionCode = preferences().getInt(  
  3.                     UPDATE_FILE_VERSIONCODE, 0);  
  4.             if (newestVersionCode == existTemFileVersionCode  
  5.                     || newestVersionCode == TEST) {  
  6.                 File temFile = new File(context.getFilesDir(),  
  7.                         Integer.valueOf(newestVersionCode) + ".apk");  
  8.                 if (!temFile.exists()) {  
  9.                     saveLogFile(newestVersionCode, 0);  
  10.                 }  
  11.             } else {  
  12.                 deleteApkFile(existTemFileVersionCode);  
  13.                 saveLogFile(newestVersionCode, 0);  
  14.             }  
  15. }  
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包下载到系统内存中

 

  1. context.getFilesDir();  
context.getFilesDir();

这样创建的文件在/data/data/应用包名/files

 

伪代码:

 

  1. if (downLength > 0) {//接着上次的下载   
  2.   
  3.     outStream = context.openFileOutput(Integer.valueOf(newestVersionCode) + ".apk",  
  4.             Context.MODE_APPEND+ Context.MODE_WORLD_READABLE);  
  5.   
  6. else {//从头开始下载   
  7.       outStream = context.openFileOutput(Integer.valueOf(newestVersionCode) + ".apk",Context.MODE_WORLD_READABLE);  
  8. }  
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中的进度条。

 

  1. new Thread() {  
  2.                 public void run() {  
  3.                     try {  
  4.                         boolean notFinish = true;  
  5.                         while (notFinish) {  
  6.                             Thread.sleep(1000);  
  7.                             notFinish = false;  
  8.                             if (downloadThread == null) {  
  9.                                 break;  
  10.                             }  
  11.                             if (!downloadThread.downFinish) {  
  12.                                 notFinish = true;  
  13.                             }  
  14.                             downloadThread.showNotification();  
  15.                         }  
  16.                     } catch (Exception e) {  
  17.                         e.printStackTrace();  
  18.                     } finally {  
  19.                         stopSelf();  
  20.                     }  
  21.                 };  
  22.             }.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%的时候,我想要用户点击通知栏,即可进行安装,则应该这样做

 

 

 

 

  1. notification.tickerText = "下载完成";  
  2.                     notification.when = System.currentTimeMillis();  
  3.                     Intent notificationIntent = new Intent();  
  4.                     notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  5.                     notificationIntent  
  6.                             .setAction(android.content.Intent.ACTION_VIEW);  
  7.                     String type = "application/vnd.android.package-archive";  
  8.                     notificationIntent.setDataAndType(  
  9.                             Uri.parse("file:///data/data/"  
  10.                                     + context.getPackageName()  
  11.                                     + "/files/"  
  12.                                     + (Integer.valueOf(newestVersionCode)  
  13.                                             .toString()) + ".apk"), type);  
  14.                     notification.contentView = rv;  
  15.                     notification.flags = Notification.FLAG_AUTO_CANCEL;  
  16.                     notification.contentView  
  17.                             .setProgressBar(  
  18.                                     R.id.update_notification_progressbar, 100,  
  19.                                     p, false);  
  20.                     notification.contentView.setTextViewText(  
  21.                             R.id.update_notification_progresstext, p + "%");  
  22.                     notification.contentView.setTextViewText(  
  23.                             R.id.update_notification_title, "下载完成,点击安装");  
  24.                     PendingIntent contentIntent = PendingIntent.getActivity(  
  25.                             context, 0, notificationIntent, 0);  
  26.                     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;



 


 

下面我将下载线程完整的代码贴出来

 

  1. class DownloadThread extends Thread {  
  2.     private static final String UPDATE_FILE_VERSIONCODE = "updateTemFileVersionCode";  
  3.     private static final String UPDATE_FILE_LENGTH = "updateTemFileLength";  
  4.     private static final String TEST_UPDATE_FILE_LENGTH = "testupdatefilelength";  
  5.     private static final int BUFFER_SIZE = 1024;  
  6.     private static final int NETWORK_CONNECTION_TIMEOUT = 15000;  
  7.     private static final int NETWORK_SO_TIMEOUT = 15000;  
  8.     private int newestVersionCode, existTemFileVersionCode;  
  9.     private String downUrl;  
  10.     private int fileLength = Integer.MAX_VALUE;  
  11.     private int downLength;  
  12.     private boolean downFinish;  
  13.   
  14.     private static final int CHECK_FAILED = -1;  
  15.     private static final int CHECK_SUCCESS = 0;  
  16.     private static final int CHECK_RUNNING = 1;  
  17.     private int checkStatus;  
  18.     private boolean isChecking = false;  
  19.   
  20.     private Context context;  
  21.     private boolean isPercentZeroRunning = false;  
  22.     private boolean stop;  
  23.     private Object block = new Object();  
  24.     private boolean receiverRegistered = false;  
  25.   
  26.     private NotificationManager mNM;  
  27.     private RemoteViews rv;  
  28.   
  29.     public DownloadThread(Context context, String downUrl, int versionCode) {  
  30.         super("DownloadThread");  
  31.         this.downUrl = downUrl;  
  32.         this.newestVersionCode = versionCode;  
  33.         this.context = context;  
  34.         this.mNM = (NotificationManager) context  
  35.                 .getSystemService(Context.NOTIFICATION_SERVICE);  
  36.         this.rv = new RemoteViews(context.getPackageName(), R.layout.notify);  
  37.         this.downFinish = false;  
  38.     }  
  39.   
  40.     private void checkTemFile() {  
  41.         existTemFileVersionCode = preferences().getInt(  
  42.                 UPDATE_FILE_VERSIONCODE, 0);  
  43.         if (newestVersionCode == existTemFileVersionCode  
  44.                 || newestVersionCode == TEST) {  
  45.             File temFile = new File(context.getFilesDir(),  
  46.                     Integer.valueOf(newestVersionCode) + ".apk");  
  47.             if (!temFile.exists()) {  
  48.                 saveLogFile(newestVersionCode, 0);  
  49.             }  
  50.         } else {  
  51.             deleteApkFile(existTemFileVersionCode);  
  52.             saveLogFile(newestVersionCode, 0);  
  53.         }  
  54.     }  
  55.   
  56.     private void deleteApkFile(int existVersionCode) {  
  57.         File temFile = new File(context.getFilesDir(),  
  58.                 Integer.valueOf(existVersionCode) + ".apk");  
  59.         if (temFile.exists()) {  
  60.             temFile.delete();  
  61.         }  
  62.     }  
  63.   
  64.     private SharedPreferences preferences() {  
  65.         return context.getSharedPreferences(context.getPackageName(),  
  66.                 Context.MODE_WORLD_READABLE | Context.MODE_WORLD_WRITEABLE);  
  67.     }  
  68.   
  69.     private void saveLogFile(int versionCode, int downloadLength) {  
  70.         SharedPreferences.Editor edit = preferences().edit();  
  71.         if (versionCode == TEST) {  
  72.             edit.putInt(TEST_UPDATE_FILE_LENGTH, downloadLength);  
  73.         } else {  
  74.             edit.putInt(UPDATE_FILE_VERSIONCODE, versionCode);  
  75.             edit.putInt(UPDATE_FILE_LENGTH, downloadLength);  
  76.         }  
  77.         edit.commit();  
  78.     }  
  79.   
  80.     @Override  
  81.     public void run() {  
  82.         checkTemFile();  
  83.         this.stop = false;  
  84.         while (!downFinish) {  
  85.             Log.i(TAG, "download thread start : while()");  
  86.             if (newestVersionCode == TEST) {  
  87.                 downLength = preferences().getInt(TEST_UPDATE_FILE_LENGTH,  
  88.                         0);  
  89.             } else {  
  90.                 downLength = preferences().getInt(UPDATE_FILE_LENGTH, 0);  
  91.             }  
  92.   
  93.             InputStream is = null;  
  94.             FileOutputStream outStream = null;  
  95.             try {  
  96.                 // check the network   
  97.                 ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);  
  98.                 NetworkInfo ni = cm.getActiveNetworkInfo();  
  99.                 boolean con = ni == null ? false : ni  
  100.                         .isConnectedOrConnecting();  
  101.                 synchronized (block) {  
  102.                     if (!con) {  
  103.                         context.registerReceiver(  
  104.                                 receiver,  
  105.                                 new IntentFilter(  
  106.                                         ConnectivityManager.CONNECTIVITY_ACTION));  
  107.                         receiverRegistered = true;  
  108.                         try {  
  109.                             Log.i(TAG, "network is not ok : block.wait()");  
  110.                             block.wait();  
  111.                         } catch (InterruptedException e1) {  
  112.                         }  
  113.                     }  
  114.                 }  
  115.   
  116.                 if (fileLength == Integer.MAX_VALUE) {  
  117.                     URL url = new URL(downUrl);  
  118.                     HttpURLConnection conn = (HttpURLConnection) url  
  119.                             .openConnection();  
  120.                     if (conn.getResponseCode() / 100 == 2  
  121.                             && conn.getContentLength() != -1) {  
  122.                         fileLength = conn.getContentLength();  
  123.                         Log.i(TAG, "getContentLength == " + fileLength);  
  124.                     } else {  
  125.                         Thread.sleep(5000);  
  126.                         Log.i(TAG, "getContentLength failed : retry in 5 second later");  
  127.                         continue;  
  128.                     }  
  129.                 }  
  130.   
  131.                 HttpClient httpClient = new DefaultHttpClient();  
  132.                 HttpGet request = new HttpGet(downUrl);  
  133.                 request.addHeader("Range""bytes=" + downLength + "-");  
  134.   
  135.                 if (downLength < fileLength) {  
  136.                     HttpHost proxy = HttpBase.globalProxy();  
  137.                     HttpParams httpParams = request.getParams();  
  138.                     HttpConnectionParams.setConnectionTimeout(httpParams,  
  139.                             NETWORK_CONNECTION_TIMEOUT);  
  140.                     HttpConnectionParams.setSoTimeout(httpParams,  
  141.                             NETWORK_SO_TIMEOUT);  
  142.                     ConnRouteParams.setDefaultProxy(request.getParams(),  
  143.                             proxy);  
  144.   
  145.                     HttpResponse response = httpClient.execute(request);  
  146.                     Log.i(TAG, "getContent's response status == "  
  147.                             + response.getStatusLine().getStatusCode());  
  148.                     if (response.getStatusLine().getStatusCode() / 100 != 2) {  
  149.                         continue;  
  150.                     }  
  151.                     HttpEntity entity = response.getEntity();  
  152.                     is = entity.getContent();  
  153.                     byte[] buffer = new byte[BUFFER_SIZE];  
  154.                     int offset = 0;  
  155.   
  156.                     if (downLength > 0) {  
  157.                         outStream = context  
  158.                                 .openFileOutput(  
  159.                                         Integer.valueOf(newestVersionCode)  
  160.                                                 + ".apk",  
  161.                                         Context.MODE_APPEND  
  162.                                                 + Context.MODE_WORLD_READABLE);  
  163.   
  164.                     } else {  
  165.                         outStream = context  
  166.                                 .openFileOutput(  
  167.                                         Integer.valueOf(newestVersionCode)  
  168.                                                 + ".apk",  
  169.                                         Context.MODE_WORLD_READABLE);  
  170.                     }  
  171.   
  172.                     while ((offset = is.read(buffer, 0, BUFFER_SIZE)) != -1  
  173.                             && !stop) {  
  174.                         outStream.write(buffer, 0, offset);  
  175.                         downLength += offset;  
  176.                     }  
  177.                 }  
  178.                 if (downLength == fileLength) {  
  179.                     File apkFile = new File(context.getFilesDir(),  
  180.                             Integer.valueOf(newestVersionCode) + ".apk");  
  181.                     if (isApkFileOK(apkFile)) {  
  182.                         checkStatus = CHECK_SUCCESS;  
  183.                     } else {  
  184.                         deleteApkFile(newestVersionCode);  
  185.                         saveLogFile(existTemFileVersionCode, 0);  
  186.                         checkStatus = CHECK_FAILED;  
  187.                     }  
  188.                     this.downFinish = true;  
  189.                 }  
  190.   
  191.             } catch (Exception e) {  
  192.             } finally {  
  193.                 saveLogFile(newestVersionCode, downLength);  
  194.                 if (receiverRegistered) {  
  195.                     context.unregisterReceiver(receiver);  
  196.                     receiverRegistered = false;  
  197.                 }  
  198.   
  199.                 if (stop || downFinish) {  
  200.                     break;  
  201.                 }  
  202.                 try {  
  203.                     if (outStream != null) {  
  204.                         outStream.close();  
  205.                     }  
  206.                     if (is != null) {  
  207.                         is.close();  
  208.                     }  
  209.                 } catch (IOException e) {  
  210.                 }  
  211.             }  
  212.         }  
  213.   
  214.     }  
  215.   
  216.     @Override  
  217.     public void interrupt() {  
  218.         synchronized (block) {  
  219.             Log.i(TAG, "block.notify()");  
  220.             block.notify();  
  221.         }  
  222.     }  
  223.   
  224.     private void cancel() {  
  225.         stop = true;  
  226.         mNM.cancel(MOOD_NOTIFICATIONS);  
  227.     }  
  228.   
  229.     private boolean isApkFileOK(File file) {  
  230.         checkStatus = CHECK_RUNNING;  
  231.         // first check the file header   
  232.         /*if (file.isDirectory() || !file.canRead() || file.length() < 4) { 
  233.             return false; 
  234.         } 
  235.         DataInputStream in = null; 
  236.         try { 
  237.             in = new DataInputStream(new BufferedInputStream( 
  238.                     new FileInputStream(file))); 
  239.             int test = in.readInt(); 
  240.             if (test != 0x504b0304) 
  241.                 return false; 
  242.         } catch (IOException e) { 
  243.             return false; 
  244.         } finally { 
  245.             try { 
  246.                 in.close(); 
  247.             } catch (IOException e) { 
  248.             } 
  249.         }*/  
  250.   
  251.         // second unZip file to check(without saving)   
  252.         boolean result = unzip(file);  
  253.         isChecking = false;  
  254.         return result;  
  255.     }  
  256.   
  257.     private boolean unzip(File unZipFile) {  
  258.         boolean succeed = true;  
  259.         ZipInputStream zin = null;  
  260.         ZipEntry entry = null;  
  261.         try {  
  262.             zin = new ZipInputStream(new FileInputStream(unZipFile));  
  263.             boolean first = true;  
  264.             while (true) {  
  265.                 if ((entry = zin.getNextEntry()) == null) {  
  266.                     if (first)  
  267.                         succeed = false;  
  268.                     break;  
  269.                 }  
  270.                 first = false;  
  271.                 if (entry.isDirectory()) {  
  272.                     zin.closeEntry();  
  273.                     continue;  
  274.                 }  
  275.                 if (!entry.isDirectory()) {  
  276.                     byte[] b = new byte[1024];  
  277.                     @SuppressWarnings("unused")  
  278.                     int len = 0;  
  279.                     while ((len = zin.read(b)) != -1) {  
  280.                     }  
  281.                     zin.closeEntry();  
  282.                 }  
  283.             }  
  284.         } catch (IOException e) {  
  285.             succeed = false;  
  286.         } finally {  
  287.             if (null != zin) {  
  288.                 try {  
  289.                     zin.close();  
  290.                 } catch (IOException e) {  
  291.                 }  
  292.             }  
  293.         }  
  294.         return succeed;  
  295.     }  
  296.   
  297.     public void showNotification() {  
  298.         float result = (float) downLength / (float) fileLength;  
  299.         int p = (int) (result * 100);  
  300.         if (p == 0 && isPercentZeroRunning || p == 100 && isChecking) {  
  301.             return;  
  302.         } else if (p != 0) {  
  303.             isPercentZeroRunning = false;  
  304.         }  
  305.         Notification notification = new Notification(R.drawable.icon, null,  
  306.                 0);  
  307.   
  308.         if (p == 100) {  
  309.             if (checkStatus == CHECK_RUNNING) {  
  310.                 notification.tickerText = "开始检查下载文件";  
  311.                 notification.when = System.currentTimeMillis();  
  312.                 notification.flags = notification.flags  
  313.                         | Notification.FLAG_ONGOING_EVENT  
  314.                         | Notification.FLAG_NO_CLEAR;  
  315.                 notification.contentView = rv;  
  316.                 notification.contentView.setProgressBar(  
  317.                         R.id.update_notification_progressbar, 100, p, true);  
  318.                 notification.contentView.setTextViewText(  
  319.                         R.id.update_notification_title, "正在检查下载文件...");  
  320.                 PendingIntent contentIntent = PendingIntent.getActivity(  
  321.                         context, 0null0);  
  322.                 notification.contentIntent = contentIntent;  
  323.                 isChecking = true;  
  324.             } else if (checkStatus == CHECK_FAILED) {  
  325.                 notification.tickerText = "文件验证失败!";  
  326.                 notification.when = System.currentTimeMillis();  
  327.                 notification.flags = notification.flags  
  328.                         | Notification.FLAG_AUTO_CANCEL;  
  329.                 notification.contentView = rv;  
  330.                 notification.contentView.setProgressBar(  
  331.                         R.id.update_notification_progressbar, 100, p, false);  
  332.                 notification.contentView.setTextViewText(  
  333.                         R.id.update_notification_title, "文件验证失败,请重新下载");  
  334.                 Intent notificationIntent = new Intent(context,  
  335.                         UpdateService.class);  
  336.                 notificationIntent.setAction(ACTION_STOP);  
  337.                 PendingIntent contentIntent = PendingIntent.getService(  
  338.                         context, 0, notificationIntent, 0);  
  339.                 notification.contentIntent = contentIntent;  
  340.             } else {  
  341.                 notification.tickerText = "下载完成";  
  342.                 notification.when = System.currentTimeMillis();  
  343.                 Intent notificationIntent = new Intent();  
  344.                 notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  345.                 notificationIntent  
  346.                         .setAction(android.content.Intent.ACTION_VIEW);  
  347.                 String type = "application/vnd.android.package-archive";  
  348.                 notificationIntent.setDataAndType(  
  349.                         Uri.parse("file:///data/data/"  
  350.                                 + context.getPackageName()  
  351.                                 + "/files/"  
  352.                                 + (Integer.valueOf(newestVersionCode)  
  353.                                         .toString()) + ".apk"), type);  
  354.                 notification.contentView = rv;  
  355.                 notification.flags = Notification.FLAG_AUTO_CANCEL;  
  356.                 notification.contentView  
  357.                         .setProgressBar(  
  358.                                 R.id.update_notification_progressbar, 100,  
  359.                                 p, false);  
  360.                 notification.contentView.setTextViewText(  
  361.                         R.id.update_notification_progresstext, p + "%");  
  362.                 notification.contentView.setTextViewText(  
  363.                         R.id.update_notification_title, "下载完成,点击安装");  
  364.                 PendingIntent contentIntent = PendingIntent.getActivity(  
  365.                         context, 0, notificationIntent, 0);  
  366.                 notification.contentIntent = contentIntent;  
  367.             }  
  368.   
  369.         } else if (p == 0) {  
  370.             notification.tickerText = "准备下载";  
  371.             notification.when = System.currentTimeMillis();  
  372.             Intent notificationIntent = new Intent(context,  
  373.                     UpdateService.class);  
  374.             notificationIntent.setAction(ACTION_STOP);  
  375.             notification.flags = notification.flags  
  376.                     | Notification.FLAG_ONGOING_EVENT;  
  377.             notification.contentView = rv;  
  378.             notification.contentView.setProgressBar(  
  379.                     R.id.update_notification_progressbar, 100, p, true);  
  380.             notification.contentView.setTextViewText(  
  381.                     R.id.update_notification_title, "正在准备下载(点击取消)");  
  382.             PendingIntent contentIntent = PendingIntent.getService(context,  
  383.                     0, notificationIntent, 0);  
  384.             notification.contentIntent = contentIntent;  
  385.             isPercentZeroRunning = true;  
  386.         } else {  
  387.             notification.tickerText = "开始下载";  
  388.             notification.when = System.currentTimeMillis();  
  389.             Intent notificationIntent = new Intent(context,  
  390.                     UpdateService.class);  
  391.             notificationIntent.setAction(ACTION_STOP);  
  392.             notification.contentView = rv;  
  393.             notification.flags = notification.flags  
  394.                     | Notification.FLAG_ONGOING_EVENT;  
  395.             notification.contentView.setProgressBar(  
  396.                     R.id.update_notification_progressbar, 100, p, false);  
  397.             notification.contentView.setTextViewText(  
  398.                     R.id.update_notification_progresstext, p + "%");  
  399.             notification.contentView.setTextViewText(  
  400.                     R.id.update_notification_title, "正在下载(点击取消)");  
  401.             PendingIntent contentIntent = PendingIntent.getService(context,  
  402.                     0, notificationIntent, 0);  
  403.             notification.contentIntent = contentIntent;  
  404.         }  
  405.   
  406.         mNM.notify(MOOD_NOTIFICATIONS, notification);  
  407.     }  
  408. }  
	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

 

 

  1. while ((offset = is.read(buffer, 0, BUFFER_SIZE)) != -1  
  2.                                 && !stop) {  
  3.                             outStream.write(buffer, 0, offset);  
  4.                             downLength += offset;  
  5.                         }  
while ((offset = is.read(buffer, 0, BUFFER_SIZE)) != -1
								&& !stop) {
							outStream.write(buffer, 0, offset);
							downLength += offset;
						}

有了这个就可以在外面控制强制停止下载。

 

 

在下载开始阶段我最先做的时就是检查网络情况,如果没有网络,我就使用一个block让这个线程阻塞掉,有人会问那什么时候恢复呢?我在service里面加了个广播

 

  1. private final BroadcastReceiver receiver = new BroadcastReceiver() {  
  2.   
  3.         @Override  
  4.         public void onReceive(Context context, Intent intent) {  
  5.             if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent  
  6.                     .getAction())) {  
  7.                 NetworkInfo info = (NetworkInfo) intent  
  8.                         .getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);  
  9.                 boolean hasConnectivity = (info != null && info.isConnected()) ? true  
  10.                         : false;  
  11.                 if (hasConnectivity && downloadThread != null) {  
  12.                     downloadThread.interrupt();  
  13.                 }  
  14.             }  
  15.         }  
  16.   
  17. };  
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文件是否正常,暂时也没想到更好的办法。

 

  1. private boolean unzip(File unZipFile) {  
  2.     boolean succeed = true;  
  3.     ZipInputStream zin = null;  
  4.     ZipEntry entry = null;  
  5.     try {  
  6.         zin = new ZipInputStream(new FileInputStream(unZipFile));  
  7.         boolean first = true;  
  8.         while (true) {  
  9.             if ((entry = zin.getNextEntry()) == null) {  
  10.                 if (first)  
  11.                     succeed = false;  
  12.                 break;  
  13.             }  
  14.             first = false;  
  15.             if (entry.isDirectory()) {  
  16.                 zin.closeEntry();  
  17.                 continue;  
  18.             }  
  19.             if (!entry.isDirectory()) {  
  20.                 byte[] b = new byte[1024];  
  21.                 @SuppressWarnings("unused")  
  22.                 int len = 0;  
  23.                 while ((len = zin.read(b)) != -1) {  
  24.                 }  
  25.                 zin.closeEntry();  
  26.             }  
  27.         }  
  28.     } catch (IOException e) {  
  29.         succeed = false;  
  30.     } finally {  
  31.         if (null != zin) {  
  32.             try {  
  33.                 zin.close();  
  34.             } catch (IOException e) {  
  35.             }  
  36.         }  
  37.     }  
  38.     return succeed;  
  39. }  
		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;
		}


分享到:
评论

相关推荐

    STM32单片机通过ESP8266WiFi模块与Android APP实现数据传输软件例程源码.zip

    该压缩包文件“STM32单片机通过ESP8266WiFi模块与Android APP实现数据传输软件例程源码.zip”提供了一个完整的系统,使STM32单片机能够通过ESP8266 WiFi模块与Android应用程序进行数据通信。这个系统的核心在于STM32...

    android与WIFI模块的数据传输 数据透传

    在Android平台上,与WIFI模块进行数据传输是一个常见的任务,特别是在物联网(IoT)设备的交互中。本项目中,开发者被要求创建一个Android应用程序,用于配置HLK-RM04 WIFI模块,进行数据读取、参数设置以及系统时间...

    智能家居Android APP源码

    智能家居Android APP源码的分析和学习涵盖了Android开发的许多方面,对于希望深入理解Android系统和智能家居系统集成的开发者来说,这是一个宝贵的资源。通过研究源码,开发者可以了解如何构建一个完整的智能家居...

    Android Studio设计APP实现与51单片机通过WIFI模块(ESP8266-01S)通讯控制LED灯亮灭的设计APP

    Github已开源:...详细内容也可看我的文章:https://mp.csdn.net/mp_blog/creation/editor/124478948《Android Studio设计APP实现与51单片机通过WIFI模块(ESP8266-01S)通讯控制LED灯亮灭的设计源码【详解】》

    Android-Car电商平台App购物车模块功能

    在Android开发领域,构建一个电商平台App的购物车模块是一项关键任务。购物车模块是用户与应用交互的重要环节,它需要实现一系列复杂的功能来提供顺畅的购物体验。在"Android-Car电商平台App购物车模块功能"这个项目...

    基于Android studio编写的一款新闻app

    Android Studio是Google推出的一款强大的Android应用程序开发工具,它基于IntelliJ IDEA,提供了高效的代码编辑、构建、调试和分析功能,是Android开发者首选的集成开发环境(IDE)。在这个项目中,我们看到的是一个...

    AndroidAPP开发入门教程.pdf

    本教程主要介绍了Android APP开发的基本步骤,从SDK下载、开发环境搭建、代码编写、APP打包等步骤一一讲解,为读者提供了一个简明的Android 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

    "ChameleonMiniApp变色龙硬件Android APP"是一款专为Android设备设计的应用程序,它能够与硬件设备通过OTG(On-The-Go)技术进行连接,实现设备间的交互。这款APP未经编译,用户需要自行在手机上进行安装。下面我们...

    make the android app

    根据提供的文件信息,我们可以推断出这是一段与Android应用构建相关的脚本代码。下面将对这段代码进行...当然,实际操作过程中还可能遇到各种具体问题,需要不断学习和实践才能熟练掌握。希望这些内容对你有所帮助!

    鸿蒙App与Android APP的联系

    本文将深入探讨鸿蒙App与Android App之间的联系,帮助开发者理解两者间的共通之处以及差异性。 首先,我们要知道鸿蒙OS和Android系统都是基于Linux内核构建的,这为两者之间建立联系提供了基础。鸿蒙OS采用了微内核...

    Android Studio项目《天气预报app》

    总的来说,这个《天气预报app》项目涵盖了Android应用开发的多个关键方面,如UI设计、网络请求(获取天气数据)、数据解析(处理JSON数据)、以及与系统服务的交互(获取位置信息)。开发者可以通过这个项目学习如何...

    基于Android的BlueTooth开发手机蓝牙和蓝牙模块通讯

    11. **蓝牙问题处理**:在实际开发中,可能会遇到连接不稳定、数据丢失等问题,需要考虑重试机制和错误处理。此外,不同Android版本的蓝牙API可能存在差异,需适配不同系统版本。 通过以上知识点,开发者可以构建一...

    自己从零编写的Android记事本APP软件

    标题中的“自己从零编写的Android记事本APP软件”表明了这是一个个人开发的项目,开发者从基础开始,没有依赖现成的框架或模板,完全通过自己的编程技能创建了一个Android平台上的记事本应用程序。这涉及到Android...

    Android手机上位机蓝牙APP

    1. **Android应用开发**:APP是为Android操作系统定制的,使用Java或Kotlin等语言编写,遵循Android SDK和Android Studio的开发环境。 2. **蓝牙通信**:APP具备蓝牙功能,能够与具有蓝牙功能的硬件设备(如STM32微...

    Android APP安全评估工具-Drozer.zip

    《Android APP安全评估工具——Drozer详解》 在当今移动互联网时代,Android应用程序的安全性愈发受到关注。作为开发者和安全研究人员,确保APP免受恶意攻击是至关重要的任务。Drozer,一款专为Android应用安全评估...

    android 新闻app源码

    【Android 新闻App源码详解】 在Android平台上开发新闻应用是一项常见的任务,它涉及到许多核心技术和组件的集成。...学习和分析这样的源码,对于Android开发者来说,是提升技能和解决问题的重要途径。

    android Camera模块分析

    总结来说,Android Camera模块的运行依赖于JNI来桥接Java和原生代码,Mediaserver进程中的CameraService负责处理相机相关的操作。Binder机制则确保了JAVA应用进程与CameraService进程间的高效、安全通信,使得应用...

    APP自启动模块 实现独个APP开机自启动

    本教程将详细讲解如何在Android项目中实现一个单独的APP自启动模块。 首先,我们要明白在Android系统中,自启动机制是受到一定限制的,尤其是从Android 6.0(API级别23)开始,系统引入了运行时权限管理,对应用的...

Global site tag (gtag.js) - Google Analytics