├── README.md └── UpdateDemo ├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── app ├── .gitignore ├── build.gradle ├── libs │ └── nineoldandroids-2.4.0.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── hsy │ │ └── update │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── hsy │ │ │ └── update │ │ │ ├── MainActivity.java │ │ │ ├── utils │ │ │ ├── ColorData.java │ │ │ └── Tools.java │ │ │ └── view │ │ │ ├── BaseAlertDialog.java │ │ │ ├── BaseAnimatorSet.java │ │ │ ├── BaseDialog.java │ │ │ ├── CommonProgressDialog.java │ │ │ ├── CornerUtils.java │ │ │ ├── MaterialDialog.java │ │ │ ├── OnBtnClickL.java │ │ │ └── StatusBarUtils.java │ └── res │ │ ├── drawable │ │ ├── common_progress_dialog_progressbar2.png │ │ ├── common_progress_dialog_progressbar3.png │ │ └── common_progressdialog_progressbar_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── common_progress_dialog.xml │ │ └── title_dialog.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── hsy │ └── update │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /README.md: -------------------------------------------------------------------------------- 1 | # Android应用更新-自动检测版本及自动升级 2 | 3 | ## 步骤: 4 | * 1.检测当前版本的信息AndroidManifest.xml-->manifest-->[Android] 5 | 6 | * 2.从服务器获取版本号(版本号存在于xml文件中)并与当前检测到的版本进行匹配,如果不匹配,提示用户进行升级,如果匹配则进入程序主界面。(demo中假设需要更新) 7 | 8 | * 3.当提示用户进行版本升级时,如果用户点击了“更新”,系统将自动从服务器上下载安装包并进行自动升级,如果点击取消将进入程序主界面。 9 | 10 | # 效果图如下: 11 | 12 | ![更新](http://upload-images.jianshu.io/upload_images/3805053-cbbe809e3cbf8c96.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 13 | ![下载1](http://upload-images.jianshu.io/upload_images/3805053-cf963c6429bd3147.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 14 | 15 | ![下载2](http://upload-images.jianshu.io/upload_images/3805053-03ce7e0933e1f8ea.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 16 | 17 | 18 | ![安装](http://upload-images.jianshu.io/upload_images/3805053-4ebbc800d33af51e.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 19 | **** 20 | 21 | 22 | 23 | # 下面介绍一下代码的实现: 24 | 25 | * 1.获取应用的当前版本号,我是封装了一个工具类来获取 26 | ``` 27 | 28 | // 获取本版本号,是否更新 29 | int vision = Tools.getVersion(this); 30 | 31 | ``` 32 | 33 | 获取当前版本号工具类: 34 | 35 | ``` 36 | 37 | public class Tools { 38 | /** 39 | * 检查是否存在SDCard 40 | * 41 | * @return 42 | */ 43 | public static boolean hasSdcard() { 44 | String state = Environment.getExternalStorageState(); 45 | if (state.equals(Environment.MEDIA_MOUNTED)) { 46 | return true; 47 | } else { 48 | return false; 49 | } 50 | } 51 | 52 | /** 53 | * 2 * 获取版本号 3 * @return 当前应用的版本号 4 54 | */ 55 | public static int getVersion(Context context) { 56 | try { 57 | PackageManager manager = context.getPackageManager(); 58 | PackageInfo info = manager.getPackageInfo(context.getPackageName(), 59 | 0); 60 | String version = info.versionName; 61 | int versioncode = info.versionCode; 62 | return versioncode; 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | } 66 | return 0; 67 | } 68 | 69 | } 70 | ``` 71 | 72 | * 2.获取服务器版本号,是否要更新(此处就是简单的网络请求拿到需要的数据即可,我是写了固定值) 73 | 74 | ``` 75 | // 获取更新版本号 76 | private void getVersion(final int vision) { 77 | // {"data":{"content":"其他bug修复。","id":"2","api_key":"android", 78 | // // "version":"2.1"},"msg":"获取成功","status":1} 79 | String data = ""; 80 | //网络请求获取当前版本号和下载链接 81 | //实际操作是从服务器获取 82 | //demo写死了 83 | 84 | String newversion = "2.1";//更新新的版本号 85 | String content = "\n" + 86 | "就不告诉你我们更新了什么-。-\n" + 87 | "\n" + 88 | "----------万能的分割线-----------\n" + 89 | "\n" + 90 | "(ㄒoㄒ) 被老板打了一顿,还是来告诉你吧:\n" + 91 | 92 | "1.下架商品误买了?恩。。。我搞了点小动作就不会出现了\n" + 93 | "2.侧边栏、弹框优化 —— 这个你自己去探索吧,总得留点悬念嘛-。-\n";//更新内容 94 | String url = "http://openbox.mobilem.360.cn/index/d/sid/3429345";//安装包下载地址 95 | 96 | double newversioncode = Double 97 | .parseDouble(newversion); 98 | int cc = (int) (newversioncode); 99 | 100 | System.out.println(newversion + "v" + vision + ",," 101 | + cc); 102 | if (cc != vision) { 103 | if (vision < cc) { 104 | System.out.println(newversion + "v" 105 | + vision); 106 | // 版本号不同 107 | ShowDialog(vision, newversion, content, url); 108 | } 109 | } 110 | } 111 | ``` 112 | 113 | * 3.接下来就是下载文件了 114 | ## (1) 显示下载 115 | ### 此处用的是自定义按钮: 116 | 117 | ``` 118 | /** 119 | * 升级系统 120 | * 121 | * @param content 122 | * @param url 123 | */ 124 | private void ShowDialog(int vision, String newversion, String content, 125 | final String url) { 126 | final MaterialDialog dialog = new MaterialDialog(this); 127 | dialog.content(content).btnText("取消", "更新").title("版本更新 ") 128 | .titleTextSize(15f).show(); 129 | dialog.setCanceledOnTouchOutside(false); 130 | dialog.setOnBtnClickL(new OnBtnClickL() {// left btn click listener 131 | @Override 132 | public void onBtnClick() { 133 | dialog.dismiss(); 134 | } 135 | }, new OnBtnClickL() {// right btn click listener 136 | 137 | @Override 138 | public void onBtnClick() { 139 | dialog.dismiss(); 140 | // pBar = new ProgressDialog(MainActivity.this, 141 | // R.style.dialog); 142 | pBar = new CommonProgressDialog(MainActivity.this); 143 | pBar.setCanceledOnTouchOutside(false); 144 | pBar.setTitle("正在下载"); 145 | pBar.setCustomTitle(LayoutInflater.from( 146 | MainActivity.this).inflate( 147 | R.layout.title_dialog, null)); 148 | pBar.setMessage("正在下载"); 149 | pBar.setIndeterminate(true); 150 | pBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); 151 | pBar.setCancelable(true); 152 | // downFile(URLData.DOWNLOAD_URL); 153 | final DownloadTask downloadTask = new DownloadTask( 154 | MainActivity.this); 155 | downloadTask.execute(url); 156 | pBar.setOnCancelListener(new DialogInterface.OnCancelListener() { 157 | @Override 158 | public void onCancel(DialogInterface dialog) { 159 | downloadTask.cancel(true); 160 | } 161 | }); 162 | } 163 | }); 164 | } 165 | 166 | ``` 167 | 168 | ### 原生的按钮: 169 | 170 | ``` 171 | new android.app.AlertDialog.Builder(this) 172 | .setTitle("版本更新") 173 | .setMessage(content) 174 | .setPositiveButton("更新", new DialogInterface.OnClickListener() { 175 | @Override 176 | public void onClick(DialogInterface dialog, int which) { 177 | dialog.dismiss(); 178 | pBar = new CommonProgressDialog(MainActivity.this); 179 | pBar.setCanceledOnTouchOutside(false); 180 | pBar.setTitle("正在下载"); 181 | pBar.setCustomTitle(LayoutInflater.from( 182 | MainActivity.this).inflate( 183 | R.layout.title_dialog, null)); 184 | pBar.setMessage("正在下载"); 185 | pBar.setIndeterminate(true); 186 | pBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); 187 | pBar.setCancelable(true); 188 | // downFile(URLData.DOWNLOAD_URL); 189 | final DownloadTask downloadTask = new DownloadTask( 190 | MainActivity.this); 191 | downloadTask.execute(url); 192 | pBar.setOnCancelListener(new DialogInterface.OnCancelListener() { 193 | @Override 194 | public void onCancel(DialogInterface dialog) { 195 | downloadTask.cancel(true); 196 | } 197 | }); 198 | } 199 | }) 200 | .setNegativeButton("取消", new DialogInterface.OnClickListener() { 201 | @Override 202 | public void onClick(DialogInterface dialog, int which) { 203 | dialog.dismiss(); 204 | } 205 | }) 206 | .show(); 207 | ``` 208 | ### (2)通过异步任务实现进度++ 209 | 210 | ``` 211 | 212 | /** 213 | * 下载应用 214 | * 215 | * @author Administrator 216 | */ 217 | class DownloadTask extends AsyncTask { 218 | 219 | private Context context; 220 | private PowerManager.WakeLock mWakeLock; 221 | 222 | public DownloadTask(Context context) { 223 | this.context = context; 224 | } 225 | 226 | @Override 227 | protected String doInBackground(String... sUrl) { 228 | InputStream input = null; 229 | OutputStream output = null; 230 | HttpURLConnection connection = null; 231 | File file = null; 232 | try { 233 | URL url = new URL(sUrl[0]); 234 | connection = (HttpURLConnection) url.openConnection(); 235 | connection.connect(); 236 | // expect HTTP 200 OK, so we don't mistakenly save error 237 | // report 238 | // instead of the file 239 | if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { 240 | return "Server returned HTTP " 241 | + connection.getResponseCode() + " " 242 | + connection.getResponseMessage(); 243 | } 244 | // this will be useful to display download percentage 245 | // might be -1: server did not report the length 246 | int fileLength = connection.getContentLength(); 247 | if (Environment.getExternalStorageState().equals( 248 | Environment.MEDIA_MOUNTED)) { 249 | file = new File(Environment.getExternalStorageDirectory(), 250 | DOWNLOAD_NAME); 251 | 252 | if (!file.exists()) { 253 | // 判断父文件夹是否存在 254 | if (!file.getParentFile().exists()) { 255 | file.getParentFile().mkdirs(); 256 | } 257 | } 258 | 259 | } else { 260 | Toast.makeText(MainActivity.this, "sd卡未挂载", 261 | Toast.LENGTH_LONG).show(); 262 | } 263 | input = connection.getInputStream(); 264 | output = new FileOutputStream(file); 265 | byte data[] = new byte[4096]; 266 | long total = 0; 267 | int count; 268 | while ((count = input.read(data)) != -1) { 269 | // allow canceling with back button 270 | if (isCancelled()) { 271 | input.close(); 272 | return null; 273 | } 274 | total += count; 275 | // publishing the progress.... 276 | if (fileLength > 0) // only if total length is known 277 | publishProgress((int) (total * 100 / fileLength)); 278 | output.write(data, 0, count); 279 | 280 | } 281 | } catch (Exception e) { 282 | System.out.println(e.toString()); 283 | return e.toString(); 284 | 285 | } finally { 286 | try { 287 | if (output != null) 288 | output.close(); 289 | if (input != null) 290 | input.close(); 291 | } catch (IOException ignored) { 292 | } 293 | if (connection != null) 294 | connection.disconnect(); 295 | } 296 | return null; 297 | } 298 | 299 | @Override 300 | protected void onPreExecute() { 301 | super.onPreExecute(); 302 | // take CPU lock to prevent CPU from going off if the user 303 | // presses the power button during download 304 | PowerManager pm = (PowerManager) context 305 | .getSystemService(Context.POWER_SERVICE); 306 | mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 307 | getClass().getName()); 308 | mWakeLock.acquire(); 309 | pBar.show(); 310 | } 311 | 312 | @Override 313 | protected void onProgressUpdate(Integer... progress) { 314 | super.onProgressUpdate(progress); 315 | // if we get here, length is known, now set indeterminate to false 316 | pBar.setIndeterminate(false); 317 | pBar.setMax(100); 318 | pBar.setProgress(progress[0]); 319 | } 320 | 321 | @Override 322 | protected void onPostExecute(String result) { 323 | mWakeLock.release(); 324 | pBar.dismiss(); 325 | if (result != null) { 326 | 327 | // // 申请多个权限。大神的界面 328 | // AndPermission.with(MainActivity.this) 329 | // .requestCode(REQUEST_CODE_PERMISSION_OTHER) 330 | // .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE) 331 | // // rationale作用是:用户拒绝一次权限,再次申请时先征求用户同意,再打开授权对话框,避免用户勾选不再提示。 332 | // .rationale(new RationaleListener() { 333 | // @Override 334 | // public void showRequestPermissionRationale(int requestCode, Rationale rationale) { 335 | // // 这里的对话框可以自定义,只要调用rationale.resume()就可以继续申请。 336 | // AndPermission.rationaleDialog(MainActivity.this, rationale).show(); 337 | // } 338 | // } 339 | // ) 340 | // .send(); 341 | // 申请多个权限。 342 | AndPermission.with(MainActivity.this) 343 | .requestCode(REQUEST_CODE_PERMISSION_SD) 344 | .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE) 345 | // rationale作用是:用户拒绝一次权限,再次申请时先征求用户同意,再打开授权对话框,避免用户勾选不再提示。 346 | .rationale(rationaleListener 347 | ) 348 | .send(); 349 | 350 | 351 | Toast.makeText(context, "您未打开SD卡权限" + result, Toast.LENGTH_LONG).show(); 352 | } else { 353 | // Toast.makeText(context, "File downloaded", 354 | // Toast.LENGTH_SHORT) 355 | // .show(); 356 | update(); 357 | } 358 | 359 | } 360 | } 361 | ``` 362 | 此处下载apk文件,需要获取SD的读写权限(用的是严大的权限库) 363 | 权限库GitHub:https://github.com/yanzhenjie/AndPermission 364 | 365 | ``` 366 | private static final int REQUEST_CODE_PERMISSION_SD = 101; 367 | 368 | private static final int REQUEST_CODE_SETTING = 300; 369 | private RationaleListener rationaleListener = new RationaleListener() { 370 | @Override 371 | public void showRequestPermissionRationale(int requestCode, final Rationale rationale) { 372 | // 这里使用自定义对话框,如果不想自定义,用AndPermission默认对话框: 373 | // AndPermission.rationaleDialog(Context, Rationale).show(); 374 | 375 | // 自定义对话框。 376 | AlertDialog.build(MainActivity.this) 377 | .setTitle(R.string.title_dialog) 378 | .setMessage(R.string.message_permission_rationale) 379 | .setPositiveButton(R.string.btn_dialog_yes_permission, new DialogInterface.OnClickListener() { 380 | @Override 381 | public void onClick(DialogInterface dialog, int which) { 382 | dialog.cancel(); 383 | rationale.resume(); 384 | } 385 | }) 386 | 387 | .setNegativeButton(R.string.btn_dialog_no_permission, new DialogInterface.OnClickListener() { 388 | @Override 389 | public void onClick(DialogInterface dialog, int which) { 390 | dialog.cancel(); 391 | rationale.cancel(); 392 | } 393 | }) 394 | .show(); 395 | } 396 | }; 397 | //----------------------------------SD权限----------------------------------// 398 | 399 | 400 | @PermissionYes(REQUEST_CODE_PERMISSION_SD) 401 | private void getMultiYes(List grantedPermissions) { 402 | Toast.makeText(this, R.string.message_post_succeed, Toast.LENGTH_SHORT).show(); 403 | } 404 | 405 | @PermissionNo(REQUEST_CODE_PERMISSION_SD) 406 | private void getMultiNo(List deniedPermissions) { 407 | Toast.makeText(this, R.string.message_post_failed, Toast.LENGTH_SHORT).show(); 408 | 409 | // 用户否勾选了不再提示并且拒绝了权限,那么提示用户到设置中授权。 410 | if (AndPermission.hasAlwaysDeniedPermission(this, deniedPermissions)) { 411 | AndPermission.defaultSettingDialog(this, REQUEST_CODE_SETTING) 412 | .setTitle(R.string.title_dialog) 413 | .setMessage(R.string.message_permission_failed) 414 | .setPositiveButton(R.string.btn_dialog_yes_permission) 415 | .setNegativeButton(R.string.btn_dialog_no_permission, null) 416 | .show(); 417 | 418 | // 更多自定dialog,请看上面。 419 | } 420 | } 421 | 422 | //----------------------------------权限回调处理----------------------------------// 423 | 424 | @Override 425 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] 426 | grantResults) { 427 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 428 | /** 429 | * 转给AndPermission分析结果。 430 | * 431 | * @param object 要接受结果的Activity、Fragment。 432 | * @param requestCode 请求码。 433 | * @param permissions 权限数组,一个或者多个。 434 | * @param grantResults 请求结果。 435 | */ 436 | AndPermission.onRequestPermissionsResult(this, requestCode, permissions, grantResults); 437 | } 438 | 439 | @Override 440 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 441 | switch (requestCode) { 442 | case REQUEST_CODE_SETTING: { 443 | Toast.makeText(this, R.string.message_setting_back, Toast.LENGTH_LONG).show(); 444 | //设置成功,再次请求更新 445 | getVersion(Tools.getVersion(MainActivity.this)); 446 | break; 447 | } 448 | } 449 | } 450 | ``` 451 | 452 | (3) 当apk文件下载完毕时,打开安装 453 | 454 | ``` 455 | private void update() { 456 | //安装应用 457 | Intent intent = new Intent(Intent.ACTION_VIEW); 458 | intent.setDataAndType(Uri.fromFile(new File(Environment 459 | .getExternalStorageDirectory(), DOWNLOAD_NAME)), 460 | "application/vnd.android.package-archive"); 461 | startActivity(intent); 462 | } 463 | ``` 464 | 465 | # Android 7.0 FileUriExposedException 的处理 466 | 467 | ### 发现问题 468 | 469 | 前几天把手机系统升级到基于 Android 7.0,后来在升级调试一个应用时抛出如下异常信息: 470 | ``` 471 | android.os.FileUriExposedException: file:///storage/emulated/0/Android/data/com.skyrin.bingo/cache/app/app.apk exposed beyond app through Intent.getData() 472 | at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799) 473 | 474 | at com.skyrin.bingo.update.AppUpdate.installApk(AppUpdate.java:295) 475 | ``` 476 | 根据如上日志找到 AppUpdate 类下的 installApk 方法: 477 | ``` 478 | /** 479 | * 安装apk 480 | */ 481 | public static void installApk(Context context,String apkPath) { 482 | if (TextUtils.isEmpty(apkPath)){ 483 | Toast.makeText(context,"更新失败!未找到安装包", Toast.LENGTH_SHORT).show(); 484 | return; 485 | } 486 | 487 | File apkFile = new File(apkPath 488 | + apkCacheName); 489 | 490 | Intent intent = new Intent(Intent.ACTION_VIEW); 491 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 492 | intent.setDataAndType( 493 | Uri.fromFile(apkFile), 494 | "application/vnd.android.package-archive"); 495 | context.startActivity(intent); 496 | } 497 | ``` 498 | 问题出在启动安装程序阶段 499 | 由于没升级 7.0 系统之前都没有问题,于是就在 Android 官网查看了一下 [Android 7.0 新特性](https://link.jianshu.com/?t=https%3A%2F%2Fdeveloper.android.google.cn%2Fabout%2Fversions%2Fnougat%2Fandroid-7.0-changes.html),终于发现其中 “在应用间共享文件” 一栏明确指出了这个问题 500 | ![](http://upload-images.jianshu.io/upload_images/3805053-236823b510646117.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 501 | 502 | 503 | 504 | ## 解决问题 505 | 506 | 官方给出的解决方式是通过 FileProvider 来为所共享的文件 Uri 添加临时权限,详细[请看这里](https://link.jianshu.com?t=https%3A%2F%2Fdeveloper.android.google.cn%2Ftraining%2Fsecure-file-sharing%2Fsetup-sharing.html%23DefineMetaData) 507 | 508 | * 在 标签下添加 FileProvider 节点 509 | 510 | ``` 511 | 513 | ... 514 | 519 | 522 | 523 | ... 524 | 525 | 526 | ``` 527 | 528 | `android:authority` 属性指定要用于 FileProvider 生成的 content URI 的 URI 权限,这里推荐使用 `包名.fileprovider` 以确保其唯一性。 529 | 530 | `` 的 `` 子元素指向一个 XML 文件,用于指定要共享的目录。 531 | 532 | * 在 `res/xml` 目录下创建文件 file_paths.xml 内容如下: 533 | 534 | ``` 535 | 536 | 537 | 538 | 539 | 540 | ``` 541 | 542 | `` 表示应用程序内部存储目录下的 `cache/` 目录,完整路径为 `Android/data/com.xxx.xxx/cache/`。 543 | 544 | `path` 属性用于指定子目录。 545 | 546 | `name` 属性告诉 FileProvider 为 `Android/data/com.xxx.xxx/cache/app/` 创建一个名为 `apk` 的路径字段。 547 | 548 | 想要通过 FileProvider 为文件生成 content URI 只能在此处指定目录,以上示例就表示我将要共享 `Android/data/com.xxx.xxx/cache/app/` 这个目录,除此之外还可以共享其它目录,对应的路径如下: 549 | 550 | 551 | |标签|路径| 552 | |---|---| 553 | ||[Context.getFilesDir()](https://link.jianshu.com?t=https%3A%2F%2Fdeveloper.android.google.cn%2Freference%2Fandroid%2Fcontent%2FContext.html%23getFilesDir%28%29)| 554 | |[getCacheDir()](https://link.jianshu.com?t=https%3A%2F%2Fdeveloper.android.google.cn%2Freference%2Fandroid%2Fcontent%2FContext.html%23getCacheDir%28%29) 555 | |[Environment.getExternalStorageDirectory()](https://link.jianshu.com?t=https%3A%2F%2Fdeveloper.android.google.cn%2Freference%2Fandroid%2Fos%2FEnvironment.html%23getExternalStorageDirectory%28%29) 556 | |[Context.getExternalFilesDir()](https://link.jianshu.com?t=https%3A%2F%2Fdeveloper.android.google.cn%2Freference%2Fandroid%2Fcontent%2FContext.html%23getExternalFilesDir%28java.lang.String%29) 557 | ||[Context.getExternalCacheDir()](https://link.jianshu.comt=https%3A%2F%2Fdeveloper.android.google.cn%2Freference%2Fandroid%2Fcontent%2FContext.html%23getExternalCacheDir%28%29) 558 | 559 | 具体详情参看简书:https://www.jianshu.com/p/2ab0459a9c3c 560 | 561 | 562 | 563 | * 完成以步骤后,我们修改出问题的代码如下: 564 | 565 | 566 | ``` 567 | /** 568 | * 安装apk 569 | */ 570 | public static void installApk(Context context,String apkPath) { 571 | if (TextUtils.isEmpty(apkPath)){ 572 | Toast.makeText(context,"更新失败!未找到安装包", Toast.LENGTH_SHORT).show(); 573 | return; 574 | } 575 | 576 | File apkFile = new File(apkPath 577 | + apkCacheName); 578 | 579 | Intent intent = new Intent(Intent.ACTION_VIEW); 580 | //Android 7.0 系统共享文件需要通过 FileProvider 添加临时权限,否则系统会抛出 FileUriExposedException . 581 | if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){ 582 | intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 583 | Uri contentUri = FileProvider.getUriForFile(context,"com.skyrin.bingo.fileprovider",apkFile); 584 | intent.setDataAndType(contentUri,"application/vnd.android.package-archive"); 585 | }else { 586 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 587 | intent.setDataAndType( 588 | Uri.fromFile(apkFile), 589 | "application/vnd.android.package-archive"); 590 | } 591 | context.startActivity(intent); 592 | } 593 | ... 594 | //调用,apkPath 入参就是 xml 中共享的路径 595 | String apkPath = context.getExternalCacheDir().getPath()+ File.separator+"app"+File.separator; 596 | AppUpdate.installApk(context,apkPath ); 597 | 598 | ``` 599 | 600 | ## 结语 601 | 602 | 除了上面这个问题,在 Android 7.0 之前开发的分享图文、浏览编辑本地图片、共享互传文件等功能如果没有使用 FileProvider 来生成 URI 的话,在 Android 7.0 上就必须做这种适配了,所以平时建议大家多关注 Android 新的 API ,尽早替换已被官方废弃的 API ,实际上 [FileProvider](https://link.jianshu.com?t=https%3A%2F%2Fdeveloper.android.google.cn%2Freference%2Fandroid%2Fsupport%2Fv4%2Fcontent%2FFileProvider.html) 在 API Level 22 已经添加了。 603 | 604 | 605 | 606 | 607 | -------------------------------------------------------------------------------- /UpdateDemo/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /UpdateDemo/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /UpdateDemo/.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /UpdateDemo/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /UpdateDemo/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /UpdateDemo/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /UpdateDemo/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /UpdateDemo/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /UpdateDemo/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "com.hsy.update" 8 | minSdkVersion 15 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | aaptOptions.cruncherEnabled = false 21 | aaptOptions.useNewCruncher = false 22 | } 23 | 24 | dependencies { 25 | compile fileTree(include: ['*.jar'], dir: 'libs') 26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 27 | exclude group: 'com.android.support', module: 'support-annotations' 28 | }) 29 | compile 'com.android.support:appcompat-v7:25.3.0' 30 | testCompile 'junit:junit:4.12' 31 | compile files('libs/nineoldandroids-2.4.0.jar') 32 | compile 'com.google.android.gms:play-services-appindexing:8.4.0' 33 | /*权限获取*/ 34 | compile 'com.yanzhenjie:permission:1.0.5' 35 | } 36 | -------------------------------------------------------------------------------- /UpdateDemo/app/libs/nineoldandroids-2.4.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangshuyuan/UpdateDemo/e3ef67c550aa571bbe072d54f7044f430c905042/UpdateDemo/app/libs/nineoldandroids-2.4.0.jar -------------------------------------------------------------------------------- /UpdateDemo/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Users\Administrator\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/androidTest/java/com/hsy/update/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.hsy.update; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.hsy.update", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/java/com/hsy/update/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.hsy.update; 2 | 3 | import android.Manifest; 4 | //import android.app.AlertDialog; 5 | import android.app.ProgressDialog; 6 | import android.content.Context; 7 | import android.content.DialogInterface; 8 | import android.content.Intent; 9 | import android.net.Uri; 10 | import android.os.AsyncTask; 11 | import android.os.Environment; 12 | import android.os.PowerManager; 13 | import android.support.annotation.NonNull; 14 | import android.support.v7.app.AppCompatActivity; 15 | import android.os.Bundle; 16 | import android.view.LayoutInflater; 17 | import android.view.MenuItem; 18 | import android.widget.Toast; 19 | 20 | import com.hsy.update.utils.Tools; 21 | import com.hsy.update.view.CommonProgressDialog; 22 | import com.hsy.update.view.MaterialDialog; 23 | import com.hsy.update.view.OnBtnClickL; 24 | import com.yanzhenjie.alertdialog.AlertDialog; 25 | import com.yanzhenjie.permission.AndPermission; 26 | import com.yanzhenjie.permission.PermissionListener; 27 | import com.yanzhenjie.permission.PermissionNo; 28 | import com.yanzhenjie.permission.PermissionYes; 29 | import com.yanzhenjie.permission.Rationale; 30 | import com.yanzhenjie.permission.RationaleListener; 31 | 32 | import java.io.File; 33 | import java.io.FileOutputStream; 34 | import java.io.IOException; 35 | import java.io.InputStream; 36 | import java.io.OutputStream; 37 | import java.net.HttpURLConnection; 38 | import java.net.URL; 39 | import java.util.List; 40 | /** 41 | * Created by huagnshuyuan on 2017/3/16. 42 | */ 43 | public class MainActivity extends AppCompatActivity { 44 | private CommonProgressDialog pBar; 45 | 46 | 47 | @Override 48 | protected void onCreate(Bundle savedInstanceState) { 49 | super.onCreate(savedInstanceState); 50 | setContentView(R.layout.activity_main); 51 | // 获取本版本号,是否更新 52 | int vision = Tools.getVersion(this); 53 | 54 | getVersion(vision); 55 | 56 | } 57 | 58 | 59 | // 下载存储的文件名 60 | private static final String DOWNLOAD_NAME = "channelWe"; 61 | 62 | // 获取更新版本号 63 | private void getVersion(final int vision) { 64 | // {"data":{"content":"其他bug修复。","id":"2","api_key":"android", 65 | // // "version":"2.1"},"msg":"获取成功","status":1} 66 | String data = ""; 67 | //网络请求获取当前版本号和下载链接 68 | //实际操作是从服务器获取 69 | //demo写死了 70 | 71 | String newversion = "2.1";//更新新的版本号 72 | String content = "\n" + 73 | "就不告诉你我们更新了什么-。-\n" + 74 | "\n" + 75 | "----------万能的分割线-----------\n" + 76 | "\n" + 77 | "(ㄒoㄒ) 被老板打了一顿,还是来告诉你吧:\n" + 78 | 79 | "1.下架商品误买了?恩。。。我搞了点小动作就不会出现了\n" + 80 | "2.侧边栏、弹框优化 —— 这个你自己去探索吧,总得留点悬念嘛-。-\n";//更新内容 81 | String url = "http://openbox.mobilem.360.cn/index/d/sid/3429345";//安装包下载地址 82 | 83 | double newversioncode = Double 84 | .parseDouble(newversion); 85 | int cc = (int) (newversioncode); 86 | 87 | System.out.println(newversion + "v" + vision + ",," 88 | + cc); 89 | if (cc != vision) { 90 | if (vision < cc) { 91 | System.out.println(newversion + "v" 92 | + vision); 93 | // 版本号不同 94 | ShowDialog(vision, newversion, content, url); 95 | } 96 | } 97 | } 98 | 99 | // int progressValue = 0; 100 | 101 | /** 102 | * 升级系统 103 | * 104 | * @param content 105 | * @param url 106 | */ 107 | private void ShowDialog(int vision, String newversion, String content, 108 | final String url) { 109 | // final MaterialDialog dialog = new MaterialDialog(this);//自定义的对话框,可以呀alertdialog 110 | // dialog.content(content).btnText("取消", "更新").title("版本更新 ") 111 | // .titleTextSize(15f).show(); 112 | // dialog.setCanceledOnTouchOutside(false); 113 | // dialog.setOnBtnClickL(new OnBtnClickL() {// left btn click listener 114 | // @Override 115 | // public void onBtnClick() { 116 | // dialog.dismiss(); 117 | // } 118 | // }, new OnBtnClickL() {// right btn click listener 119 | // 120 | // @Override 121 | // public void onBtnClick() { 122 | // dialog.dismiss(); 123 | // // pBar = new ProgressDialog(MainActivity.this, 124 | // // R.style.dialog); 125 | // pBar = new CommonProgressDialog(MainActivity.this); 126 | // pBar.setCanceledOnTouchOutside(false); 127 | // pBar.setTitle("正在下载"); 128 | // pBar.setCustomTitle(LayoutInflater.from( 129 | // MainActivity.this).inflate( 130 | // R.layout.title_dialog, null)); 131 | // pBar.setMessage("正在下载"); 132 | // pBar.setIndeterminate(true); 133 | // pBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); 134 | // pBar.setCancelable(true); 135 | // // downFile(URLData.DOWNLOAD_URL); 136 | // final DownloadTask downloadTask = new DownloadTask( 137 | // MainActivity.this); 138 | // downloadTask.execute(url); 139 | // pBar.setOnCancelListener(new DialogInterface.OnCancelListener() { 140 | // @Override 141 | // public void onCancel(DialogInterface dialog) { 142 | // downloadTask.cancel(true); 143 | // } 144 | // }); 145 | // } 146 | // }); 147 | 148 | new android.app.AlertDialog.Builder(this) 149 | .setTitle("版本更新") 150 | .setMessage(content) 151 | .setPositiveButton("更新", new DialogInterface.OnClickListener() { 152 | @Override 153 | public void onClick(DialogInterface dialog, int which) { 154 | dialog.dismiss(); 155 | pBar = new CommonProgressDialog(MainActivity.this); 156 | pBar.setCanceledOnTouchOutside(false); 157 | pBar.setTitle("正在下载"); 158 | pBar.setCustomTitle(LayoutInflater.from( 159 | MainActivity.this).inflate( 160 | R.layout.title_dialog, null)); 161 | pBar.setMessage("正在下载"); 162 | pBar.setIndeterminate(true); 163 | pBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); 164 | pBar.setCancelable(true); 165 | // downFile(URLData.DOWNLOAD_URL); 166 | final DownloadTask downloadTask = new DownloadTask( 167 | MainActivity.this); 168 | downloadTask.execute(url); 169 | pBar.setOnCancelListener(new DialogInterface.OnCancelListener() { 170 | @Override 171 | public void onCancel(DialogInterface dialog) { 172 | downloadTask.cancel(true); 173 | } 174 | }); 175 | } 176 | }) 177 | .setNegativeButton("取消", new DialogInterface.OnClickListener() { 178 | @Override 179 | public void onClick(DialogInterface dialog, int which) { 180 | dialog.dismiss(); 181 | } 182 | }) 183 | .show(); 184 | } 185 | 186 | 187 | /** 188 | * 下载应用 189 | * 190 | * @author Administrator 191 | */ 192 | class DownloadTask extends AsyncTask { 193 | 194 | private Context context; 195 | private PowerManager.WakeLock mWakeLock; 196 | 197 | public DownloadTask(Context context) { 198 | this.context = context; 199 | } 200 | 201 | @Override 202 | protected String doInBackground(String... sUrl) { 203 | InputStream input = null; 204 | OutputStream output = null; 205 | HttpURLConnection connection = null; 206 | File file = null; 207 | try { 208 | URL url = new URL(sUrl[0]); 209 | connection = (HttpURLConnection) url.openConnection(); 210 | connection.connect(); 211 | // expect HTTP 200 OK, so we don't mistakenly save error 212 | // report 213 | // instead of the file 214 | if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { 215 | return "Server returned HTTP " 216 | + connection.getResponseCode() + " " 217 | + connection.getResponseMessage(); 218 | } 219 | // this will be useful to display download percentage 220 | // might be -1: server did not report the length 221 | int fileLength = connection.getContentLength(); 222 | if (Environment.getExternalStorageState().equals( 223 | Environment.MEDIA_MOUNTED)) { 224 | file = new File(Environment.getExternalStorageDirectory(), 225 | DOWNLOAD_NAME); 226 | 227 | if (!file.exists()) { 228 | // 判断父文件夹是否存在 229 | if (!file.getParentFile().exists()) { 230 | file.getParentFile().mkdirs(); 231 | } 232 | } 233 | 234 | } else { 235 | Toast.makeText(MainActivity.this, "sd卡未挂载", 236 | Toast.LENGTH_LONG).show(); 237 | } 238 | input = connection.getInputStream(); 239 | output = new FileOutputStream(file); 240 | byte data[] = new byte[4096]; 241 | long total = 0; 242 | int count; 243 | while ((count = input.read(data)) != -1) { 244 | // allow canceling with back button 245 | if (isCancelled()) { 246 | input.close(); 247 | return null; 248 | } 249 | total += count; 250 | // publishing the progress.... 251 | if (fileLength > 0) // only if total length is known 252 | publishProgress((int) (total * 100 / fileLength)); 253 | output.write(data, 0, count); 254 | 255 | } 256 | } catch (Exception e) { 257 | System.out.println(e.toString()); 258 | return e.toString(); 259 | 260 | } finally { 261 | try { 262 | if (output != null) 263 | output.close(); 264 | if (input != null) 265 | input.close(); 266 | } catch (IOException ignored) { 267 | } 268 | if (connection != null) 269 | connection.disconnect(); 270 | } 271 | return null; 272 | } 273 | 274 | @Override 275 | protected void onPreExecute() { 276 | super.onPreExecute(); 277 | // take CPU lock to prevent CPU from going off if the user 278 | // presses the power button during download 279 | PowerManager pm = (PowerManager) context 280 | .getSystemService(Context.POWER_SERVICE); 281 | mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 282 | getClass().getName()); 283 | mWakeLock.acquire(); 284 | pBar.show(); 285 | } 286 | 287 | @Override 288 | protected void onProgressUpdate(Integer... progress) { 289 | super.onProgressUpdate(progress); 290 | // if we get here, length is known, now set indeterminate to false 291 | pBar.setIndeterminate(false); 292 | pBar.setMax(100); 293 | pBar.setProgress(progress[0]); 294 | } 295 | 296 | @Override 297 | protected void onPostExecute(String result) { 298 | mWakeLock.release(); 299 | pBar.dismiss(); 300 | if (result != null) { 301 | 302 | // // 申请多个权限。大神的界面 303 | // AndPermission.with(MainActivity.this) 304 | // .requestCode(REQUEST_CODE_PERMISSION_OTHER) 305 | // .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE) 306 | // // rationale作用是:用户拒绝一次权限,再次申请时先征求用户同意,再打开授权对话框,避免用户勾选不再提示。 307 | // .rationale(new RationaleListener() { 308 | // @Override 309 | // public void showRequestPermissionRationale(int requestCode, Rationale rationale) { 310 | // // 这里的对话框可以自定义,只要调用rationale.resume()就可以继续申请。 311 | // AndPermission.rationaleDialog(MainActivity.this, rationale).show(); 312 | // } 313 | // } 314 | // ) 315 | // .send(); 316 | // 申请多个权限。 317 | AndPermission.with(MainActivity.this) 318 | .requestCode(REQUEST_CODE_PERMISSION_SD) 319 | .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE) 320 | // rationale作用是:用户拒绝一次权限,再次申请时先征求用户同意,再打开授权对话框,避免用户勾选不再提示。 321 | .rationale(rationaleListener 322 | ) 323 | .send(); 324 | 325 | 326 | Toast.makeText(context, "您未打开SD卡权限" + result, Toast.LENGTH_LONG).show(); 327 | } else { 328 | // Toast.makeText(context, "File downloaded", 329 | // Toast.LENGTH_SHORT) 330 | // .show(); 331 | update(); 332 | } 333 | 334 | } 335 | } 336 | 337 | private static final int REQUEST_CODE_PERMISSION_SD = 101; 338 | 339 | private static final int REQUEST_CODE_SETTING = 300; 340 | private RationaleListener rationaleListener = new RationaleListener() { 341 | @Override 342 | public void showRequestPermissionRationale(int requestCode, final Rationale rationale) { 343 | // 这里使用自定义对话框,如果不想自定义,用AndPermission默认对话框: 344 | // AndPermission.rationaleDialog(Context, Rationale).show(); 345 | 346 | // 自定义对话框。 347 | AlertDialog.build(MainActivity.this) 348 | .setTitle(R.string.title_dialog) 349 | .setMessage(R.string.message_permission_rationale) 350 | .setPositiveButton(R.string.btn_dialog_yes_permission, new DialogInterface.OnClickListener() { 351 | @Override 352 | public void onClick(DialogInterface dialog, int which) { 353 | dialog.cancel(); 354 | rationale.resume(); 355 | } 356 | }) 357 | 358 | .setNegativeButton(R.string.btn_dialog_no_permission, new DialogInterface.OnClickListener() { 359 | @Override 360 | public void onClick(DialogInterface dialog, int which) { 361 | dialog.cancel(); 362 | rationale.cancel(); 363 | } 364 | }) 365 | .show(); 366 | } 367 | }; 368 | //----------------------------------SD权限----------------------------------// 369 | 370 | 371 | @PermissionYes(REQUEST_CODE_PERMISSION_SD) 372 | private void getMultiYes(List grantedPermissions) { 373 | Toast.makeText(this, R.string.message_post_succeed, Toast.LENGTH_SHORT).show(); 374 | } 375 | 376 | @PermissionNo(REQUEST_CODE_PERMISSION_SD) 377 | private void getMultiNo(List deniedPermissions) { 378 | Toast.makeText(this, R.string.message_post_failed, Toast.LENGTH_SHORT).show(); 379 | 380 | // 用户否勾选了不再提示并且拒绝了权限,那么提示用户到设置中授权。 381 | if (AndPermission.hasAlwaysDeniedPermission(this, deniedPermissions)) { 382 | AndPermission.defaultSettingDialog(this, REQUEST_CODE_SETTING) 383 | .setTitle(R.string.title_dialog) 384 | .setMessage(R.string.message_permission_failed) 385 | .setPositiveButton(R.string.btn_dialog_yes_permission) 386 | .setNegativeButton(R.string.btn_dialog_no_permission, null) 387 | .show(); 388 | 389 | // 更多自定dialog,请看上面。 390 | } 391 | } 392 | 393 | //----------------------------------权限回调处理----------------------------------// 394 | 395 | @Override 396 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] 397 | grantResults) { 398 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 399 | /** 400 | * 转给AndPermission分析结果。 401 | * 402 | * @param object 要接受结果的Activity、Fragment。 403 | * @param requestCode 请求码。 404 | * @param permissions 权限数组,一个或者多个。 405 | * @param grantResults 请求结果。 406 | */ 407 | AndPermission.onRequestPermissionsResult(this, requestCode, permissions, grantResults); 408 | } 409 | 410 | @Override 411 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 412 | switch (requestCode) { 413 | case REQUEST_CODE_SETTING: { 414 | Toast.makeText(this, R.string.message_setting_back, Toast.LENGTH_LONG).show(); 415 | //设置成功,再次请求更新 416 | getVersion(Tools.getVersion(MainActivity.this)); 417 | break; 418 | } 419 | } 420 | } 421 | 422 | 423 | private void update() { 424 | //安装应用 425 | Intent intent = new Intent(Intent.ACTION_VIEW); 426 | intent.setDataAndType(Uri.fromFile(new File(Environment 427 | .getExternalStorageDirectory(), DOWNLOAD_NAME)), 428 | "application/vnd.android.package-archive"); 429 | startActivity(intent); 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/java/com/hsy/update/utils/ColorData.java: -------------------------------------------------------------------------------- 1 | package com.hsy.update.utils; 2 | 3 | import android.graphics.Color; 4 | 5 | /** 6 | * Created by huagnshuyuan on 2017/3/16. 7 | */ 8 | 9 | public class ColorData { 10 | /** 11 | * 对话框字体主题色 12 | */ 13 | 14 | public static int RED_THEME = Color.parseColor("#eb2127"); 15 | } 16 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/java/com/hsy/update/utils/Tools.java: -------------------------------------------------------------------------------- 1 | package com.hsy.update.utils; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageInfo; 5 | import android.content.pm.PackageManager; 6 | import android.os.Environment; 7 | /** 8 | * Created by huagnshuyuan on 2017/3/16. 9 | */ 10 | public class Tools { 11 | /** 12 | * 检查是否存在SDCard 13 | * 14 | * @return 15 | */ 16 | public static boolean hasSdcard() { 17 | String state = Environment.getExternalStorageState(); 18 | if (state.equals(Environment.MEDIA_MOUNTED)) { 19 | return true; 20 | } else { 21 | return false; 22 | } 23 | } 24 | 25 | /** 26 | * 2 * 获取版本号 3 * @return 当前应用的版本号 4 27 | */ 28 | public static int getVersion(Context context) { 29 | try { 30 | PackageManager manager = context.getPackageManager(); 31 | PackageInfo info = manager.getPackageInfo(context.getPackageName(), 32 | 0); 33 | String version = info.versionName; 34 | int versioncode = info.versionCode; 35 | return versioncode; 36 | } catch (Exception e) { 37 | e.printStackTrace(); 38 | } 39 | return 0; 40 | } 41 | // if (VERSION.SDK_INT > 16) { 42 | // Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); 43 | // final RenderScript rs = RenderScript.create(context); 44 | // final Allocation input = Allocation.createFromBitmap(rs, sentBitmap, 45 | // Allocation.MipmapControl.MIPMAP_NONE, 46 | // Allocation.USAGE_SCRIPT); 47 | // final Allocation output = Allocation.createTyped(rs, input.getType()); 48 | // final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, 49 | // Element.U8_4(rs)); 50 | // script.setRadius(radius /* e.g. 3.f */); 51 | // script.setInput(input); 52 | // script.forEach(output); 53 | // output.copyTo(bitmap); 54 | // return bitmap; 55 | // } 56 | } 57 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/java/com/hsy/update/view/BaseAlertDialog.java: -------------------------------------------------------------------------------- 1 | package com.hsy.update.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.text.TextUtils; 6 | import android.util.TypedValue; 7 | import android.view.Gravity; 8 | import android.view.View; 9 | import android.widget.LinearLayout; 10 | import android.widget.TextView; 11 | 12 | 13 | 14 | public abstract class BaseAlertDialog> extends BaseDialog { 15 | /** container */ 16 | protected LinearLayout ll_container; 17 | //title 18 | /** title */ 19 | protected TextView tv_title; 20 | /** title content(标题) */ 21 | protected String title; 22 | /** title textcolor(标题颜色) */ 23 | protected int titleTextColor; 24 | /** title textsize(标题字体大小,单位sp) */ 25 | protected float titleTextSize_SP; 26 | /** enable title show(是否显示标题) */ 27 | protected boolean isTitleShow = true; 28 | 29 | //content 30 | /** content */ 31 | protected TextView tv_content; 32 | /** content text */ 33 | protected String content; 34 | /** show gravity of content(正文内容显示位置) */ 35 | protected int contentGravity = Gravity.CENTER_VERTICAL; 36 | /** content textcolor(正文字体颜色) */ 37 | protected int contentTextColor; 38 | /** content textsize(正文字体大小) */ 39 | protected float contentTextSize_SP; 40 | 41 | //btns 42 | /** num of btns, [1,3] */ 43 | protected int btnNum = 2; 44 | /** btn container */ 45 | protected LinearLayout ll_btns; 46 | /** btns */ 47 | protected TextView tv_btn_left; 48 | protected TextView tv_btn_right; 49 | protected TextView tv_btn_middle; 50 | /** btn text(按钮内容) */ 51 | protected String btnLeftText = "取消"; 52 | protected String btnRightText = "确定"; 53 | protected String btnMiddleText = "继续"; 54 | /** btn textcolor(按钮字体颜色) */ 55 | protected int leftBtnTextColor; 56 | protected int rightBtnTextColor; 57 | protected int middleBtnTextColor; 58 | /** btn textsize(按钮字体大小) */ 59 | protected float leftBtnTextSize_SP = 15f; 60 | protected float rightBtnTextSize_SP = 15f; 61 | protected float middleBtnTextSize_SP = 15f; 62 | /** btn press color(按钮点击颜色) */ 63 | protected int btnPressColor = Color.parseColor("#E3E3E3");// #85D3EF,#ffcccccc,#E3E3E3 64 | /** left btn click listener(左按钮接口) */ 65 | protected OnBtnClickL onBtnLeftClickL; 66 | /** right btn click listener(右按钮接口) */ 67 | protected OnBtnClickL onBtnRightClickL; 68 | /** middle btn click listener(右按钮接口) */ 69 | protected OnBtnClickL onBtnMiddleClickL; 70 | 71 | /** corner radius,dp(圆角程度,单位dp) */ 72 | protected float cornerRadius_DP = 3; 73 | /** background color(背景颜色) */ 74 | protected int bgColor = Color.parseColor("#ffffff"); 75 | 76 | /** 77 | * method execute order: 78 | * show:constrouctor---show---oncreate---onStart---onAttachToWindow 79 | * dismiss:dismiss---onDetachedFromWindow---onStop 80 | * 81 | * @param context 82 | */ 83 | public BaseAlertDialog(Context context) { 84 | super(context); 85 | widthScale(0.88f); 86 | 87 | ll_container = new LinearLayout(context); 88 | ll_container.setOrientation(LinearLayout.VERTICAL); 89 | 90 | /** title */ 91 | tv_title = new TextView(context); 92 | 93 | /** content */ 94 | tv_content = new TextView(context); 95 | 96 | /**btns*/ 97 | ll_btns = new LinearLayout(context); 98 | ll_btns.setOrientation(LinearLayout.HORIZONTAL); 99 | 100 | tv_btn_left = new TextView(context); 101 | tv_btn_left.setGravity(Gravity.CENTER); 102 | 103 | tv_btn_middle = new TextView(context); 104 | tv_btn_middle.setGravity(Gravity.CENTER); 105 | 106 | tv_btn_right = new TextView(context); 107 | tv_btn_right.setGravity(Gravity.CENTER); 108 | } 109 | 110 | @Override 111 | public void setUiBeforShow() { 112 | /** title */ 113 | tv_title.setVisibility(isTitleShow ? View.VISIBLE : View.GONE); 114 | 115 | tv_title.setText(TextUtils.isEmpty(title) ? "温馨提示" : title); 116 | tv_title.setTextColor(titleTextColor); 117 | tv_title.setTextSize(TypedValue.COMPLEX_UNIT_SP, titleTextSize_SP); 118 | 119 | /** content */ 120 | tv_content.setGravity(contentGravity); 121 | tv_content.setText(content); 122 | tv_content.setTextColor(contentTextColor); 123 | tv_content.setTextSize(TypedValue.COMPLEX_UNIT_SP, contentTextSize_SP); 124 | tv_content.setLineSpacing(0, 1.3f); 125 | 126 | /**btns*/ 127 | tv_btn_left.setText(btnLeftText); 128 | tv_btn_right.setText(btnRightText); 129 | tv_btn_middle.setText(btnMiddleText); 130 | 131 | tv_btn_left.setTextColor(leftBtnTextColor); 132 | tv_btn_right.setTextColor(rightBtnTextColor); 133 | tv_btn_middle.setTextColor(middleBtnTextColor); 134 | 135 | tv_btn_left.setTextSize(TypedValue.COMPLEX_UNIT_SP, leftBtnTextSize_SP); 136 | tv_btn_right.setTextSize(TypedValue.COMPLEX_UNIT_SP, rightBtnTextSize_SP); 137 | tv_btn_middle.setTextSize(TypedValue.COMPLEX_UNIT_SP, middleBtnTextSize_SP); 138 | 139 | if (btnNum == 1) { 140 | tv_btn_left.setVisibility(View.GONE); 141 | tv_btn_right.setVisibility(View.GONE); 142 | } else if (btnNum == 2) { 143 | tv_btn_middle.setVisibility(View.GONE); 144 | } 145 | 146 | tv_btn_left.setOnClickListener(new View.OnClickListener() { 147 | @Override 148 | public void onClick(View v) { 149 | if (onBtnLeftClickL != null) { 150 | onBtnLeftClickL.onBtnClick(); 151 | } else { 152 | dismiss(); 153 | } 154 | } 155 | }); 156 | 157 | tv_btn_right.setOnClickListener(new View.OnClickListener() { 158 | @Override 159 | public void onClick(View v) { 160 | if (onBtnRightClickL != null) { 161 | onBtnRightClickL.onBtnClick(); 162 | } else { 163 | dismiss(); 164 | } 165 | } 166 | }); 167 | 168 | tv_btn_middle.setOnClickListener(new View.OnClickListener() { 169 | @Override 170 | public void onClick(View v) { 171 | if (onBtnMiddleClickL != null) { 172 | onBtnMiddleClickL.onBtnClick(); 173 | } else { 174 | dismiss(); 175 | } 176 | } 177 | }); 178 | } 179 | 180 | /** set title text(设置标题内容) @return MaterialDialog */ 181 | public T title(String title) { 182 | this.title = title; 183 | return (T) this; 184 | } 185 | 186 | /** set title textcolor(设置标题字体颜色) */ 187 | public T titleTextColor(int titleTextColor) { 188 | this.titleTextColor = titleTextColor; 189 | return (T) this; 190 | } 191 | 192 | /** set title textsize(设置标题字体大小) */ 193 | public T titleTextSize(float titleTextSize_SP) { 194 | this.titleTextSize_SP = titleTextSize_SP; 195 | return (T) this; 196 | } 197 | 198 | /** enable title show(设置标题是否显示) */ 199 | public T isTitleShow(boolean isTitleShow) { 200 | this.isTitleShow = isTitleShow; 201 | return (T) this; 202 | } 203 | 204 | /** set content text(设置正文内容) */ 205 | public T content(String content) { 206 | this.content = content; 207 | return (T) this; 208 | } 209 | 210 | /** set content gravity(设置正文内容,显示位置) */ 211 | public T contentGravity(int contentGravity) { 212 | this.contentGravity = contentGravity; 213 | return (T) this; 214 | } 215 | 216 | /** set content textcolor(设置正文字体颜色) */ 217 | public T contentTextColor(int contentTextColor) { 218 | this.contentTextColor = contentTextColor; 219 | return (T) this; 220 | } 221 | 222 | /** set content textsize(设置正文字体大小,单位sp) */ 223 | public T contentTextSize(float contentTextSize_SP) { 224 | this.contentTextSize_SP = contentTextSize_SP; 225 | return (T) this; 226 | } 227 | 228 | /** 229 | * set btn text(设置按钮文字内容) 230 | * btnTexts size 1, middle 231 | * btnTexts size 2, left right 232 | * btnTexts size 3, left right middle 233 | */ 234 | public T btnNum(int btnNum) { 235 | if (btnNum < 1 || btnNum > 3) { 236 | throw new IllegalStateException("btnNum is [1,3]!"); 237 | } 238 | this.btnNum = btnNum; 239 | 240 | return (T) this; 241 | } 242 | 243 | /** 244 | * set btn text(设置按钮文字内容) 245 | * btnTexts size 1, middle 246 | * btnTexts size 2, left right 247 | * btnTexts size 3, left right middle 248 | */ 249 | public T btnText(String... btnTexts) { 250 | if (btnTexts.length < 1 || btnTexts.length > 3) { 251 | throw new IllegalStateException(" range of param btnTexts length is [1,3]!"); 252 | } 253 | 254 | if (btnTexts.length == 1) { 255 | this.btnMiddleText = btnTexts[0]; 256 | } else if (btnTexts.length == 2) { 257 | this.btnLeftText = btnTexts[0]; 258 | this.btnRightText = btnTexts[1]; 259 | } else if (btnTexts.length == 3) { 260 | this.btnLeftText = btnTexts[0]; 261 | this.btnRightText = btnTexts[1]; 262 | this.btnMiddleText = btnTexts[2]; 263 | } 264 | 265 | return (T) this; 266 | } 267 | 268 | /** 269 | * set btn textcolor(设置按钮字体颜色) 270 | * btnTextColors size 1, middle 271 | * btnTextColors size 2, left right 272 | * btnTextColors size 3, left right middle 273 | */ 274 | public T btnTextColor(int... btnTextColors) { 275 | if (btnTextColors.length < 1 || btnTextColors.length > 3) { 276 | throw new IllegalStateException(" range of param textColors length is [1,3]!"); 277 | } 278 | 279 | if (btnTextColors.length == 1) { 280 | this.middleBtnTextColor = btnTextColors[0]; 281 | } else if (btnTextColors.length == 2) { 282 | this.leftBtnTextColor = btnTextColors[0]; 283 | this.rightBtnTextColor = btnTextColors[1]; 284 | } else if (btnTextColors.length == 3) { 285 | this.leftBtnTextColor = btnTextColors[0]; 286 | this.rightBtnTextColor = btnTextColors[1]; 287 | this.middleBtnTextColor = btnTextColors[2]; 288 | } 289 | 290 | return (T) this; 291 | } 292 | 293 | /** 294 | * set btn textsize(设置字体大小,单位sp) 295 | * btnTextSizes size 1, middle 296 | * btnTextSizes size 2, left right 297 | * btnTextSizes size 3, left right middle 298 | */ 299 | public T btnTextSize(float... btnTextSizes) { 300 | if (btnTextSizes.length < 1 || btnTextSizes.length > 3) { 301 | throw new IllegalStateException(" range of param btnTextSizes length is [1,3]!"); 302 | } 303 | 304 | if (btnTextSizes.length == 1) { 305 | this.middleBtnTextSize_SP = btnTextSizes[0]; 306 | } else if (btnTextSizes.length == 2) { 307 | this.leftBtnTextSize_SP = btnTextSizes[0]; 308 | this.rightBtnTextSize_SP = btnTextSizes[1]; 309 | } else if (btnTextSizes.length == 3) { 310 | this.leftBtnTextSize_SP = btnTextSizes[0]; 311 | this.rightBtnTextSize_SP = btnTextSizes[1]; 312 | this.middleBtnTextSize_SP = btnTextSizes[2]; 313 | } 314 | 315 | return (T) this; 316 | } 317 | 318 | /** set btn press color(设置按钮点击颜色) */ 319 | public T btnPressColor(int btnPressColor) { 320 | this.btnPressColor = btnPressColor; 321 | return (T) this; 322 | } 323 | 324 | /** set corner radius (设置圆角程度) */ 325 | public T cornerRadius(float cornerRadius_DP) { 326 | this.cornerRadius_DP = cornerRadius_DP; 327 | return (T) this; 328 | } 329 | 330 | /** set backgroud color(设置背景色) */ 331 | public T bgColor(int bgColor) { 332 | this.bgColor = bgColor; 333 | return (T) this; 334 | } 335 | 336 | /** 337 | * set btn click listener(设置按钮监听事件) 338 | * onBtnClickLs size 1, middle 339 | * onBtnClickLs size 2, left right 340 | * onBtnClickLs size 3, left right middle 341 | */ 342 | public void setOnBtnClickL(OnBtnClickL... onBtnClickLs) { 343 | if (onBtnClickLs.length < 1 || onBtnClickLs.length > 3) { 344 | throw new IllegalStateException(" range of param onBtnClickLs length is [1,3]!"); 345 | } 346 | 347 | if (onBtnClickLs.length == 1) { 348 | this.onBtnMiddleClickL = onBtnClickLs[0]; 349 | } else if (onBtnClickLs.length == 2) { 350 | this.onBtnLeftClickL = onBtnClickLs[0]; 351 | this.onBtnRightClickL = onBtnClickLs[1]; 352 | } else if (onBtnClickLs.length == 3) { 353 | this.onBtnLeftClickL = onBtnClickLs[0]; 354 | this.onBtnRightClickL = onBtnClickLs[1]; 355 | this.onBtnMiddleClickL = onBtnClickLs[2]; 356 | } 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/java/com/hsy/update/view/BaseAnimatorSet.java: -------------------------------------------------------------------------------- 1 | package com.hsy.update.view; 2 | 3 | import android.view.View; 4 | import android.view.animation.Interpolator; 5 | 6 | import com.nineoldandroids.animation.Animator; 7 | import com.nineoldandroids.animation.AnimatorSet; 8 | import com.nineoldandroids.view.ViewHelper; 9 | 10 | public abstract class BaseAnimatorSet { 11 | /** 12 | * 动画时长,系统默认250 13 | */ 14 | protected long duration = 500; 15 | protected AnimatorSet animatorSet = new AnimatorSet(); 16 | private Interpolator interpolator; 17 | private long delay; 18 | private AnimatorListener listener; 19 | 20 | public abstract void setAnimation(View view); 21 | 22 | protected void start(final View view) { 23 | /** 设置动画中心炿pivotX--->X轴方向动画中心点,pivotY--->Y轴方向动画中心点 */ 24 | // ViewHelper.setPivotX(view, view.getMeasuredWidth() / 2.0f); 25 | // ViewHelper.setPivotY(view, view.getMeasuredHeight() / 2.0f); 26 | reset(view); 27 | setAnimation(view); 28 | 29 | animatorSet.setDuration(duration); 30 | if (interpolator != null) { 31 | animatorSet.setInterpolator(interpolator); 32 | } 33 | 34 | if (delay > 0) { 35 | animatorSet.setStartDelay(delay); 36 | } 37 | 38 | if (listener != null) { 39 | animatorSet.addListener(new Animator.AnimatorListener() { 40 | @Override 41 | public void onAnimationStart(Animator animator) { 42 | listener.onAnimationStart(animator); 43 | } 44 | 45 | @Override 46 | public void onAnimationRepeat(Animator animator) { 47 | listener.onAnimationRepeat(animator); 48 | } 49 | 50 | @Override 51 | public void onAnimationEnd(Animator animator) { 52 | listener.onAnimationEnd(animator); 53 | } 54 | 55 | @Override 56 | public void onAnimationCancel(Animator animator) { 57 | listener.onAnimationCancel(animator); 58 | } 59 | }); 60 | } 61 | 62 | animatorSet.start(); 63 | } 64 | 65 | public static void reset(View view) { 66 | ViewHelper.setAlpha(view, 1); 67 | ViewHelper.setScaleX(view, 1); 68 | ViewHelper.setScaleY(view, 1); 69 | ViewHelper.setTranslationX(view, 0); 70 | ViewHelper.setTranslationY(view, 0); 71 | ViewHelper.setRotation(view, 0); 72 | ViewHelper.setRotationY(view, 0); 73 | ViewHelper.setRotationX(view, 0); 74 | } 75 | 76 | /** 77 | * 设置动画时长 78 | */ 79 | public BaseAnimatorSet duration(long duration) { 80 | this.duration = duration; 81 | return this; 82 | } 83 | 84 | /** 85 | * 设置动画时长 86 | */ 87 | public BaseAnimatorSet delay(long delay) { 88 | this.delay = delay; 89 | return this; 90 | } 91 | 92 | /** 93 | * 设置动画插补噿 94 | */ 95 | public BaseAnimatorSet interpolator(Interpolator interpolator) { 96 | this.interpolator = interpolator; 97 | return this; 98 | } 99 | 100 | /** 101 | * 动画监听 102 | */ 103 | public BaseAnimatorSet listener(AnimatorListener listener) { 104 | this.listener = listener; 105 | return this; 106 | } 107 | 108 | /** 109 | * 在View上执行动甿 110 | */ 111 | public void playOn(View view) { 112 | start(view); 113 | } 114 | 115 | public interface AnimatorListener { 116 | void onAnimationStart(Animator animator); 117 | 118 | void onAnimationRepeat(Animator animator); 119 | 120 | void onAnimationEnd(Animator animator); 121 | 122 | void onAnimationCancel(Animator animator); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/java/com/hsy/update/view/BaseDialog.java: -------------------------------------------------------------------------------- 1 | package com.hsy.update.view; 2 | import android.app.Dialog; 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.os.Bundle; 7 | import android.util.DisplayMetrics; 8 | import android.util.Log; 9 | import android.view.Gravity; 10 | import android.view.MotionEvent; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.view.Window; 14 | import android.view.WindowManager.LayoutParams; 15 | import android.widget.LinearLayout; 16 | 17 | import com.nineoldandroids.animation.Animator; 18 | 19 | public abstract class BaseDialog> extends Dialog { 20 | /** TAG(日志) */ 21 | protected String TAG; 22 | /** context(上下文) */ 23 | protected Context context; 24 | /** (DisplayMetrics)设备密度 */ 25 | protected DisplayMetrics dm; 26 | /** enable dismiss outside dialog(设置点击对话框以外区域,是否dismiss) */ 27 | protected boolean cancel; 28 | /** dialog width scale(宽度比例) */ 29 | protected float widthScale = 1; 30 | /** dialog height scale(高度比例) */ 31 | protected float heightScale; 32 | /** showAnim(对话框显示动画) */ 33 | private BaseAnimatorSet showAnim; 34 | /** dismissAnim(对话框消失动画) */ 35 | private BaseAnimatorSet dismissAnim; 36 | /** top container(最上层容器) */ 37 | protected LinearLayout ll_top; 38 | /** container to control dialog height(用于控制对话框高度) */ 39 | protected LinearLayout ll_control_height; 40 | /** is showAnim running(显示动画是否正在执行) */ 41 | private boolean isShowAnim; 42 | /** is DismissAnim running(消失动画是否正在执行) */ 43 | private boolean isDismissAnim; 44 | /** max height(最大高度) */ 45 | protected float maxHeight; 46 | 47 | /** 48 | * method execute order: 49 | * show:constrouctor---show---oncreate---onStart---onAttachToWindow 50 | * dismiss:dismiss---onDetachedFromWindow---onStop 51 | */ 52 | public BaseDialog(Context context) { 53 | super(context); 54 | setDialogTheme(); 55 | this.context = context; 56 | this.TAG = this.getClass().getSimpleName(); 57 | Log.d(TAG, "constructor"); 58 | } 59 | 60 | /** 61 | * set dialog theme(设置对话框主题) 62 | */ 63 | private void setDialogTheme() { 64 | requestWindowFeature(Window.FEATURE_NO_TITLE);// android:windowNoTitle 65 | getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));// android:windowBackground 66 | getWindow().addFlags(LayoutParams.FLAG_DIM_BEHIND);// android:backgroundDimEnabled默认是true的 67 | } 68 | 69 | /** 70 | * inflate layout for dialog ui and return (填充对话框所需要的布局并返回) 71 | *
 72 |      *
 73 |      * public View onCreateView() {
 74 |      * View inflate = View.inflate(context, R.layout.dialog_share, null);
 75 |      * return inflate;
 76 |      * }
 77 |      * 
78 | */ 79 | public abstract View onCreateView(); 80 | 81 | /** 82 | * set Ui data or logic opreation before attatched window(在对话框显示之前,设置界面数据或者逻辑) 83 | */ 84 | public abstract void setUiBeforShow(); 85 | 86 | @Override 87 | protected void onCreate(Bundle savedInstanceState) { 88 | Log.d(TAG, "onCreate"); 89 | dm = context.getResources().getDisplayMetrics(); 90 | ll_top = new LinearLayout(context); 91 | ll_top.setGravity(Gravity.CENTER); 92 | 93 | ll_control_height = new LinearLayout(context); 94 | ll_control_height.setOrientation(LinearLayout.VERTICAL); 95 | 96 | ll_control_height.addView(onCreateView()); 97 | ll_top.addView(ll_control_height); 98 | 99 | maxHeight = dm.heightPixels - StatusBarUtils.getHeight(context); 100 | // maxHeight = dm.heightPixels; 101 | setContentView(ll_top, new ViewGroup.LayoutParams(dm.widthPixels, (int) maxHeight)); 102 | setCanceledOnTouchOutside(true); 103 | 104 | ll_top.setOnClickListener(new View.OnClickListener() { 105 | @Override 106 | public void onClick(View v) { 107 | if (cancel) { 108 | dismiss(); 109 | } 110 | } 111 | }); 112 | } 113 | 114 | /** 115 | * when dailog attached to window,set dialog width and height and show anim 116 | * (当dailog依附在window上,设置对话框宽高以及显示动画) 117 | */ 118 | @Override 119 | public void onAttachedToWindow() { 120 | super.onAttachedToWindow(); 121 | Log.d(TAG, "onAttachedToWindow"); 122 | 123 | setUiBeforShow(); 124 | 125 | int width; 126 | if (widthScale == 0) { 127 | width = ViewGroup.LayoutParams.WRAP_CONTENT; 128 | } else { 129 | width = (int) (dm.widthPixels * widthScale); 130 | } 131 | 132 | int height; 133 | if (heightScale == 0) { 134 | height = ViewGroup.LayoutParams.WRAP_CONTENT; 135 | } else if (heightScale == 1) { 136 | height = ViewGroup.LayoutParams.MATCH_PARENT; 137 | } else { 138 | height = (int) (maxHeight * heightScale); 139 | } 140 | 141 | ll_control_height.setLayoutParams(new LinearLayout.LayoutParams(width, height)); 142 | 143 | if (showAnim != null) { 144 | showAnim.listener(new BaseAnimatorSet.AnimatorListener() { 145 | @Override 146 | public void onAnimationStart(Animator animator) { 147 | isShowAnim = true; 148 | } 149 | 150 | @Override 151 | public void onAnimationRepeat(Animator animator) { 152 | } 153 | 154 | @Override 155 | public void onAnimationEnd(Animator animator) { 156 | isShowAnim = false; 157 | } 158 | 159 | @Override 160 | public void onAnimationCancel(Animator animator) { 161 | isShowAnim = false; 162 | } 163 | }).playOn(ll_control_height); 164 | } else { 165 | BaseAnimatorSet.reset(ll_control_height); 166 | } 167 | } 168 | 169 | 170 | @Override 171 | public void setCanceledOnTouchOutside(boolean cancel) { 172 | this.cancel = cancel; 173 | super.setCanceledOnTouchOutside(cancel); 174 | } 175 | 176 | @Override 177 | public void show() { 178 | 179 | super.show(); 180 | Log.d(TAG, "show"); 181 | } 182 | 183 | 184 | @Override 185 | protected void onStart() { 186 | super.onStart(); 187 | Log.d(TAG, "onStart"); 188 | } 189 | 190 | @Override 191 | protected void onStop() { 192 | super.onStop(); 193 | Log.d(TAG, "onStop"); 194 | } 195 | 196 | @Override 197 | public void onDetachedFromWindow() { 198 | super.onDetachedFromWindow(); 199 | Log.d(TAG, "onDetachedFromWindow"); 200 | } 201 | 202 | @Override 203 | public void dismiss() { 204 | Log.d(TAG, "dismiss"); 205 | if (dismissAnim != null) { 206 | dismissAnim.listener(new BaseAnimatorSet.AnimatorListener() { 207 | @Override 208 | public void onAnimationStart(Animator animator) { 209 | isDismissAnim = true; 210 | } 211 | 212 | @Override 213 | public void onAnimationRepeat(Animator animator) { 214 | } 215 | 216 | @Override 217 | public void onAnimationEnd(Animator animator) { 218 | isDismissAnim = false; 219 | superDismiss(); 220 | } 221 | 222 | @Override 223 | public void onAnimationCancel(Animator animator) { 224 | isDismissAnim = false; 225 | superDismiss(); 226 | } 227 | }).playOn(ll_control_height); 228 | } else { 229 | superDismiss(); 230 | } 231 | } 232 | 233 | /** dismiss without anim(无动画dismiss) */ 234 | public void superDismiss() { 235 | super.dismiss(); 236 | } 237 | 238 | /** dialog anim by styles(动画弹出对话框,style动画资源) */ 239 | public void show(int animStyle) { 240 | Window window = getWindow(); 241 | window.setWindowAnimations(animStyle); 242 | show(); 243 | } 244 | 245 | /** set window dim or not(设置背景是否昏暗) */ 246 | public T dimEnabled(boolean isDimEnabled) { 247 | if (isDimEnabled) { 248 | getWindow().addFlags(LayoutParams.FLAG_DIM_BEHIND); 249 | } else { 250 | getWindow().clearFlags(LayoutParams.FLAG_DIM_BEHIND); 251 | } 252 | return (T) this; 253 | } 254 | 255 | /** set dialog width scale:0-1(设置对话框宽度,占屏幕宽的比例0-1) */ 256 | public T widthScale(float widthScale) { 257 | this.widthScale = widthScale; 258 | return (T) this; 259 | } 260 | 261 | /** set dialog height scale:0-1(设置对话框高度,占屏幕宽的比例0-1) */ 262 | public T heightScale(float heightScale) { 263 | this.heightScale = heightScale; 264 | return (T) this; 265 | } 266 | 267 | /** set show anim(设置显示的动画) */ 268 | public T showAnim(BaseAnimatorSet showAnim) { 269 | this.showAnim = showAnim; 270 | return (T) this; 271 | } 272 | 273 | /** set dismiss anim(设置隐藏的动画) */ 274 | public T dismissAnim(BaseAnimatorSet dismissAnim) { 275 | this.dismissAnim = dismissAnim; 276 | return (T) this; 277 | } 278 | 279 | @Override 280 | public boolean dispatchTouchEvent(MotionEvent ev) { 281 | if (isDismissAnim || isShowAnim) { 282 | return true; 283 | } 284 | return super.dispatchTouchEvent(ev); 285 | } 286 | 287 | @Override 288 | public void onBackPressed() { 289 | if (isDismissAnim || isShowAnim) { 290 | return; 291 | } 292 | super.onBackPressed(); 293 | } 294 | 295 | /** dp to px */ 296 | protected int dp2px(float dp) { 297 | final float scale = context.getResources().getDisplayMetrics().density; 298 | return (int) (dp * scale + 0.5f); 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/java/com/hsy/update/view/CommonProgressDialog.java: -------------------------------------------------------------------------------- 1 | package com.hsy.update.view; 2 | 3 | import java.text.NumberFormat; 4 | 5 | import android.app.AlertDialog; 6 | import android.content.Context; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.os.Message; 10 | import android.text.Spannable; 11 | import android.text.SpannableString; 12 | import android.text.style.StyleSpan; 13 | import android.widget.ProgressBar; 14 | import android.widget.TextView; 15 | 16 | 17 | import java.text.NumberFormat; 18 | 19 | import android.app.AlertDialog; 20 | import android.content.Context; 21 | import android.os.Bundle; 22 | import android.os.Handler; 23 | import android.os.Message; 24 | import android.text.Spannable; 25 | import android.text.SpannableString; 26 | import android.text.style.StyleSpan; 27 | import android.widget.ProgressBar; 28 | import android.widget.TextView; 29 | 30 | import com.hsy.update.R; 31 | 32 | 33 | /** 34 | * 自定义进度条 35 | * 36 | * @author 黄淑媛 37 | * 38 | */ 39 | 40 | public class CommonProgressDialog extends AlertDialog { 41 | private ProgressBar mProgress; 42 | private TextView mProgressNumber; 43 | private TextView mProgressPercent; 44 | private TextView mProgressMessage; 45 | private Handler mViewUpdateHandler; 46 | private int mMax; 47 | private CharSequence mMessage; 48 | private boolean mHasStarted; 49 | private int mProgressVal; 50 | private String TAG = "CommonProgressDialog"; 51 | private String mProgressNumberFormat; 52 | private NumberFormat mProgressPercentFormat; 53 | 54 | public CommonProgressDialog(Context context) { 55 | super(context); 56 | // TODO Auto-generated constructor stub 57 | initFormats(); 58 | } 59 | 60 | @Override 61 | protected void onCreate(Bundle savedInstanceState) { 62 | // TODO Auto-generated method stub 63 | super.onCreate(savedInstanceState); 64 | setContentView(R.layout.common_progress_dialog); 65 | mProgress = (ProgressBar) findViewById(R.id.progress); 66 | mProgressNumber = (TextView) findViewById(R.id.progress_number); 67 | mProgressPercent = (TextView) findViewById(R.id.progress_percent); 68 | mProgressMessage = (TextView) findViewById(R.id.progress_message); 69 | // LayoutInflater inflater = LayoutInflater.from(getContext()); 70 | mViewUpdateHandler = new Handler() { 71 | @Override 72 | public void handleMessage(Message msg) { 73 | // TODO Auto-generated method stub 74 | super.handleMessage(msg); 75 | int progress = mProgress.getProgress(); 76 | int max = mProgress.getMax(); 77 | double dProgress = (double) progress / (double) (1024 * 1024); 78 | double dMax = (double) max / (double) (1024 * 1024); 79 | if (mProgressNumberFormat != null) { 80 | String format = mProgressNumberFormat; 81 | mProgressNumber.setText(String.format(format, dProgress, 82 | dMax)); 83 | } else { 84 | mProgressNumber.setText(""); 85 | } 86 | if (mProgressPercentFormat != null) { 87 | double percent = (double) progress / (double) max; 88 | SpannableString tmp = new SpannableString( 89 | mProgressPercentFormat.format(percent)); 90 | tmp.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 91 | 0, tmp.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 92 | mProgressPercent.setText(tmp); 93 | } else { 94 | mProgressPercent.setText(""); 95 | } 96 | } 97 | }; 98 | // View view = inflater.inflate(R.layout.common_progress_dialog, null); 99 | // mProgress = (ProgressBar) view.findViewById(R.id.progress); 100 | // mProgressNumber = (TextView) view.findViewById(R.id.progress_number); 101 | // mProgressPercent = (TextView) 102 | // view.findViewById(R.id.progress_percent); 103 | // setView(view); 104 | // mProgress.setMax(100); 105 | onProgressChanged(); 106 | if (mMessage != null) { 107 | setMessage(mMessage); 108 | } 109 | if (mMax > 0) { 110 | setMax(mMax); 111 | } 112 | if (mProgressVal > 0) { 113 | setProgress(mProgressVal); 114 | } 115 | } 116 | 117 | private void initFormats() { 118 | mProgressNumberFormat = "%1.2fM/%2.2fM"; 119 | mProgressPercentFormat = NumberFormat.getPercentInstance(); 120 | mProgressPercentFormat.setMaximumFractionDigits(0); 121 | } 122 | 123 | private void onProgressChanged() { 124 | mViewUpdateHandler.sendEmptyMessage(0); 125 | } 126 | 127 | public void setProgressStyle(int style) { 128 | // mProgressStyle = style; 129 | } 130 | 131 | public int getMax() { 132 | if (mProgress != null) { 133 | return mProgress.getMax(); 134 | } 135 | return mMax; 136 | } 137 | 138 | public void setMax(int max) { 139 | if (mProgress != null) { 140 | mProgress.setMax(max); 141 | onProgressChanged(); 142 | } else { 143 | mMax = max; 144 | } 145 | } 146 | 147 | public void setIndeterminate(boolean indeterminate) { 148 | if (mProgress != null) { 149 | mProgress.setIndeterminate(indeterminate); 150 | } 151 | // else { 152 | // mIndeterminate = indeterminate; 153 | // } 154 | } 155 | 156 | public void setProgress(int value) { 157 | if (mHasStarted) { 158 | mProgress.setProgress(value); 159 | onProgressChanged(); 160 | } else { 161 | mProgressVal = value; 162 | } 163 | } 164 | 165 | @Override 166 | public void setMessage(CharSequence message) { 167 | // TODO Auto-generated method stub 168 | // super.setMessage(message); 169 | if (mProgressMessage != null) { 170 | mProgressMessage.setText(message); 171 | } else { 172 | mMessage = message; 173 | } 174 | } 175 | 176 | @Override 177 | protected void onStart() { 178 | // TODO Auto-generated method stub 179 | super.onStart(); 180 | mHasStarted = true; 181 | } 182 | 183 | @Override 184 | protected void onStop() { 185 | // TODO Auto-generated method stub 186 | super.onStop(); 187 | mHasStarted = false; 188 | } 189 | 190 | } -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/java/com/hsy/update/view/CornerUtils.java: -------------------------------------------------------------------------------- 1 | package com.hsy.update.view; 2 | 3 | import android.graphics.drawable.ColorDrawable; 4 | import android.graphics.drawable.Drawable; 5 | import android.graphics.drawable.GradientDrawable; 6 | import android.graphics.drawable.StateListDrawable; 7 | 8 | /** 9 | * Utils to get corner drawable 10 | */ 11 | public class CornerUtils { 12 | public static Drawable cornerDrawable(final int bgColor, float cornerradius) { 13 | final GradientDrawable bg = new GradientDrawable(); 14 | bg.setCornerRadius(cornerradius); 15 | bg.setColor(bgColor); 16 | 17 | return bg; 18 | } 19 | 20 | public static Drawable cornerDrawable(final int bgColor, float[] cornerradius) { 21 | final GradientDrawable bg = new GradientDrawable(); 22 | bg.setCornerRadii(cornerradius); 23 | bg.setColor(bgColor); 24 | 25 | return bg; 26 | } 27 | 28 | public static Drawable cornerDrawable(final int bgColor, float[] cornerradius, int borderwidth, int bordercolor) { 29 | final GradientDrawable bg = new GradientDrawable(); 30 | bg.setCornerRadii(cornerradius); 31 | bg.setStroke(borderwidth, bordercolor); 32 | bg.setColor(bgColor); 33 | 34 | return bg; 35 | } 36 | 37 | /** 38 | * set btn selector with corner drawable for special position 39 | */ 40 | public static StateListDrawable btnSelector(float radius, int normalColor, int pressColor, int postion) { 41 | StateListDrawable bg = new StateListDrawable(); 42 | Drawable normal = null; 43 | Drawable pressed = null; 44 | 45 | if (postion == 0) {// left btn 46 | normal = cornerDrawable(normalColor, new float[]{0, 0, 0, 0, 0, 0, radius, radius}); 47 | pressed = cornerDrawable(pressColor, new float[]{0, 0, 0, 0, 0, 0, radius, radius}); 48 | } else if (postion == 1) {// right btn 49 | normal = cornerDrawable(normalColor, new float[]{0, 0, 0, 0, radius, radius, 0, 0}); 50 | pressed = cornerDrawable(pressColor, new float[]{0, 0, 0, 0, radius, radius, 0, 0}); 51 | } else if (postion == -1) {// only one btn 52 | normal = cornerDrawable(normalColor, new float[]{0, 0, 0, 0, radius, radius, radius, radius}); 53 | pressed = cornerDrawable(pressColor, new float[]{0, 0, 0, 0, radius, radius, radius, radius}); 54 | } else if (postion == -2) {// for material dialog 55 | normal = cornerDrawable(normalColor, radius); 56 | pressed = cornerDrawable(pressColor, radius); 57 | } 58 | 59 | bg.addState(new int[]{-android.R.attr.state_pressed}, normal); 60 | bg.addState(new int[]{android.R.attr.state_pressed}, pressed); 61 | return bg; 62 | } 63 | 64 | /** 65 | * set ListView item selector with corner drawable for the last position 66 | * (ListView的item点击效果,只处理最后一项圆角处�? 67 | */ 68 | public static StateListDrawable listItemSelector(float radius, int normalColor, int pressColor, boolean isLastPostion) { 69 | StateListDrawable bg = new StateListDrawable(); 70 | Drawable normal = null; 71 | Drawable pressed = null; 72 | 73 | if (!isLastPostion) { 74 | normal = new ColorDrawable(normalColor); 75 | pressed = new ColorDrawable(pressColor); 76 | } else { 77 | normal = cornerDrawable(normalColor, new float[]{0, 0, 0, 0, radius, radius, radius, radius}); 78 | pressed = cornerDrawable(pressColor, new float[]{0, 0, 0, 0, radius, radius, radius, radius}); 79 | } 80 | 81 | bg.addState(new int[]{-android.R.attr.state_pressed}, normal); 82 | bg.addState(new int[]{android.R.attr.state_pressed}, pressed); 83 | return bg; 84 | } 85 | 86 | /** 87 | * set ListView item selector with corner drawable for the first and the last position 88 | * (ListView的item点击效果,第一项和朿Ў丿y圆角处理) 89 | */ 90 | public static StateListDrawable listItemSelector(float radius, int normalColor, int pressColor, int itemTotalSize, 91 | int itemPosition) { 92 | StateListDrawable bg = new StateListDrawable(); 93 | Drawable normal = null; 94 | Drawable pressed = null; 95 | 96 | if (itemPosition == 0 && itemPosition == itemTotalSize - 1) {// 只有丿y 97 | normal = cornerDrawable(normalColor, new float[]{radius, radius, radius, radius, radius, radius, radius, 98 | radius}); 99 | pressed = cornerDrawable(pressColor, new float[]{radius, radius, radius, radius, radius, radius, radius, 100 | radius}); 101 | } else if (itemPosition == 0) { 102 | normal = cornerDrawable(normalColor, new float[]{radius, radius, radius, radius, 0, 0, 0, 0,}); 103 | pressed = cornerDrawable(pressColor, new float[]{radius, radius, radius, radius, 0, 0, 0, 0}); 104 | } else if (itemPosition == itemTotalSize - 1) { 105 | normal = cornerDrawable(normalColor, new float[]{0, 0, 0, 0, radius, radius, radius, radius}); 106 | pressed = cornerDrawable(pressColor, new float[]{0, 0, 0, 0, radius, radius, radius, radius}); 107 | } else { 108 | normal = new ColorDrawable(normalColor); 109 | pressed = new ColorDrawable(pressColor); 110 | } 111 | 112 | bg.addState(new int[]{-android.R.attr.state_pressed}, normal); 113 | bg.addState(new int[]{android.R.attr.state_pressed}, pressed); 114 | return bg; 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/java/com/hsy/update/view/MaterialDialog.java: -------------------------------------------------------------------------------- 1 | package com.hsy.update.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.view.Gravity; 6 | import android.view.View; 7 | import android.widget.LinearLayout; 8 | 9 | import com.hsy.update.utils.ColorData; 10 | 11 | 12 | /** 13 | * Dialog like Material Design Dialog 14 | */ 15 | public class MaterialDialog extends BaseAlertDialog { 16 | 17 | public MaterialDialog(Context context) { 18 | super(context); 19 | 20 | /** default value */ 21 | titleTextColor = Color.parseColor("#DE000000"); 22 | titleTextSize_SP = 22f; 23 | contentTextColor = Color.parseColor("#8a000000"); 24 | contentTextSize_SP = 16f; 25 | leftBtnTextColor = Color.parseColor("#383838"); 26 | // Color.parseColor("#468ED0"); 27 | rightBtnTextColor = ColorData.RED_THEME; 28 | middleBtnTextColor = Color.parseColor("#00796B"); 29 | /** default value */ 30 | } 31 | 32 | @Override 33 | public View onCreateView() { 34 | 35 | /** title */ 36 | tv_title.setGravity(Gravity.CENTER_VERTICAL); 37 | tv_title.setPadding(dp2px(20), dp2px(20), dp2px(20), dp2px(0)); 38 | tv_title.setLayoutParams(new LinearLayout.LayoutParams( 39 | LinearLayout.LayoutParams.MATCH_PARENT, 40 | LinearLayout.LayoutParams.WRAP_CONTENT)); 41 | ll_container.addView(tv_title); 42 | 43 | /** content */ 44 | tv_content.setPadding(dp2px(20), dp2px(20), dp2px(20), dp2px(20)); 45 | tv_content.setLayoutParams(new LinearLayout.LayoutParams( 46 | LinearLayout.LayoutParams.MATCH_PARENT, 47 | LinearLayout.LayoutParams.WRAP_CONTENT)); 48 | ll_container.addView(tv_content); 49 | 50 | /** btns */ 51 | ll_btns.setGravity(Gravity.RIGHT); 52 | ll_btns.addView(tv_btn_left); 53 | ll_btns.addView(tv_btn_middle); 54 | ll_btns.addView(tv_btn_right); 55 | 56 | tv_btn_left.setPadding(dp2px(15), dp2px(8), dp2px(15), dp2px(8)); 57 | tv_btn_right.setPadding(dp2px(15), dp2px(8), dp2px(15), dp2px(8)); 58 | tv_btn_middle.setPadding(dp2px(15), dp2px(8), dp2px(15), dp2px(8)); 59 | ll_btns.setPadding(dp2px(20), dp2px(0), dp2px(10), dp2px(10)); 60 | 61 | ll_container.addView(ll_btns); 62 | 63 | return ll_container; 64 | } 65 | 66 | @Override 67 | public void setUiBeforShow() { 68 | super.setUiBeforShow(); 69 | /** set background color and corner radius */ 70 | float radius = dp2px(cornerRadius_DP); 71 | ll_container.setBackgroundDrawable(CornerUtils.cornerDrawable(bgColor, 72 | radius)); 73 | tv_btn_left.setBackgroundDrawable(CornerUtils.btnSelector(radius, 74 | bgColor, btnPressColor, -2)); 75 | tv_btn_right.setBackgroundDrawable(CornerUtils.btnSelector(radius, 76 | bgColor, btnPressColor, -2)); 77 | tv_btn_middle.setBackgroundDrawable(CornerUtils.btnSelector(radius, 78 | bgColor, btnPressColor, -2)); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/java/com/hsy/update/view/OnBtnClickL.java: -------------------------------------------------------------------------------- 1 | package com.hsy.update.view; 2 | /** 3 | * Created by huagnshuyuan on 2017/3/16. 4 | */ 5 | public interface OnBtnClickL { 6 | void onBtnClick(); 7 | } 8 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/java/com/hsy/update/view/StatusBarUtils.java: -------------------------------------------------------------------------------- 1 | package com.hsy.update.view; 2 | 3 | import android.content.Context; 4 | import android.text.TextUtils; 5 | import android.util.Log; 6 | /** 7 | * Created by huagnshuyuan on 2017/3/16. 8 | */ 9 | /** 10 | * StatusBar Utils handle with special FlymeOS4.x/Android4.4.4 11 | * (状态栏工具,处理魅族FlymeOS4.x/Android4.4.4) 12 | */ 13 | public class StatusBarUtils { 14 | public static int getHeight(Context context) { 15 | int statusBarHeight = 0; 16 | int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); 17 | if (resourceId > 0) { 18 | statusBarHeight = context.getResources().getDimensionPixelSize(resourceId); 19 | } 20 | Log.d(StatusBarUtils.class.getSimpleName(), "statusBarHeight--->" + statusBarHeight); 21 | if (isFlymeOs4x()) { 22 | return 2 * statusBarHeight; 23 | } 24 | 25 | return statusBarHeight; 26 | } 27 | 28 | public static boolean isFlymeOs4x() { 29 | String sysVersion = android.os.Build.VERSION.RELEASE; 30 | if ("4.4.4".equals(sysVersion)) { 31 | String sysIncrement = android.os.Build.VERSION.INCREMENTAL; 32 | String displayId = android.os.Build.DISPLAY; 33 | if (!TextUtils.isEmpty(sysIncrement)) { 34 | return sysIncrement.contains("Flyme_OS_4"); 35 | } else { 36 | return displayId.contains("Flyme OS 4"); 37 | } 38 | } 39 | return false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/drawable/common_progress_dialog_progressbar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangshuyuan/UpdateDemo/e3ef67c550aa571bbe072d54f7044f430c905042/UpdateDemo/app/src/main/res/drawable/common_progress_dialog_progressbar2.png -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/drawable/common_progress_dialog_progressbar3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangshuyuan/UpdateDemo/e3ef67c550aa571bbe072d54f7044f430c905042/UpdateDemo/app/src/main/res/drawable/common_progress_dialog_progressbar3.png -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/drawable/common_progressdialog_progressbar_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/layout/common_progress_dialog.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 22 | 23 | 32 | 33 | 39 | 40 | 49 | 50 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/layout/title_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangshuyuan/UpdateDemo/e3ef67c550aa571bbe072d54f7044f430c905042/UpdateDemo/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangshuyuan/UpdateDemo/e3ef67c550aa571bbe072d54f7044f430c905042/UpdateDemo/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangshuyuan/UpdateDemo/e3ef67c550aa571bbe072d54f7044f430c905042/UpdateDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangshuyuan/UpdateDemo/e3ef67c550aa571bbe072d54f7044f430c905042/UpdateDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangshuyuan/UpdateDemo/e3ef67c550aa571bbe072d54f7044f430c905042/UpdateDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | #eb2127 7 | #ffffff 8 | 9 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | UpdateDemo 3 | 友情提示 4 | 尚未获取SD卡读取权限 5 | 获取SD卡读取权限 6 | 去设置 7 | 取消 8 | 用户从设置回来了 9 | 获取SD卡权限成功 10 | 获取SD卡权限失败 11 | 12 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /UpdateDemo/app/src/test/java/com/hsy/update/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.hsy.update; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /UpdateDemo/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /UpdateDemo/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /UpdateDemo/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangshuyuan/UpdateDemo/e3ef67c550aa571bbe072d54f7044f430c905042/UpdateDemo/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /UpdateDemo/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /UpdateDemo/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /UpdateDemo/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /UpdateDemo/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------