okhttpUtils是okhttp的一个工具包,首先在app的build.gradle中添加以下内容:
implementation 'com.squareup.okhttp3:okhttp:4.3.0'
implementation 'com.squareup.okio:okio:2.4.3'
implementation 'com.zhy:okhttputils:2.6.2'
//实例中使用了Toast和Dialog的第三方框架,如需使用请添加以下内容
implementation 'com.github.GrenderG:Toasty:1.3.0'
implementation 'com.afollestad.material-dialogs:core:0.9.6.0'
首先实例流程为:①获取当前APP版本号 ②请求检查更新接口 ③若有新版本则提示下载 ④若点击下载则通过异步请求将服务器上的APK文件下载完成后自动跳转安装界面
注意由于谷歌要求针对高版本安卓开发,低版本不允许上架, 所以当前实例并未考虑低版本安卓,测试机为安卓7-10
首先在安卓7以上存在一个问题,就是普通file格式的uri无法直接使用,需通过共享目录生成content格式的uri,所以我们需要在androidmanifest.xml的application中添加以下内容:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.packagename.fileProvider" //改为你的包名.fileProvider即可
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
然后在res目录中新建一个xml目录,如果已有则不需要新建,在xml中新建一个files_paths.xml并写入以下内容:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!-- 物理路径为Context.getFilesDir() + /files/... -->
<files-path path="files" name="files" />
<!-- 物理路径为Context.getCacheDir() + /files/... -->
<cache-path path="files" name="cache" />
<!-- 物理路径为Environment.getExternalStorageDirectory() + /files/... -->
<external-path path="files" name="external" />
<!-- 物理路径为Context.getExternalFilesDir(String) + /files/... -->
<external-files-path path="files" name="externalfiles"/>
<!-- 物理路径为Context.getExternalCacheDir() + /files/... -->
<external-cache-path path="files" name="externalcache"/>
<!-- 物理路径为`Context.getExternalMediaDirs() + /files/..., 要求API21+ -->
<external-media-path name="externalmedia" path="files" />
<root-path name="root_path" path="."/>
</paths>
这样我们的共享目录就配置好了,在代码中直接使用即可,接下来我将直接贴出检查更新的完整点击事件供大家参考(重要部分在代码中注释标出):
check_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
long versionCode = 1000;
//获取当前APP的版本号
try {
PackageInfo packageInfo = getContext().getApplicationContext()
.getPackageManager()
.getPackageInfo(getContext().getPackageName(), 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
versionCode = packageInfo.getLongVersionCode();
} else {
versionCode = packageInfo.versionCode;
}
} catch (PackageManager.NameNotFoundException e) {
Log.e("", e.getMessage());
}
//请求地址+等待提示框
String host = new Defines().SERVER_HOST+"checkUpdate";
final MaterialDialog waitForDialog = new MaterialDialog.Builder(getActivity())
.content("请稍等...")
.progress(true,-1)//等待图标 true=圆形icon false=进度条
.cancelable(false)//不会被取消 (包括返回键和外部点击都无法取消)
.build();
waitForDialog.show();
//调用检查更新接口,传入当前版本号
OkHttpUtils
.post()
.url(host)
.addParams("version_code",""+versionCode)
.build()
.execute(new StringCallback() {
@Override
public void onError(Call call, Exception e, int id) {
waitForDialog.dismiss();
Toasty.error(getActivity(),"服务器请求失败", Toast.LENGTH_SHORT).show();
}
@Override
public void onResponse(String response, int id) {
waitForDialog.dismiss();
try {
JSONObject jsonObject = new JSONObject(response);
int code = jsonObject.getInt("code");
//code=200为最新版本无需更新 code=250为有新版本可供下载
if (code==200) {
Toasty.success(getActivity(),jsonObject.getString("msg"),Toast.LENGTH_SHORT).show();
} else if(code==250) {
final JSONObject data = jsonObject.getJSONObject("data");
//将后台编写的更新日志的<br>打散重组为\n以在安卓可以换行提示
String log = "";
String[] chrstr = data.getString("update_log").split("<br>");
for(int i=0;i<chrstr.length;i++)
log = log + chrstr[i]+"\n";
MaterialDialog dialog = new MaterialDialog.Builder(getContext())
.title(jsonObject.getString("msg"))
.content(log)
.positiveText("下载更新")
.negativeText("以后再说")
.cancelable(true)
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
//获取APP内部存储的chche文件夹路径
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
cachePath = getActivity().getExternalCacheDir().getPath();
} else {
cachePath = getActivity().getFilesDir().getAbsolutePath();
}
//新建一个下载进度条并允许点击其他区域关闭
final ProgressDialog progressDialog = new ProgressDialog(getContext());
progressDialog.setTitle("正在下载,请耐心等待...");
progressDialog.setMessage("点击其他区域或返回以转入后台下载");
progressDialog.setCancelable(true);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.show();
try {
//通过okhttpUtils下载文件
OkHttpUtils.get()
.url(data.getString("position"))
.build()
.execute(new FileCallBack(cachePath,"fingerdorm.apk") {
@Override
public void onError(Call call, Exception e, int id) {
Toasty.error(getActivity(),"下载失败",Toast.LENGTH_SHORT).show();
}
//inpogress回调为更新进度条
@Override
public void inProgress(float progress, long total, int id) {
super.inProgress(progress, total, id);
progressDialog.setProgress((int) (100 * progress));
}
//下载完成,处理打开安装
@Override
public void onResponse(File downloadFile, int id) {
Toasty.success(getActivity(),"下载完成,即将启动安装",Toast.LENGTH_LONG).show();
progressDialog.hide();
progressDialog.dismiss();
//开启子线程,延迟三秒后执行安装
new Thread(new Runnable()
{
@Override
public void run()
{
try {
sleep(3000);
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
cachePath = getContext().getExternalCacheDir().getPath();
} else {
cachePath = getContext().getFilesDir().getAbsolutePath();
}
File uriFile = new File(cachePath +"/fingerdorm.apk");
//这里获取uri的第二个参数一定要和androidmanifest中配置的的对应
Uri uri = getUriForFile(getContext(), "com.packaname.fileProvider", uriFile);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//打开APK执行的TYPE为"application/vnd.android.package-archive"
intent.setDataAndType(uri,"application/vnd.android.package-archive");
getContext().startActivity(intent);
} catch (Exception e) {
Looper.prepare();
Toasty.error(getActivity(), "自动安装失败,请手动安装", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}
}).start();
}
});
} catch (JSONException e) {
Toasty.error(getActivity(), "服务器数据解析失败", Toast.LENGTH_SHORT).show();
}
}
})
.onNegative(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
}
})
.build();
dialog.show();
}else{
Toasty.error(getActivity(),jsonObject.getString("msg"), Toast.LENGTH_SHORT).show();
}
} catch (JSONException e) {
e.printStackTrace();
Toasty.warning(getActivity(),"检查更新失败",Toast.LENGTH_LONG).show();
}
}
});
}
});