优化细节

pull/27/head
jenly1314 5 years ago
parent ea8f2603a8
commit 0a7e40dfce
  1. 28
      README.md
  2. 5
      app-dialog/src/main/java/com/king/app/dialog/AppDialog.java
  3. 1
      app-updater/build.gradle
  4. 27
      app-updater/src/main/java/com/king/app/updater/AppUpdater.java
  5. 2
      app-updater/src/main/java/com/king/app/updater/callback/UpdateCallback.java
  6. 11
      app-updater/src/main/java/com/king/app/updater/constant/Constants.java
  7. 24
      app-updater/src/main/java/com/king/app/updater/http/HttpManager.java
  8. 3
      app-updater/src/main/java/com/king/app/updater/http/IHttpManager.java
  9. 227
      app-updater/src/main/java/com/king/app/updater/http/OkHttpManager.java
  10. 55
      app-updater/src/main/java/com/king/app/updater/service/DownloadService.java
  11. 89
      app-updater/src/main/java/com/king/app/updater/util/AppUtils.java
  12. 17
      app-updater/src/main/java/com/king/app/updater/util/PermissionUtils.java
  13. 4
      app-updater/src/main/java/com/king/app/updater/util/SSLSocketFactoryUtils.java
  14. 1
      app/build.gradle
  15. BIN
      app/release/app-release.apk
  16. 2
      app/release/output.json
  17. 26
      app/src/main/java/com/king/appupdater/MainActivity.java
  18. 9
      versions.gradle

@ -3,7 +3,7 @@
![Image](app/src/main/ic_launcher-web.png) ![Image](app/src/main/ic_launcher-web.png)
[![Download](https://img.shields.io/badge/download-App-blue.svg)](https://raw.githubusercontent.com/jenly1314/AppUpdater/master/app/release/app-release.apk) [![Download](https://img.shields.io/badge/download-App-blue.svg)](https://raw.githubusercontent.com/jenly1314/AppUpdater/master/app/release/app-release.apk)
[![Jitpack](https://jitpack.io/v/jenly1314/AppUpdater.svg)](https://jitpack.io/#jenly1314/AppUpdater) [![JitPack](https://jitpack.io/v/jenly1314/AppUpdater.svg)](https://jitpack.io/#jenly1314/AppUpdater)
[![CI](https://travis-ci.org/jenly1314/AppUpdater.svg?branch=master)](https://travis-ci.org/jenly1314/AppUpdater) [![CI](https://travis-ci.org/jenly1314/AppUpdater.svg?branch=master)](https://travis-ci.org/jenly1314/AppUpdater)
[![CircleCI](https://circleci.com/gh/jenly1314/AppUpdater.svg?style=svg)](https://circleci.com/gh/jenly1314/AppUpdater) [![CircleCI](https://circleci.com/gh/jenly1314/AppUpdater.svg?style=svg)](https://circleci.com/gh/jenly1314/AppUpdater)
[![API](https://img.shields.io/badge/API-15%2B-blue.svg?style=flat)](https://android-arsenal.com/api?level=15) [![API](https://img.shields.io/badge/API-15%2B-blue.svg?style=flat)](https://android-arsenal.com/api?level=15)
@ -25,7 +25,8 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
- [x] 支持下载失败,重新下载 - [x] 支持下载失败,重新下载
- [x] 支持下载优先取本地缓存 - [x] 支持下载优先取本地缓存
- [x] 支持通知栏提示内容和过程全部可配置 - [x] 支持通知栏提示内容和过程全部可配置
- [x] 支持Android O - [x] 支持Android Q(10)
- [x] 支持使用OkHttpClient下载
## Gif 展示 ## Gif 展示
@ -39,7 +40,7 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
<dependency> <dependency>
<groupId>com.king.app</groupId> <groupId>com.king.app</groupId>
<artifactId>app-updater</artifactId> <artifactId>app-updater</artifactId>
<version>1.0.5</version> <version>1.0.6</version>
<type>pom</type> <type>pom</type>
</dependency> </dependency>
@ -47,7 +48,7 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
<dependency> <dependency>
<groupId>com.king.app</groupId> <groupId>com.king.app</groupId>
<artifactId>app-dialog</artifactId> <artifactId>app-dialog</artifactId>
<version>1.0.5</version> <version>1.0.6</version>
<type>pom</type> <type>pom</type>
</dependency> </dependency>
``` ```
@ -56,25 +57,25 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
//----------AndroidX 版本 //----------AndroidX 版本
//app-updater //app-updater
implementation 'com.king.app:app-updater:1.0.5-androidx' implementation 'com.king.app:app-updater:1.0.6-androidx'
//app-dialog //app-dialog
implementation 'com.king.app:app-dialog:1.0.5-androidx' implementation 'com.king.app:app-dialog:1.0.6-androidx'
//----------Android 版本 //----------Android Support 版本
//app-updater //app-updater
implementation 'com.king.app:app-updater:1.0.5' implementation 'com.king.app:app-updater:1.0.6'
//app-dialog //app-dialog
implementation 'com.king.app:app-dialog:1.0.5' implementation 'com.king.app:app-dialog:1.0.6'
``` ```
### Lvy: ### Lvy:
```lvy ```lvy
//app-updater //app-updater
<dependency org='com.king.app' name='app-dialog' rev='1.0.5'> <dependency org='com.king.app' name='app-dialog' rev='1.0.6'>
<artifact name='$AID' ext='pom'></artifact> <artifact name='$AID' ext='pom'></artifact>
</dependency> </dependency>
//app-dialog //app-dialog
<dependency org='com.king.app' name='app-dialog' rev='1.0.5'> <dependency org='com.king.app' name='app-dialog' rev='1.0.6'>
<artifact name='$AID' ext='pom'></artifact> <artifact name='$AID' ext='pom'></artifact>
</dependency> </dependency>
``` ```
@ -127,6 +128,7 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
.serUrl(mUrl) .serUrl(mUrl)
.setFilename("AppUpdater.apk") .setFilename("AppUpdater.apk")
.build(getContext()) .build(getContext())
.setHttpManager(OkHttpManager.getInstance())//使用OkHttpClient实现下载,需依赖okhttp库
.start(); .start();
AppDialog.INSTANCE.dismissDialogFragment(getSupportFragmentManager()); AppDialog.INSTANCE.dismissDialogFragment(getSupportFragmentManager());
} }
@ -139,6 +141,10 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
## 版本记录 ## 版本记录
#### v1.0.6:2019-11-27
* 新增OkHttpManager 如果使用了OkHttpManager则必须依赖[okhttp](https://github.com/square/okhttp)
* 优化细节 (progress,total 变更 int -> long)
#### v1.0.5:2019-9-4 #### v1.0.5:2019-9-4
* 支持取消下载 * 支持取消下载

@ -39,7 +39,7 @@ public enum AppDialog {
* @param config 弹框配置 {@link AppDialogConfig} * @param config 弹框配置 {@link AppDialogConfig}
* @return * @return
*/ */
public View createAppDialogView(@NonNull Context context, @NonNull AppDialogConfig config){ public View createAppDialogView(@NonNull Context context,@NonNull AppDialogConfig config){
View view = config.getView(context); View view = config.getView(context);
TextView tvDialogTitle = view.findViewById(config.getTitleId()); TextView tvDialogTitle = view.findViewById(config.getTitleId());
setText(tvDialogTitle,config.getTitle()); setText(tvDialogTitle,config.getTitle());
@ -235,8 +235,9 @@ public enum AppDialog {
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK && isCancel){ if(keyCode == KeyEvent.KEYCODE_BACK && isCancel){
dismissDialog(); dismissDialog();
}
return true; return true;
}
return false;
} }
}); });

@ -36,5 +36,6 @@ dependencies {
androidTestImplementation deps.test.espresso androidTestImplementation deps.test.espresso
//support //support
compileOnly deps.support.appcompat compileOnly deps.support.appcompat
compileOnly deps.okhttp
} }

@ -11,7 +11,9 @@ import android.util.Log;
import com.king.app.updater.callback.UpdateCallback; import com.king.app.updater.callback.UpdateCallback;
import com.king.app.updater.constant.Constants; import com.king.app.updater.constant.Constants;
import com.king.app.updater.http.HttpManager;
import com.king.app.updater.http.IHttpManager; import com.king.app.updater.http.IHttpManager;
import com.king.app.updater.http.OkHttpManager;
import com.king.app.updater.service.DownloadService; import com.king.app.updater.service.DownloadService;
import com.king.app.updater.util.PermissionUtils; import com.king.app.updater.util.PermissionUtils;
@ -44,7 +46,7 @@ public class AppUpdater {
private ServiceConnection mServiceConnection; private ServiceConnection mServiceConnection;
public AppUpdater(@NonNull Context context, @NonNull UpdateConfig config){ public AppUpdater(@NonNull Context context,@NonNull UpdateConfig config){
this.mContext = context; this.mContext = context;
this.mConfig = config; this.mConfig = config;
} }
@ -55,11 +57,24 @@ public class AppUpdater {
mConfig.setUrl(url); mConfig.setUrl(url);
} }
/**
* 设置下载更新进度回调
* @param callback
* @return
*/
public AppUpdater setUpdateCallback(UpdateCallback callback){ public AppUpdater setUpdateCallback(UpdateCallback callback){
this.mCallback = callback; this.mCallback = callback;
return this; return this;
} }
/**
* 设置一个IHttpManager
* @param httpManager AppUpdater内置提供{@link HttpManager} {@link OkHttpManager}两种实现
* 如果不设置将默认使用{@link HttpManager},你也可以使用{@link OkHttpManager}或自己去实现一个
* {@link IHttpManager}
* 当使用{@link OkHttpManager}必需依赖okhttp库
* @return
*/
public AppUpdater setHttpManager(IHttpManager httpManager){ public AppUpdater setHttpManager(IHttpManager httpManager){
this.mHttpManager = httpManager; this.mHttpManager = httpManager;
return this; return this;
@ -72,10 +87,10 @@ public class AppUpdater {
if(mConfig!=null && !TextUtils.isEmpty(mConfig.getUrl())){ if(mConfig!=null && !TextUtils.isEmpty(mConfig.getUrl())){
//如果mContext是Activity,则默认会校验一次动态权限。 //如果mContext是Activity,则默认会校验一次动态权限。
if(mContext instanceof Activity){ if(mContext instanceof Activity){
PermissionUtils.INSTANCE.verifyReadAndWritePermissions((Activity) mContext,Constants.RE_CODE_STORAGE_PERMISSION); PermissionUtils.verifyReadAndWritePermissions((Activity) mContext,Constants.RE_CODE_STORAGE_PERMISSION);
} }
if(mConfig.isShowNotification() && !PermissionUtils.INSTANCE.isNotificationEnabled(mContext)){ if(mConfig.isShowNotification() && !PermissionUtils.isNotificationEnabled(mContext)){
Log.w(Constants.TAG,"Notification permission not enabled."); Log.w(Constants.TAG,"Notification permission not enabled.");
} }
@ -129,7 +144,7 @@ public class AppUpdater {
} }
/** /**
* AppUpdater构建器 * AppUpdater建造者
*/ */
public static class Builder{ public static class Builder{
@ -151,7 +166,7 @@ public class AppUpdater {
/** /**
* 设置保存的路径 * 设置保存的路径
* @param path 下载保存的文件路径 默认SD卡/.AppUpdater目录 * @param path 下载保存的文件路径
* @return * @return
*/ */
public Builder setPath(String path){ public Builder setPath(String path){
@ -252,7 +267,7 @@ public class AppUpdater {
/** /**
* 设置FileProvider的authority * 设置FileProvider的authority
* @param authority FileProvider的authority默认兼容N默认值context.getPackageName() + ".fileProvider" * @param authority FileProvider的authority默认兼容N默认值{@link Context#getPackageName() + ".fileProvider"}
* @return * @return
*/ */
public Builder setAuthority(String authority){ public Builder setAuthority(String authority){

@ -24,7 +24,7 @@ public interface UpdateCallback {
* @param total * @param total
* @param isChange 进度百分比是否有改变主要可以用来过滤无用的刷新从而降低刷新频率 * @param isChange 进度百分比是否有改变主要可以用来过滤无用的刷新从而降低刷新频率
*/ */
void onProgress(int progress,int total,boolean isChange); void onProgress(long progress,long total,boolean isChange);
/** /**
* 完成 * 完成

@ -1,9 +1,5 @@
package com.king.app.updater.constant; package com.king.app.updater.constant;
import android.os.Environment;
import java.io.File;
/** /**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/ */
@ -23,9 +19,12 @@ public final class Constants {
public static final String KEY_RE_DOWNLOAD = "app_update_re_download"; public static final String KEY_RE_DOWNLOAD = "app_update_re_download";
public static final String DEFAULT_DIR_PATH = Environment.getExternalStorageDirectory() + File.separator + ".AppUpdater";
public static final int RE_CODE_STORAGE_PERMISSION = 0x66; public static final int RE_CODE_STORAGE_PERMISSION = 0x66;
public static final int NONE = -1; public static final int NONE = -1;
public static final String DEFAULT_FILE_PROVIDER = ".fileProvider";
public static final String DEFAULT_DIR = "apk";
} }

@ -20,6 +20,7 @@ import javax.net.ssl.HttpsURLConnection;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
/** /**
* HttpManager使用{@link HttpURLConnection}实现{@link IHttpManager}
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/ */
public class HttpManager implements IHttpManager { public class HttpManager implements IHttpManager {
@ -30,14 +31,16 @@ public class HttpManager implements IHttpManager {
private boolean isCancel; private boolean isCancel;
private static HttpManager INSTANCE; private static volatile HttpManager INSTANCE;
public static HttpManager getInstance(){ public static HttpManager getInstance(){
if(INSTANCE == null){ if(INSTANCE == null){
synchronized (HttpManager.class){ synchronized (HttpManager.class){
if(INSTANCE == null){
INSTANCE = new HttpManager(); INSTANCE = new HttpManager();
} }
} }
}
return INSTANCE; return INSTANCE;
} }
@ -46,6 +49,9 @@ public class HttpManager implements IHttpManager {
this(DEFAULT_TIME_OUT); this(DEFAULT_TIME_OUT);
} }
/**
* HttpManager对外暴露如果没有特殊需求推荐使用{@link HttpManager#getInstance()}
*/
public HttpManager(int timeout){ public HttpManager(int timeout){
this.mTimeout = timeout; this.mTimeout = timeout;
} }
@ -64,7 +70,7 @@ public class HttpManager implements IHttpManager {
/** /**
* 异步下载任务 * 异步下载任务
*/ */
private class DownloadTask extends AsyncTask<Void,Integer,File> { private class DownloadTask extends AsyncTask<Void,Long,File> {
private String url; private String url;
private String path; private String path;
@ -112,15 +118,17 @@ public class HttpManager implements IHttpManager {
InputStream is = connect.getInputStream(); InputStream is = connect.getInputStream();
int length = connect.getContentLength(); long length = connect.getContentLength();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
length = (int)connect.getContentLengthLong(); length = connect.getContentLengthLong();
} }
int progress = 0; Log.d(Constants.TAG,"contentLength:" + length);
long progress = 0;
byte[] buffer = new byte[4096]; byte[] buffer = new byte[8192];
int len; int len;
File file = new File(path,filename); File file = new File(path,filename);
@ -133,8 +141,10 @@ public class HttpManager implements IHttpManager {
fos.write(buffer,0,len); fos.write(buffer,0,len);
progress += len; progress += len;
//更新进度 //更新进度
if(length>0){
publishProgress(progress,length); publishProgress(progress,length);
} }
}
fos.flush(); fos.flush();
fos.close(); fos.close();
@ -177,7 +187,7 @@ public class HttpManager implements IHttpManager {
} }
@Override @Override
protected void onProgressUpdate(Integer... values) { protected void onProgressUpdate(Long... values) {
super.onProgressUpdate(values); super.onProgressUpdate(values);
if(callback!=null){ if(callback!=null){
if(!isCancelled()){ if(!isCancelled()){

@ -8,6 +8,7 @@ import java.util.Map;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
/** /**
* IHttpManager 默认提供{@link HttpManager} {@link OkHttpManager}两种实现
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/ */
public interface IHttpManager { public interface IHttpManager {
@ -40,7 +41,7 @@ public interface IHttpManager {
* @param progress * @param progress
* @param total * @param total
*/ */
void onProgress(int progress,int total); void onProgress(long progress,long total);
/** /**
* 完成 * 完成

@ -0,0 +1,227 @@
package com.king.app.updater.http;
import android.os.AsyncTask;
import android.util.Log;
import com.king.app.updater.constant.Constants;
import com.king.app.updater.util.SSLSocketFactoryUtils;
import org.apache.http.conn.ssl.SSLSocketFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.ConnectException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* OkHttpManager使用{@link OkHttpClient}实现{@link IHttpManager}
* 使用OkHttpManager时必须依赖OkHttp库
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class OkHttpManager implements IHttpManager {
private static final int DEFAULT_TIME_OUT = 20000;
private OkHttpClient okHttpClient;
private boolean isCancel;
private static volatile OkHttpManager INSTANCE;
public static OkHttpManager getInstance(){
if(INSTANCE == null){
synchronized (HttpManager.class){
if(INSTANCE == null){
INSTANCE = new OkHttpManager();
}
}
}
return INSTANCE;
}
private OkHttpManager(){
this(DEFAULT_TIME_OUT);
}
/**
* HttpManager对外暴露如果没有特殊需求推荐使用{@link HttpManager#getInstance()}
* @param timeout 超时时间单位毫秒
*/
public OkHttpManager(int timeout){
this(new OkHttpClient.Builder()
.readTimeout(timeout, TimeUnit.MILLISECONDS)
.connectTimeout(timeout, TimeUnit.MILLISECONDS)
.sslSocketFactory(SSLSocketFactoryUtils.createSSLSocketFactory(),SSLSocketFactoryUtils.createTrustAllManager())
.hostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
.build());
}
/**
* HttpManager对外暴露推荐使用{@link HttpManager#getInstance()}
* @param okHttpClient {@link OkHttpClient}
*/
public OkHttpManager(@NonNull OkHttpClient okHttpClient){
this.okHttpClient = okHttpClient;
}
@Override
public void download(String url, final String path, final String filename, @Nullable Map<String, String> requestProperty, final DownloadCallback callback) {
isCancel = false;
new DownloadTask(okHttpClient,url,path,filename,requestProperty,callback).execute();
}
@Override
public void cancel() {
isCancel = true;
}
/**
* 异步下载任务
*/
private class DownloadTask extends AsyncTask<Void,Long, File> {
private String url;
private String path;
private String filename;
private Map<String,String> requestProperty;
private DownloadCallback callback;
private Exception exception;
private OkHttpClient okHttpClient;
public DownloadTask(OkHttpClient okHttpClient,String url, String path, String filename ,@Nullable Map<String,String> requestProperty, DownloadCallback callback){
this.okHttpClient = okHttpClient;
this.url = url;
this.path = path;
this.filename = filename;
this.callback = callback;
this.requestProperty = requestProperty;
}
@Override
protected File doInBackground(Void... voids) {
try{
Request.Builder builder = new Request.Builder()
.url(url)
.addHeader("Accept-Encoding", "identity")
.get();
if(requestProperty!=null){
for(Map.Entry<String,String> entry : requestProperty.entrySet()){
builder.addHeader(entry.getKey(),entry.getValue());
}
}
Call call = okHttpClient.newCall(builder.build());
Response response = call.execute();
if(response.isSuccessful()){
InputStream is = response.body().byteStream();
long length = response.body().contentLength();
Log.d(Constants.TAG,"contentLength:" + length);
long progress = 0;
byte[] buffer = new byte[8192];
int len;
File file = new File(path,filename);
FileOutputStream fos = new FileOutputStream(file);
while ((len = is.read(buffer)) != -1){
if(isCancel){
if(call != null){
call.cancel();
}
cancel(true);
break;
}
fos.write(buffer,0,len);
progress += len;
//更新进度
if(length>0){
publishProgress(progress,length);
}
}
fos.flush();
fos.close();
is.close();
response.close();
return file;
}else {//连接失败
throw new ConnectException(String.format("responseCode = %d",response.code()));
}
}catch (Exception e){
this.exception = e;
e.printStackTrace();
}
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
if(callback!=null){
callback.onStart(url);
}
}
@Override
protected void onPostExecute(File file) {
super.onPostExecute(file);
if(callback!=null){
if(file!=null){
callback.onFinish(file);
}else{
callback.onError(exception);
}
}
}
@Override
protected void onProgressUpdate(Long... values) {
super.onProgressUpdate(values);
if(callback!=null){
if(!isCancelled()){
callback.onProgress(values[0],values[1]);
}
}
}
@Override
protected void onCancelled() {
super.onCancelled();
if(callback!=null){
callback.onCancel();
}
}
}
}

@ -7,10 +7,8 @@ import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri;
import android.os.Binder; import android.os.Binder;
import android.os.Build; import android.os.Build;
import android.os.Environment;
import android.os.IBinder; import android.os.IBinder;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
@ -29,7 +27,7 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.content.FileProvider; import androidx.core.content.ContextCompat;
/** /**
@ -119,16 +117,16 @@ public class DownloadService extends Service {
//如果保存路径为空则使用缓存路径 //如果保存路径为空则使用缓存路径
if(TextUtils.isEmpty(path)){ if(TextUtils.isEmpty(path)){
path = getDiskCacheDir(getContext()); path = getExternalFilesDir(getContext());
} }
File dirFile = new File(path); File dirFile = new File(path);
if(!dirFile.exists()){ if(!dirFile.exists()){
dirFile.mkdir(); dirFile.mkdirs();
} }
//如果文件名为空则使用路径 //如果文件名为空则使用路径
if(TextUtils.isEmpty(filename)){ if(TextUtils.isEmpty(filename)){
filename = AppUtils.INSTANCE.getAppFullName(getContext(),url,getResources().getString(R.string.app_name)); filename = AppUtils.getAppFullName(getContext(),url,getResources().getString(R.string.app_name));
} }
mFile = new File(path,filename); mFile = new File(path,filename);
@ -136,15 +134,15 @@ public class DownloadService extends Service {
Integer versionCode = config.getVersionCode(); Integer versionCode = config.getVersionCode();
if(versionCode!=null){ if(versionCode!=null){
try{ try{
if(AppUtils.INSTANCE.apkExists(getContext(),versionCode,mFile)){ if(AppUtils.apkExists(getContext(),versionCode,mFile)){
//本地已经存在要下载的APK //本地已经存在要下载的APK
Log.d(Constants.TAG,"CacheFile:" + mFile); Log.d(Constants.TAG,"CacheFile:" + mFile);
if(config.isInstallApk()){ if(config.isInstallApk()){
String authority = config.getAuthority(); String authority = config.getAuthority();
if(TextUtils.isEmpty(authority)){//如果为空则默认 if(TextUtils.isEmpty(authority)){//如果为空则默认
authority = getContext().getPackageName() + ".fileProvider"; authority = getContext().getPackageName() + Constants.DEFAULT_FILE_PROVIDER;
} }
AppUtils.INSTANCE.installApk(getContext(),mFile,authority); AppUtils.installApk(getContext(),mFile,authority);
} }
if(callback!=null){ if(callback!=null){
callback.onFinish(mFile); callback.onFinish(mFile);
@ -184,12 +182,13 @@ public class DownloadService extends Service {
* @param context * @param context
* @return * @return
*/ */
public String getDiskCacheDir(Context context) { private String getExternalFilesDir(Context context) {
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { File[] files = ContextCompat.getExternalFilesDirs(context,Constants.DEFAULT_DIR);
return Constants.DEFAULT_DIR_PATH; if(files!=null && files.length > 0){
return files[0].getAbsolutePath();
} }
return context.getExternalFilesDir(Constants.DEFAULT_DIR).getAbsolutePath();
return context.getCacheDir().getAbsolutePath();
} }
/** /**
@ -238,8 +237,8 @@ public class DownloadService extends Service {
this.channelId = TextUtils.isEmpty(config.getChannelId()) ? Constants.DEFAULT_NOTIFICATION_CHANNEL_ID : config.getChannelId(); this.channelId = TextUtils.isEmpty(config.getChannelId()) ? Constants.DEFAULT_NOTIFICATION_CHANNEL_ID : config.getChannelId();
this.channelName = TextUtils.isEmpty(config.getChannelName()) ? Constants.DEFAULT_NOTIFICATION_CHANNEL_NAME : config.getChannelName(); this.channelName = TextUtils.isEmpty(config.getChannelName()) ? Constants.DEFAULT_NOTIFICATION_CHANNEL_NAME : config.getChannelName();
} }
if(config.getNotificationIcon()<=0){ if(config.getNotificationIcon() <=0 ){
this.notificationIcon = AppUtils.INSTANCE.getAppIcon(getContext()); this.notificationIcon = AppUtils.getAppIcon(getContext());
}else{ }else{
this.notificationIcon = config.getNotificationIcon(); this.notificationIcon = config.getNotificationIcon();
} }
@ -248,7 +247,7 @@ public class DownloadService extends Service {
this.authority = config.getAuthority(); this.authority = config.getAuthority();
if(TextUtils.isEmpty(config.getAuthority())){//如果为空则默认 if(TextUtils.isEmpty(config.getAuthority())){//如果为空则默认
authority = getContext().getPackageName() + ".fileProvider"; authority = getContext().getPackageName() + Constants.DEFAULT_FILE_PROVIDER;
} }
this.isShowPercentage = config.isShowPercentage(); this.isShowPercentage = config.isShowPercentage();
@ -256,7 +255,6 @@ public class DownloadService extends Service {
} }
@Override @Override
public void onStart(String url) { public void onStart(String url) {
Log.d(Constants.TAG,"onStart:" + url); Log.d(Constants.TAG,"onStart:" + url);
@ -272,7 +270,7 @@ public class DownloadService extends Service {
} }
@Override @Override
public void onProgress(int progress, int total) { public void onProgress(long progress, long total) {
boolean isChange = false; boolean isChange = false;
long curTime = System.currentTimeMillis(); long curTime = System.currentTimeMillis();
@ -290,7 +288,7 @@ public class DownloadService extends Service {
content += percentage; content += percentage;
} }
showProgressNotification(notifyId, channelId, notificationIcon, getString(R.string.app_updater_progress_notification_title), content, progress, total); showProgressNotification(notifyId, channelId, notificationIcon, getString(R.string.app_updater_progress_notification_title), content, currProgress, 100);
} }
} }
@ -307,7 +305,7 @@ public class DownloadService extends Service {
isDownloading = false; isDownloading = false;
showFinishNotification(notifyId,channelId,notificationIcon,getString(R.string.app_updater_finish_notification_title),getString(R.string.app_updater_finish_notification_content),file,authority); showFinishNotification(notifyId,channelId,notificationIcon,getString(R.string.app_updater_finish_notification_title),getString(R.string.app_updater_finish_notification_content),file,authority);
if(isInstallApk){ if(isInstallApk){
AppUtils.INSTANCE.installApk(getContext(),file,authority); AppUtils.installApk(getContext(),file,authority);
} }
if(callback!=null){ if(callback!=null){
callback.onFinish(file); callback.onFinish(file);
@ -366,7 +364,7 @@ public class DownloadService extends Service {
* @param title * @param title
* @param content * @param content
*/ */
private void showStartNotification(int notifyId, String channelId, String channelName, @DrawableRes int icon, CharSequence title, CharSequence content, boolean isVibrate, boolean isSound){ private void showStartNotification(int notifyId,String channelId, String channelName,@DrawableRes int icon,CharSequence title,CharSequence content,boolean isVibrate,boolean isSound){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
createNotificationChannel(channelId,channelName,isVibrate,isSound); createNotificationChannel(channelId,channelName,isVibrate,isSound);
} }
@ -413,18 +411,7 @@ public class DownloadService extends Service {
cancelNotification(notifyId); cancelNotification(notifyId);
NotificationCompat.Builder builder = buildNotification(channelId,icon,title,content); NotificationCompat.Builder builder = buildNotification(channelId,icon,title,content);
builder.setAutoCancel(true); builder.setAutoCancel(true);
Intent intent = new Intent(Intent.ACTION_VIEW); Intent intent = AppUtils.getInstallIntent(getContext(),file,authority);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addCategory(Intent.CATEGORY_DEFAULT);
Uri uriData;
String type = "application/vnd.android.package-archive";
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
uriData = FileProvider.getUriForFile(getContext(), authority, file);
}else{
uriData = Uri.fromFile(file);
}
intent.setDataAndType(uriData, type);
PendingIntent clickIntent = PendingIntent.getActivity(getContext(), notifyId,intent, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent clickIntent = PendingIntent.getActivity(getContext(), notifyId,intent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(clickIntent); builder.setContentIntent(clickIntent);
Notification notification = builder.build(); Notification notification = builder.build();
@ -469,7 +456,7 @@ public class DownloadService extends Service {
} }
/** /**
* * 取消通知
* @param notifyId * @param notifyId
*/ */
private void cancelNotification(int notifyId){ private void cancelNotification(int notifyId){

@ -1,32 +1,38 @@
package com.king.app.updater.util; package com.king.app.updater.util;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import java.io.File; import java.io.File;
import java.lang.Exception; import java.io.FileNotFoundException;
import java.io.IOException;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
/** /**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/ */
public enum AppUtils { public final class AppUtils {
INSTANCE; private AppUtils(){
throw new AssertionError();
}
/** /**
* 通过url获取App的全名称 * 通过url获取App的全名称
* @param context * @param context
* @return AppName.apk * @return AppName.apk
*/ */
public String getAppFullName(Context context,String url,String defaultName){ public static String getAppFullName(Context context,String url,String defaultName){
if(url.endsWith(".apk")){ if(url.endsWith(".apk")){
return url.substring(url.lastIndexOf("/") + 1); return url.substring(url.lastIndexOf("/") + 1);
} }
@ -46,7 +52,7 @@ public enum AppUtils {
* @return * @return
* @throws PackageManager.NameNotFoundException * @throws PackageManager.NameNotFoundException
*/ */
public PackageInfo getPackageInfo(Context context) throws PackageManager.NameNotFoundException { public static PackageInfo getPackageInfo(Context context) throws PackageManager.NameNotFoundException {
PackageManager packageManager = context.getPackageManager(); PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo( context.getPackageName(), 0); PackageInfo packageInfo = packageManager.getPackageInfo( context.getPackageName(), 0);
return packageInfo; return packageInfo;
@ -68,7 +74,7 @@ public enum AppUtils {
/** /**
* 获取App的名称 * 获取App的名称
*/ */
public String getAppName(Context context) { public static String getAppName(Context context) {
try{ try{
int labelRes = getPackageInfo(context).applicationInfo.labelRes; int labelRes = getPackageInfo(context).applicationInfo.labelRes;
@ -84,7 +90,7 @@ public enum AppUtils {
* @param context * @param context
* @return * @return
*/ */
public int getAppIcon(Context context){ public static int getAppIcon(Context context){
try{ try{
return getPackageInfo(context).applicationInfo.icon; return getPackageInfo(context).applicationInfo.icon;
} catch (Exception e) { } catch (Exception e) {
@ -99,7 +105,19 @@ public enum AppUtils {
* @param context * @param context
* @param file * @param file
*/ */
public void installApk(Context context,File file,String authority){ public static void installApk(Context context,File file,String authority){
Intent intent = getInstallIntent(context,file,authority);
context.startActivity(intent);
}
/**
* 获取安装Intent
* @param context
* @param file
* @param authority
* @return
*/
public static Intent getInstallIntent(Context context,File file,String authority){
Intent intent = new Intent(Intent.ACTION_VIEW); Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@ -113,7 +131,7 @@ public enum AppUtils {
uriData = Uri.fromFile(file); uriData = Uri.fromFile(file);
} }
intent.setDataAndType(uriData, type); intent.setDataAndType(uriData, type);
context.startActivity(intent); return intent;
} }
/** /**
@ -124,7 +142,7 @@ public enum AppUtils {
* @return * @return
* @throws Exception * @throws Exception
*/ */
public boolean apkExists(Context context,int versionCode,File file) throws Exception{ public static boolean apkExists(Context context,int versionCode,File file) throws Exception{
if(file!=null && file.exists()){ if(file!=null && file.exists()){
String packageName = context.getPackageName(); String packageName = context.getPackageName();
PackageInfo packageInfo = AppUtils.getPackageInfo(context,file.getAbsolutePath()); PackageInfo packageInfo = AppUtils.getPackageInfo(context,file.getAbsolutePath());
@ -137,4 +155,55 @@ public enum AppUtils {
} }
return false; return false;
} }
/**
* 判断文件是否存在
* @param context
* @param path
* @return
*/
public static boolean isAndroidQFileExists(Context context,String path){
return isAndroidQFileExists(context,new File(path));
}
/**
* 判断文件是否存在
* @param context
* @param file
* @return
*/
public static boolean isAndroidQFileExists(Context context,File file){
AssetFileDescriptor descriptor = null;
ContentResolver contentResolver = context.getContentResolver();
try {
Uri uri = Uri.fromFile(file);
descriptor = contentResolver.openAssetFileDescriptor(uri, "r");
if (descriptor == null) {
return false;
} else {
close(descriptor);
}
return true;
} catch (FileNotFoundException e) {
}finally {
close(descriptor);
}
return false;
}
/**
* 关闭
* @param descriptor
*/
private static void close(AssetFileDescriptor descriptor){
if(descriptor != null){
try {
descriptor.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} }

@ -1,7 +1,6 @@
package com.king.app.updater.util; package com.king.app.updater.util;
import android.Manifest; import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.app.AppOpsManager; import android.app.AppOpsManager;
import android.app.NotificationManager; import android.app.NotificationManager;
@ -19,22 +18,24 @@ import androidx.core.app.ActivityCompat;
/** /**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/ */
public enum PermissionUtils { public final class PermissionUtils {
INSTANCE; private static String[] PERMISSIONS_STORAGE = {
private String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE}; Manifest.permission.READ_PHONE_STATE};
private PermissionUtils(){
throw new AssertionError();
}
/** /**
* 校验权限 * 校验权限
* @param activity * @param activity
* @param requestCode * @param requestCode
* @return * @return
*/ */
public boolean verifyReadAndWritePermissions(@NonNull Activity activity, int requestCode){ public static boolean verifyReadAndWritePermissions(@NonNull Activity activity,int requestCode){
int readResult = checkPermission(activity,Manifest.permission.READ_EXTERNAL_STORAGE); int readResult = checkPermission(activity,Manifest.permission.READ_EXTERNAL_STORAGE);
int writeResult = checkPermission(activity,Manifest.permission.WRITE_EXTERNAL_STORAGE); int writeResult = checkPermission(activity,Manifest.permission.WRITE_EXTERNAL_STORAGE);
@ -46,7 +47,7 @@ public enum PermissionUtils {
return true; return true;
} }
public int checkPermission(@NonNull Activity activity,@NonNull String permission){ public static int checkPermission(@NonNull Activity activity,@NonNull String permission){
return ActivityCompat.checkSelfPermission(activity,permission); return ActivityCompat.checkSelfPermission(activity,permission);
} }
@ -54,7 +55,7 @@ public enum PermissionUtils {
* 获取通知权限 * 获取通知权限
* @param context * @param context
*/ */
public boolean isNotificationEnabled(Context context) { public static boolean isNotificationEnabled(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE); NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);

@ -25,10 +25,10 @@ import androidx.annotation.RawRes;
/** /**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/ */
public class SSLSocketFactoryUtils { public final class SSLSocketFactoryUtils {
private SSLSocketFactoryUtils(){ private SSLSocketFactoryUtils(){
throw new AssertionError();
} }
public static SSLSocketFactory createSSLSocketFactory() { public static SSLSocketFactory createSSLSocketFactory() {

@ -30,6 +30,7 @@ dependencies {
//support //support
implementation deps.support.appcompat implementation deps.support.appcompat
implementation deps.support.constraintlayout implementation deps.support.constraintlayout
implementation deps.okhttp
implementation project(':app-updater') implementation project(':app-updater')
implementation project(':app-dialog') implementation project(':app-dialog')

Binary file not shown.

@ -1 +1 @@
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":8,"versionName":"1.0.5-androidx","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] [{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":10,"versionName":"1.0.6-androidx","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]

@ -3,7 +3,6 @@ package com.king.appupdater;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
@ -18,6 +17,7 @@ import com.king.app.updater.UpdateConfig;
import com.king.app.updater.callback.AppUpdateCallback; import com.king.app.updater.callback.AppUpdateCallback;
import com.king.app.updater.callback.UpdateCallback; import com.king.app.updater.callback.UpdateCallback;
import com.king.app.updater.constant.Constants; import com.king.app.updater.constant.Constants;
import com.king.app.updater.http.OkHttpManager;
import com.king.app.updater.util.PermissionUtils; import com.king.app.updater.util.PermissionUtils;
import java.io.File; import java.io.File;
@ -25,6 +25,9 @@ import java.io.File;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
private final Object mLock = new Object(); private final Object mLock = new Object();
@ -43,8 +46,9 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
progressBar = findViewById(R.id.progressBar); progressBar = findViewById(R.id.progressBar);
progressBar.setVisibility(View.INVISIBLE); progressBar.setVisibility(View.INVISIBLE);
progressBar.setMax(100);
PermissionUtils.INSTANCE.verifyReadAndWritePermissions(this,Constants.RE_CODE_STORAGE_PERMISSION); PermissionUtils.verifyReadAndWritePermissions(this,Constants.RE_CODE_STORAGE_PERMISSION);
} }
public Context getContext(){ public Context getContext(){
@ -79,6 +83,7 @@ public class MainActivity extends AppCompatActivity {
config.setUrl(mUrl); config.setUrl(mUrl);
config.addHeader("token","xxxxxx"); config.addHeader("token","xxxxxx");
mAppUpdater = new AppUpdater(getContext(),config) mAppUpdater = new AppUpdater(getContext(),config)
.setHttpManager(OkHttpManager.getInstance())
.setUpdateCallback(new UpdateCallback() { .setUpdateCallback(new UpdateCallback() {
@Override @Override
@ -95,10 +100,10 @@ public class MainActivity extends AppCompatActivity {
} }
@Override @Override
public void onProgress(int progress, int total, boolean isChange) { public void onProgress(long progress, long total, boolean isChange) {
if(isChange){ if(isChange){
progressBar.setMax(total); int currProgress = Math.round(progress * 1.0f / total * 100.0f);
progressBar.setProgress(progress); progressBar.setProgress(currProgress);
} }
} }
@ -135,7 +140,7 @@ public class MainActivity extends AppCompatActivity {
.build(getContext()) .build(getContext())
.setUpdateCallback(new AppUpdateCallback() { .setUpdateCallback(new AppUpdateCallback() {
@Override @Override
public void onProgress(int progress, int total, boolean isChange) { public void onProgress(long progress, long total, boolean isChange) {
} }
@ -215,12 +220,13 @@ public class MainActivity extends AppCompatActivity {
public void onClick(View v) { public void onClick(View v) {
mAppUpdater = new AppUpdater.Builder() mAppUpdater = new AppUpdater.Builder()
.serUrl(mUrl) .serUrl(mUrl)
.setPath(Environment.getExternalStorageDirectory() + "/.AppUpdater") // .setPath(Environment.getExternalStorageDirectory() + "/.AppUpdater")//如果适配Android Q,则Environment.getExternalStorageDirectory()将废弃
.setPath(getExternalFilesDir(Constants.DEFAULT_DIR).getAbsolutePath())//自定义路径
.setVersionCode(BuildConfig.VERSION_CODE)//设置versionCode之后,新版本相同的apk只下载一次,优先取本地缓存。 .setVersionCode(BuildConfig.VERSION_CODE)//设置versionCode之后,新版本相同的apk只下载一次,优先取本地缓存。
.setFilename("AppUpdater1.apk") .setFilename("AppUpdater.apk")
.setVibrate(true) .setVibrate(true)
.build(getContext()); .build(getContext());
mAppUpdater.start(); mAppUpdater.setHttpManager(OkHttpManager.getInstance()).start();
AppDialog.INSTANCE.dismissDialog(); AppDialog.INSTANCE.dismissDialog();
} }
}); });
@ -244,7 +250,7 @@ public class MainActivity extends AppCompatActivity {
.setVibrate(true) .setVibrate(true)
.setSound(true) .setSound(true)
.build(getContext()); .build(getContext());
mAppUpdater.start(); mAppUpdater.setHttpManager(OkHttpManager.getInstance()).start();
AppDialog.INSTANCE.dismissDialogFragment(getSupportFragmentManager()); AppDialog.INSTANCE.dismissDialogFragment(getSupportFragmentManager());
} }
}); });

@ -1,7 +1,7 @@
//App //App
def app_version = [:] def app_version = [:]
app_version.versionCode = 8 //androidx 8 app_version.versionCode = 10 //androidx 10
app_version.versionName = "1.0.5-androidx" app_version.versionName = "1.0.6-androidx"
ext.app_version = app_version ext.app_version = app_version
//build version //build version
@ -23,6 +23,9 @@ versions.junit = "1.1.0"
versions.test = "1.2.0" versions.test = "1.2.0"
versions.runner = "1.2.0" versions.runner = "1.2.0"
versions.espresso = "3.2.0" versions.espresso = "3.2.0"
versions.okhttp = "4.2.2"
ext.versions = versions ext.versions = versions
ext.deps = [:] ext.deps = [:]
@ -41,6 +44,8 @@ test.runner = "androidx.test:runner:$versions.runner"
test.espresso = "androidx.test.espresso:espresso-core:$versions.espresso" test.espresso = "androidx.test.espresso:espresso-core:$versions.espresso"
deps.test = test deps.test = test
//okHttp
deps.okhttp = "com.squareup.okhttp3:okhttp:$versions.okhttp"
ext.deps = deps ext.deps = deps

Loading…
Cancel
Save