Android Studio 实现APP内部更新版本
需求
开发android app时,我们希望不通过应用市场而实现app内部更新。
权限配置
我们需要先在AndroidManifest.xml文件申请权限。
//允许应用程序访问有关网络的信息
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
//允许应用程序打开网络套接字
<uses-permission android:name="android.permission.INTERNET" />
//允许应用程序广泛访问范围存储中的外部存储
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
//允许应用程序写入外部存储
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
//允许应用程序从外部存储读取
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
//允许应用程序请求安装包
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
//允许应用使用类型创建窗口 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,显示在所有其他应用之上
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
//允许应用程序安装包
<uses-permission android:name="android.permission.INSTALL_PACKAGES"
tools:ignore="ProtectedPermissions" />
实现流程
1、版本比对
我们需要通过版本号来确定是否是最新版,是否需要更新。那么就得在数据库建个app版本号表,用来保存app的版本信息,这个作为线上的版本,app本地也有个版本信息,通过这两个版本号对比,来确定版本是否需要升级。
版本号表
id | version_code | version_name | version_des | download_url |
---|---|---|---|---|
1 | 100 | 1.0.0 | 初始版本 | apk下载地址1 |
2 | 101 | 1.0.1 | 1.0.1版本 | apk下载地址2 |
3 | 102 | 1.0.2 | 1.0.2版本 | apk下载地址3 |
版本号表解析
versionCode int //版本号
versionName String // 版本名称
version_des String // 版本描述
download_url String // 版本下载地址---我是把该版本的apk文件扔到阿里的oss上面去
android端发起get请求获取版本信息
URL url = new URL (urlStr);
HttpURLConnection connect = (HttpURLConnection) url.openConnection ();
connect.setConnectTimeout(3000);
connect.setReadTimeout(3000);
connect.setRequestMethod("GET");
connect.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
connect.setDoInput(true);
connect.setDoOutput(false);
connect.connect();
int responseCode = connect.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
throw new IOException("HTTP error code" + responseCode);
}
InputStream input = connect.getInputStream ();
String line = null;
StringBuffer sb = new StringBuffer ();
while ((line = in.readLine ()) != null) {
sb.append (line);
}
result = sb.toString ();
版本号比对代码
// 获取当前版本号
VersionCode = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
2、下载apk文件
通过发起http请求获取到最新的版本号,进行比对,如果线上的versionCode大于本地的versionCode,那就要通过downloadUrl来下载apk文件存放到指定目录。
/**
* 下载新版本应用
*/
private void downloadApp() {
new Thread (new Runnable () {
@Override
public void run() {
URL url = null;
InputStream in = null;
FileOutputStream out = null;
HttpURLConnection conn = null;
try {
url = new URL (downlaodUrl);
conn = (HttpURLConnection) url.openConnection ();
conn.connect ();
long fileLength = conn.getContentLength ();
in = conn.getInputStream ();
File filePath = new File (FILE_PATH);
if (!filePath.exists ()) {
filePath.mkdir ();
}
out = new FileOutputStream (FILE_NAME);
byte[] buffer = new byte[1024];
int len = 0;
long readedLength = 0l;
while ((len = in.read (buffer)) != -1) {
// 用户点击“取消”按钮,下载中断
if (isCancel) {
break;
}
out.write (buffer, 0, len);
readedLength += len;
curProgress = (int) (((float) readedLength / fileLength) * 100);
handler.sendEmptyMessage (UPDATE_TOKEN);
if (readedLength >= fileLength) {
dialog.dismiss ();
// 下载完毕,通知安装
handler.sendEmptyMessage (INSTALL_TOKEN);
break;
}
}
out.flush ();
} catch (Exception e) {
e.printStackTrace ();
} finally {
if (out != null) {
try {
out.close ();
} catch (IOException e) {
e.printStackTrace ();
}
}
if (in != null) {
try {
in.close ();
} catch (IOException e) {
e.printStackTrace ();
}
}
if (conn != null) {
conn.disconnect ();
}
}
}
}).start ();
}
3、安装新版本的apk (兼容android 6.0、7.0、8.0)
这里我也踩了很多坑,例如:
android6.0:通过DownloadManager 获取到的Uri不一样。与android5.0有差异;
android7.0:对文件的访问权限作出了修改,不能在使用file://格式的Uri 访问文件 ,Android 7.0提供 FileProvider,应该使用这个来获取apk地址,然后安装apk;
android8.0:未知来源的应用权限,也就是说权限不去申请的话,不允许安装软件。
private static final String FILE_PATH = Environment.getExternalStorageDirectory() + FILE_SEPARATOR + "xxx" + FILE_SEPARATOR;
private static final String FILE_NAME = FILE_PATH + "xxx.apk";
/**
* 安装新版本应用
*/
private void installApp() {
File appFile = new File (FILE_NAME);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (!appFile.exists ()) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
String ss = "com.xxx.xxx" + ".fileprovider";
// xxx.xxx为自己的项目包名
Uri contentUri = FileProvider.getUriForFile(context.getApplicationContext(), ss, appFile);
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
} catch (Exception e) {
e.printStackTrace();
}
} else {
intent.setDataAndType(Uri.fromFile(appFile), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
try {
context.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
在AndroidManifest.xml中增加如下文件
<application
...
android:requestLegacyExternalStorage="true"
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.mes_app.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths"
/>
</provider>
</application>
在资源文件目录下新增xml文件夹,新建filepaths.xml文件
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
发布新版本时,需要在build.gradle文件(module)中修改版本信息
完结。