Android之APP更新(通过接口更新)
文章目录
- 前言
- 一、效果图
- 二、实现步骤
- 1.AndroidManifest权限申请
- 2.activity实现
- 3.有版本更新弹框UpdateappUtilDialog
- 4.下载弹框DownloadAppUtils
- 5.弹框背景图
- 总结
前言
对于做Android的朋友来说,APP更新功能再常见不过了,因为平台更新审核时间较长,所以现在很多都会通过接口更新,这里就记录一下吧,欢迎各位讨论。
一、效果图
二、实现步骤
1.AndroidManifest权限申请
<!--外置存储卡写入权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--请求安装APK的权限-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!--读取删除SD卡权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!--修改删除SD卡权限-->
<uses-permission
android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
//application标签下加如下代码
<!-- 安卓7.0安装时需要,拍照需要的临时权限 -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="这里是applicationId也就是包名.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
//这里的provider_paths文件放在xml文件下,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!--拍照需要的路径-->
<external-path
name="comxf.activity.provider.download"
path="." />
<external-files-path name="extfiles" path="autoupdatecache/" />
<external-cache-path name="extcachs" path="autoupdatecache/" />
<cache-path name="intcachs" path="autoupdatecache/" />
<paths>
<external-path path="" name="download"/>
</paths>
</paths>
2.activity实现
1.通过接口获取Javabean对象
public Integer id;
public String version_name;//App版本名
public Integer version_code;//版本编号
public String content;//版本更新内容
public String is_must_update;//是否需要强更新(0:否,1:是)
public String file_url;//app包的下载地址
@Override
public String toString() {
return "VersionUpgradeBean{" +
"id=" + id +
", version_name='" + version_name + '\'' +
", version_code=" + version_code +
", content='" + content + '\'' +
", is_must_update='" + is_must_update + '\'' +
", file_url='" + file_url + '\'' +
'}';
}
2.获取到版本号后判断是否高于当前版本
if (versionbean.version_code > MyApplication.getVersionCode(this@SetUpActivity)) {
val myDialog = UpdateappUtilDialog()
myDialog.initDialog(this@SetUpActivity, versionbean)
myDialog.buttonClickEvent(object : UpdateappUtilDialog.DialogButtonClick {
override fun cilckComfirmButton(view: View?) {
myDialog.Dismiss()
//6.0才用动态权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(
this@SetUpActivity,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
// 申请读写内存卡内容的权限
ActivityCompat.requestPermissions(
this@SetUpActivity, arrayOf(
Manifest.permission.`WRITE_EXTERNAL_STORAGE`
), 123
)
} else {
//授权成功
if (FileUtils.createOrExistsDir(NetConst.baseFileName)) {
val mUpdateManger = DownloadAppUtils(
this@SetUpActivity, versionbean.file_url, versionbean
)
mUpdateManger.checkUpdateInfo()
}
}
} else {
//授权成功
if (FileUtils.createOrExistsDir(NetConst.baseFileName)) {
val mUpdateManger = DownloadAppUtils(
this@SetUpActivity, versionbean.file_url, versionbean
)
mUpdateManger.checkUpdateInfo()
}
}
}
override fun cilckCancleButton(view: View?) {
//点击取消按钮
myDialog.Dismiss()
}
})
}
3.用户权限结果处理
/**
* todo 对用户权限授予结果处理
*
* @param requestCode 权限要求码,即我们申请权限时传入的常量 如: TAKE_PHOTO_PERMISSION_REQUEST_CODE
* @param permissions 保存权限名称的 String 数组,可以同时申请一个以上的权限
* @param grantResults 每一个申请的权限的用户处理结果数组(是否授权)
*/
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String?>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
123 -> {
//授权成功
if (FileUtils.createOrExistsDir(NetConst.baseFileName)) {
val mUpdateManger = DownloadAppUtils(
this@SetUpActivity, versionbean.file_url, versionbean
)
mUpdateManger.checkUpdateInfo()
}
}
}
}
3.有版本更新弹框UpdateappUtilDialog
1.util代码
/**
* Created by :caoliulang
* ❤
* Creation time :2025/2/24
* ❤
* Function :更新app的Dialog
*/
public class UpdateappUtilDialog {
private TextView secondBtn, textvs;
private ImageView tv_cancel;
private AlertDialog alertDialog;
private DialogButtonClick mClick;
private TextView tv_desc;
public interface DialogButtonClick {
void cilckComfirmButton(View view);
void cilckCancleButton(View view);
}
public void buttonClickEvent(DialogButtonClick bc) {
if (bc != null) {
mClick = bc;
cilckEvent();
}
}
public void initDialog(Context context, VersionUpgradeBean upbean) {
alertDialog = new AlertDialog.Builder(context).create();
alertDialog.show();
alertDialog.setCancelable(false);//调用这个方法时,按对话框以外的地方不起作用。返回键 不作用
// alertDialog.setCanceledOnTouchOutside(false);//调用这个方法时,按对话框以外的地方不起作用。返回键 有作用
Window window = alertDialog.getWindow();
window.setContentView(R.layout.fragment_update_app);
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
tv_desc = window.findViewById(R.id.tv_desc);
tv_desc.setText(upbean.content);
secondBtn = window.findViewById(R.id.tv_ok);
textvs = window.findViewById(R.id.textvs);
textvs.setText(upbean.version_name);
tv_cancel = window.findViewById(R.id.tv_cancel);
//1不强制更新 2 强制更新
// if(upbean.getUpdateType().equals("2")){
// firstBtn.setVisibility(View.GONE);
// }
// //不关闭弹窗也能点击外部事件
// alertDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
String isqg = upbean.is_must_update + "";
if (isqg.equals("true")) {
tv_cancel.setVisibility(View.GONE);
} else {
tv_cancel.setVisibility(View.VISIBLE);
}
}
public void cilckEvent() {
// if (firstBtn != null) {
// firstBtn.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// alertDialog.dismiss();
// mClick.cilckCancleButton(v);
// }
// });
secondBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// alertDialog.dismiss();
mClick.cilckComfirmButton(v);
}
});
tv_cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mClick.cilckCancleButton(view);
}
});
// }
}
public void Dismiss() {
if (null != alertDialog) {
alertDialog.dismiss();
}
}
}
2.xml布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#01000000"
android:gravity="center"
android:orientation="vertical">
<RelativeLayout
android:layout_width="301dp"
android:layout_height="400dp"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_top"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/lib_update_app_top_bg" />
<TextView
android:id="@+id/textname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="55dp"
android:text="@string/Newversionfound"
android:textColor="#2e2e2e"
android:textSize="18dp"
android:textStyle="bold" />
<TextView
android:id="@+id/textvs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textname"
android:layout_marginLeft="16dp"
android:layout_marginTop="5dp"
android:text="1.0.0"
android:textColor="#2e2e2e"
android:textSize="16dp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="210dp"
android:layout_marginTop="180dp"
android:orientation="vertical">
<!--这个地方需要设置可以滚动-->
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingTop="18dp"
android:paddingBottom="5dp"
android:scrollbars="none">
<TextView
android:id="@+id/tv_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:lineSpacingExtra="5dp"
android:text="1.已知bug修复;\n2.新功能增加;\n3.性能优化;"
android:textColor="@android:color/black"
android:textSize="14dp" />
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="20dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_ok"
android:layout_width="match_parent"
android:layout_height="44dp"
android:background="@drawable/jianbianlanse"
android:gravity="center"
android:text="@string/UPDATENOW"
android:textColor="#ffffff"
android:textSize="18dp" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
<ImageView
android:id="@+id/tv_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:src="@mipmap/ico_sjgb" />
</LinearLayout>
4.下载弹框DownloadAppUtils
1.util代码如下
**
* Created by :caoliulang
* ❤
* Creation time :2024/2/24
* ❤
* Function :APP更新下载
*/
public class DownloadAppUtils {
// 应用程序Context
public static Activity mContext;
private static final String savePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "tdzr/Download/";// 保存apk的文件夹
///storage/emulated/0/tdzr/Download/aidigital1.0.4.apk
private static final String saveFileName = savePath + "aidigital" + MyApplication.versionname + ".apk";
// 进度条与通知UI刷新的handler和msg常量
private ProgressBar mProgress;
private TextView text_progress, textvs;
private static final int DOWN_UPDATE = 1;
private static final int DOWN_OVER = 2;
private static final int INT_NOT = 9;
private String downlogdurl;//下载地址
private int progress = 0;// 当前进度
private Thread downLoadThread; // 下载线程
private boolean interceptFlag = false;// 用户取消下载
private AlertDialog dialog;
private VersionUpgradeBean upbean;
// 通知处理刷新界面的handler
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@SuppressLint("HandlerLeak")
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DOWN_UPDATE:
mProgress.setProgress(progress); //设置第一进度
text_progress.setText(progress + "%");
break;
case DOWN_OVER:
// //1不强制更新 2 强制更新
// if(upbean.getUpdateType().equals("1")){
// dialog.dismiss();
// }
dialog.dismiss();
installApk();
break;
case INT_NOT:
dialog.dismiss();
FileUtils.deleteFile(saveFileName);//删除文件
ToastUtils.showToast(mContext.getResources().getString(R.string.Networkconnectionfailed));
break;
}
super.handleMessage(msg);
}
};
public DownloadAppUtils(Activity context, String downlogdurl, VersionUpgradeBean upbean) {
this.mContext = context;
this.downlogdurl = downlogdurl;
this.upbean = upbean;
System.out.println("过来了--1");
}
// 显示更新程序对话框,供主程序调用
public void checkUpdateInfo() {
//判断本地是否存在已下载的apk(有bug)
// if (FileUtils.isFileExists(saveFileName) == true) {
// //安装
// installApk();
// } else {
// //下载
// showDownloadDialog();
// }
//判断本地是否存在已下载的apk
if (FileUtils.isFileExists(saveFileName) == true) {
//删除
DeletFile.delete(savePath);
}
//下载
showDownloadDialog();
}
//弹出更新进度条
private void showDownloadDialog() {
dialog = new AlertDialog.Builder(mContext).create();
dialog.show();
dialog.setCancelable(false);
View viewt = View.inflate(mContext, R.layout.xiazai_two_dialog, null);
Window window = dialog.getWindow();
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
dialog.setContentView(viewt);
mProgress = viewt.findViewById(R.id.upprogressBar);
text_progress = viewt.findViewById(R.id.text_progress);
text_progress.setText("0%");
textvs = viewt.findViewById(R.id.textvs);
textvs.setText(upbean.version_name);
ImageView tv_cancel = viewt.findViewById(R.id.tv_cancel);
String isqg = upbean.is_must_update + "";
if (isqg.equals("true")) {
tv_cancel.setVisibility(View.GONE);
} else {
tv_cancel.setVisibility(View.VISIBLE);
}
tv_cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FileUtils.deleteFile(saveFileName);//删除文件
dialog.dismiss();
interceptFlag = true;
}
});
downloadApk();
}
//安装apk
private static void installApk() {
File apkfile = new File(saveFileName);
if (!apkfile.exists()) {
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
//版本在7.0以上是不能直接通过uri访问的
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// 由于没有在Activity环境下启动Activity,设置下面的标签
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致 参数3 共享的文件
Uri apkUri = FileProvider.getUriForFile(mContext, "这里是applicationId也就是包名.fileprovider", apkfile);
//添加这一句表示对目标应用临时授权该Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(new File(saveFileName)),
"application/vnd.android.package-archive");
}
mContext.startActivity(intent);
}
//下载apk
private void downloadApk() {
System.out.println("url打印:" + downlogdurl);
downLoadThread = new Thread(mdownApkRunnable);
downLoadThread.start();
}
private Runnable mdownApkRunnable = new Runnable() {
@Override
public void run() {
System.out.println("打印路径:" + saveFileName);
URL url;
try {
url = new URL(downlogdurl);
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
conn.setConnectTimeout(15000); //设置连接超时为15s
conn.setReadTimeout(15000); //读取数据超时也是15s
int code = conn.getResponseCode();
System.out.println("code打印:" + code);
if (code == 200) {
conn.connect();
int length = conn.getContentLength();
InputStream ins = conn.getInputStream();
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
Toast.makeText(mContext, "SD卡不可用~", Toast.LENGTH_SHORT).show();
return;
}
File file = new File(savePath);
if (!file.exists()) {
file.mkdir();
}
String apkFile = saveFileName;
File ApkFile = new File(apkFile);
if (!ApkFile.exists()) {
ApkFile.createNewFile();
}
FileOutputStream outStream = new FileOutputStream(ApkFile);
int count = 0;
byte buf[] = new byte[1024];
do {
int numread = ins.read(buf);
count += numread;
int s = (int) (((float) count / length) * 100);
if (progress != s) {
progress = (int) (((float) count / length) * 100);
// 下载进度
mHandler.sendEmptyMessage(DOWN_UPDATE);
}
if (numread <= 0) {
// 下载完成通知安装
mHandler.sendEmptyMessage(DOWN_OVER);
break;
}
outStream.write(buf, 0, numread);
} while (!interceptFlag);// 点击取消停止下载
outStream.close();
ins.close();
} else {
mHandler.sendEmptyMessage(INT_NOT);
}
} catch (Exception e) {
//这里报错因为路径不对,正确路径storage/emulated/0/tdzr/Download/aidigital1.0.4.apk
System.out.println("code打印:---" + e.toString());
mHandler.sendEmptyMessage(INT_NOT);
}
}
};
2.下载弹框xml布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#01000000"
android:gravity="center"
android:orientation="vertical">
<RelativeLayout
android:layout_width="301dp"
android:layout_height="400dp"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/topimg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/lib_update_app_top_bg" />
<TextView
android:id="@+id/textname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="55dp"
android:text="@string/Newversionfound"
android:textColor="#2E2E2E"
android:textSize="18dp"
android:textStyle="bold" />
<TextView
android:id="@+id/textvs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textname"
android:layout_marginLeft="16dp"
android:layout_marginTop="5dp"
android:text="1.0.0"
android:textColor="#2E2E2E"
android:textSize="16dp"
android:textStyle="bold" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="250dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/text_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="0%"
android:textColor="#2e2e2e"
android:textSize="16dp"
android:textStyle="bold" />
<ProgressBar
android:id="@+id/upprogressBar"
style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="18dp"
android:layout_marginStart="15dp"
android:layout_marginTop="15dp"
android:layout_marginEnd="15dp"
android:layout_marginBottom="15dp"
android:max="100"
android:progress="0"
android:progressDrawable="@drawable/progressbar_h" />
</LinearLayout>
</RelativeLayout>
<ImageView
android:id="@+id/tv_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:src="@mipmap/ico_sjgb" />
</LinearLayout>
3.progressbar_h(在drawable下新建文件)
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@android:id/background"
android:gravity="center_vertical|fill_horizontal">
<shape android:shape="rectangle">
<size android:height="20dp" />
<solid android:color="#E4E4E4" />
<corners android:radius="10dp" />
</shape>
</item>
<!--第二进度条-->
<item
android:id="@android:id/secondaryProgress"
android:gravity="center_vertical|fill_horizontal">
<scale android:scaleWidth="100%">
<shape android:shape="rectangle">
<size android:height="20dp" />
<solid android:color="#E4E4E4" />
<corners android:radius="10dp" />
</shape>
</scale>
</item>
<!--第一进度条-->
<item
android:id="@android:id/progress"
android:gravity="center_vertical|fill_horizontal">
<scale android:scaleWidth="100%">
<shape android:shape="rectangle">
<size android:height="20dp" />
<solid android:color="#217FFD" />
<corners android:radius="10dp" />
</shape>
</scale>
</item>
</layer-list>
5.弹框背景图
总结
其实这功能挺简单的,就是里面细节太多,首先AndroidManifest加权限,还要加provider,provider里面记得更改包名,下载util里面的报名也是一样。其次判断版本是否更新,最后更新下载安装即可,得吃。