概述: 为了以快速并且节约的方式让APP更新版本,通常需要在APP内增加自动检测更新新版本的功能。
运行截图: ) ) )
实现:4个步骤
1.在服务端放置存储版本信息的文件 一般以json格式保存必要的信息:apk文件下载地址、版本号、更新内容
1 2 3 4 5 6 { "url":"http://crazyfzw.github.io/demo/auto-update-version/new-version-v2.0.apk", "versionCode":2, "updateMessage":"[1]新增视频弹幕功能<br /> [2]优化离线缓存功能<br /> [3]增强了稳定性" }
2.继承AsyncTask创建一个异步任务去下载版本信息文件 从服务器取得版本信息,与本地apk对比版本号,判断是否有更新,若有,则以Dialog让用户选择是否更新,若用户选择更新,则调用服务去完成apk文件的下载
CheckVersionInfoTask.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 public class CheckVersionInfoTask extends AsyncTask <Void , Void , String > { private static final String TAG = "CheckVersionInfoTask" ; private ProgressDialog dialog; private Context mContext; private boolean mShowProgressDialog; private static final String VERSION_INFO_URL = "http://crazyfzw.github.io/demo/auto-update-version/update.json" ; public CheckVersionInfoTask (Context context, boolean showProgressDialog) { this .mContext = context; this .mShowProgressDialog = showProgressDialog; } protected void onPreExecute () { if (mShowProgressDialog) { dialog = new ProgressDialog(mContext); dialog.setMessage(mContext.getString(R.string.check_new_version)); dialog.show(); } } @Override protected String doInBackground (Void... params) { return getVersionInfo(VERSION_INFO_URL); } @Override protected void onPostExecute (String result) { if (dialog != null && dialog.isShowing()) { dialog.dismiss(); } if (!TextUtils.isEmpty(result)) { parseJson(result); } } public String getVersionInfo (String urlStr) { HttpURLConnection uRLConnection = null ; InputStream is = null ; BufferedReader buffer = null ; String result = null ; try { URL url = new URL(urlStr); uRLConnection = (HttpURLConnection) url.openConnection(); uRLConnection.setRequestMethod("GET" ); is = uRLConnection.getInputStream(); buffer = new BufferedReader(new InputStreamReader(is)); StringBuilder strBuilder = new StringBuilder(); String line; while ((line = buffer.readLine()) != null ) { strBuilder.append(line); } result = strBuilder.toString(); } catch (Exception e) { Log.e(TAG, "http post error" ); } finally { if (buffer != null ) { try { buffer.close(); } catch (IOException ignored) { } } if (is != null ) { try { is.close(); } catch (IOException ignored) { } } if (uRLConnection != null ) { uRLConnection.disconnect(); } } return result; } private void parseJson (String result) { try { JSONObject obj = new JSONObject(result); String apkUrl = obj.getString("url" ); String updateMessage = obj.getString("updateMessage" ); int apkCode = obj.getInt("versionCode" ); int versionCode = getCurrentVersionCode(); if (apkCode > versionCode) { showDialog(updateMessage, apkUrl); } else if (mShowProgressDialog) { Toast.makeText(mContext, mContext.getString(R.string.there_no_new_version), Toast.LENGTH_SHORT).show(); } } catch (JSONException e) { Log.e(TAG, "parse json error" ); } } public int getCurrentVersionCode () { try { return mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0 ).versionCode; } catch (PackageManager.NameNotFoundException ignored) { } return 0 ; } public void showDialog (String content, final String downloadUrl) { AlertDialog.Builder builder = new AlertDialog.Builder(mContext); builder.setTitle(R.string.dialog_choose_update_title); builder.setMessage(Html.fromHtml(content)) .setPositiveButton(R.string.dialog_btn_confirm_download, new DialogInterface.OnClickListener() { public void onClick (DialogInterface dialog, int id) { goToDownloadApk(downloadUrl); } }) .setNegativeButton(R.string.dialog_btn_cancel_download, new DialogInterface.OnClickListener() { public void onClick (DialogInterface dialog, int id) { } }); AlertDialog dialog = builder.create(); dialog.setCanceledOnTouchOutside(false ); dialog.show(); } private void goToDownloadApk (String downloadUrl) { Intent intent = new Intent(mContext, DownloadApkService.class ) ; intent.putExtra("apkUrl" , downloadUrl); mContext.startService(intent); } }
注:其中用到的 StorageUtils.getCacheDirectory(context) 是用于取得应用在手机缓存目录
StorageUtils.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 final class StorageUtils { private static final String TAG = "StorageUtils" ; private static final String EXTERNAL_STORAGE_PERMISSION = "android.permission.WRITE_EXTERNAL_STORAGE" ; private StorageUtils () { } public static File getCacheDirectory (Context context) { File appCacheDir = null ; if (MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && hasExternalStoragePermission(context)) { appCacheDir = getExternalCacheDir(context); } if (appCacheDir == null ) { appCacheDir = context.getCacheDir(); } if (appCacheDir == null ) { Log.w(TAG, "Can't define system cache directory! The app should be re-installed." ); } return appCacheDir; } private static File getExternalCacheDir (Context context) { File dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android" ), "data" ); File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache" ); if (!appCacheDir.exists()) { if (!appCacheDir.mkdirs()) { Log.w(TAG, "Unable to create external cache directory" ); return null ; } try { new File(appCacheDir, ".nomedia" ).createNewFile(); } catch (IOException e) { Log.i(TAG, "Can't create \".nomedia\" file in application external cache directory" ); } } return appCacheDir; } private static boolean hasExternalStoragePermission (Context context) { int perm = context.checkCallingOrSelfPermission(EXTERNAL_STORAGE_PERMISSION); return perm == PackageManager.PERMISSION_GRANTED; } }
3.创建服务(Service)完成apk文件的下载,下载完成后调用系统的安装程序完成安装 DownloadApkService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 public class DownloadApkService extends IntentService { private static final int BUFFER_SIZE = 10 * 1024 ; private static final String TAG = "DownloadService" ; private static final int NOTIFICATION_ID = 0 ; private NotificationManager mNotifyManager; private NotificationCompat.Builder mBuilder; public DownloadApkService () { super ("DownloadApkService" ); } @Override protected void onHandleIntent (Intent intent) { mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); mBuilder = new NotificationCompat.Builder(this ); String appName = getString(getApplicationInfo().labelRes); int icon = getApplicationInfo().icon; mBuilder.setContentTitle(appName).setSmallIcon(icon); String urlStr = intent.getStringExtra("apkUrl" ); InputStream in = null ; FileOutputStream out = null ; try { URL url = new URL(urlStr); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setRequestMethod("GET" ); urlConnection.setDoOutput(false ); urlConnection.setConnectTimeout(10 * 1000 ); urlConnection.setReadTimeout(10 * 1000 ); urlConnection.setRequestProperty("Connection" , "Keep-Alive" ); urlConnection.setRequestProperty("Charset" , "UTF-8" ); urlConnection.setRequestProperty("Accept-Encoding" , "gzip, deflate" ); urlConnection.connect(); long bytetotal = urlConnection.getContentLength(); long bytesum = 0 ; int byteread = 0 ; in = urlConnection.getInputStream(); File dir = StorageUtils.getCacheDirectory(this ); String apkName = urlStr.substring(urlStr.lastIndexOf("/" ) + 1 , urlStr.length()); File apkFile = new File(dir, apkName); out = new FileOutputStream(apkFile); byte [] buffer = new byte [BUFFER_SIZE]; int limit = 0 ; int oldProgress = 0 ; while ((byteread = in.read(buffer)) != -1 ) { bytesum += byteread; out.write(buffer, 0 , byteread); int progress = (int ) (bytesum * 100L / bytetotal); if (progress != oldProgress) { updateProgress(progress); } oldProgress = progress; } installAPk(apkFile); Log.d("调试" ,"download apk finish" ); mNotifyManager.cancel(NOTIFICATION_ID); } catch (Exception e) { Log.e(TAG, "download apk file error" ); } finally { if (out != null ) { try { out.close(); } catch (IOException ignored) { } } if (in != null ) { try { in.close(); } catch (IOException ignored) { } } } } private void updateProgress (int progress) { mBuilder.setContentText(this .getString(R.string.dialog_choose_update_content, progress)).setProgress(100 , progress, false ); PendingIntent pendingintent = PendingIntent.getActivity(this , 0 , new Intent(), PendingIntent.FLAG_CANCEL_CURRENT); mBuilder.setContentIntent(pendingintent); mNotifyManager.notify(NOTIFICATION_ID, mBuilder.build()); } private void installAPk (File apkFile) { Intent intent = new Intent(Intent.ACTION_VIEW); try { String[] command = {"chmod" , "777" , apkFile.toString()}; ProcessBuilder builder = new ProcessBuilder(command); builder.start(); } catch (IOException ignored) { } intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive" ); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } }
4.最后一步,new并execute我们写好的异步任务就行了。 1 new CheckVersionInfoTask(MainActivity.this, true).execute();
注:记得在AndroidManifest.xml中生明权限及注册Service哦
1 2 <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
本案例完整源码:https://github.com/crazyfzw/AppAutoCheckUpdate
用到的相关知识: 异步任务AsysTask的相关用法:郭霖的Android AsyncTask完全解析,带你从源码的角度彻底理解
自动启动线程执行耗时任务并会自动停止的服务 IntentService的用法:Android理解:IntentService