Android 实现APP内应用更新功能(支持Android7.0以上)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 实现APP内应用更新功能(支持Android7.0以上)相关的知识,希望对你有一定的参考价值。

前言:

实现APP内应用更新功能思路,这里我给大家具体说明以下。

思路:

  1. 通过API接口获取服务器端版本号,与本地应用版本号作比较。
  2. 如果本地版本号小于服务器端版本号,则弹出一个对话框,提示用户更新APP,用户可以点击更新按钮,在APP内直接更新,安装。用户也可以选择去应用商店更新。
  3. 如果用户点击更新按钮,通过给这个按钮设置监听事件,开启Service服务,在后台服务中下载apk。
  4. 在Service服务中,通过上一个Activity传来的值,获取到最新APK的下载地址,通过IO流的知识,把最新APK下载到手机SD卡的指定路径。
  5. 最后通过调用系统安装界面,实现APK的安装。

接下来我们通过代码实现以上这些步骤

版本号对比,设置对话框,开启服务

//获取本地版本号
String vername = APKVersionCodeUtils.getVerName(this);
//获取服务端版本号大小
final AppUpadeLog newapplog = new Gson().fromJson(resp, AppUpadeLog.class);
//判断版本号的大小,大于网络上的版本号不提示更新
int verflag = compareVersion(vername, newapplog.AppVer);
if (verflag == -1)
//对话框弹出
//弹出提醒对话框,提示用户登录成功
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("更新提示");
builder.setMessage("应用已有新版本发布,请前往应用商城进行更新,或者直接点击更新按钮,进行更新!");
builder.setPositiveButton("更新", new DialogInterface.OnClickListener()
@Override
public void onClick(DialogInterface dialog, int which)
//启动后台服务,下载APK
Intent intent = new Intent(MainActivity.this, UpdateService.class);
intent.putExtra("FileSrc", newapplog.FileSrc);
intent.putExtra("AppName", newapplog.AppName);
startService(intent);

);
builder.setNeutralButton("下次提醒", null);
AlertDialog alert = builder.create();
alert.show();

下载APK,设置通知栏,安装APK

public class UpdateService extends Service 
private static final String TAG = "UpdateService";
private String appUrl; //应用升级地址
private String appName; //应用名称
private String updateFile; //最新APK的路径
private NotificationManager notificationManager; //声明系统的通知管理器
//定义notification实用的ID
private static final String MESSAGES_CHANNEL = "messages";
private HttpURLConnection connection;
private NotificationCompat.Builder builder;
private final int NEW_MESSAGE_ID = 0;
private static int HANDLER_LABEL = 0X00; //设置一个常量值来标记信息

Handler handler = new Handler(new Handler.Callback()
@Override
public boolean handleMessage(@NonNull Message msg)
if (msg.what == HANDLER_LABEL)
if (!TextUtils.isEmpty(updateFile))
installAPK();


return false;

);

//安装
private void installAPK()
try
File cacheDir = new File(getApplicationContext().getExternalFilesDir(null).getPath());
File file = new File(cacheDir, appName + ".apk");

//使用隐式意图开启安装APK的Activity
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//添加这一句表示对目标应用临时授权该Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) //android7.0 API24之后获取uri要用contentProvider
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri apkUri = FileProvider.getUriForFile(this,
"com.run.xiao.FileProvider", file);

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
Log.e(TAG, "installAPK: " + apkUri);
else
Uri apkUri = Uri.parse("file://" + updateFile);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");

startActivity(intent);
notificationManager.cancel(0); //出现安装APK页面,取消通知
//结束服务
stopSelf();
catch (Exception e)
e.printStackTrace();



@Override
public int onStartCommand(Intent intent, int flags, int startId)
//获取上一个页面传来的包裹
Bundle bundle = intent.getExtras();
appUrl = bundle.getString("FileSrc");
appName = bundle.getString("AppName");
downloadUpdateFile(appUrl);
return super.onStartCommand(intent, flags, startId);


@Nullable
@Override
public IBinder onBind(Intent intent)
return null;



/**
* 下载更新APK文件
*
* @param appUrl
* @return
*/
private int downloadUpdateFile(final String appUrl)
try
//创建通知渠道
createMessageNotificationChannel();
final int NEW_MESSAGE_ID = 0;
builder = new NotificationCompat.Builder(this, MESSAGES_CHANNEL);

builder.setSmallIcon(R.mipmap.lisen_lancher) //小图标
.setContentTitle("正在下载") //标题
.setContentText("正在更新APP") //描述性文本
.setAutoCancel(true) //点击通知后关闭通知
.setOnlyAlertOnce(true); //设置提示音只响一次

//StrictMode修改默认的策略
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);

//设置进度条操作
URL url = new URL(appUrl);
//打开和URL之间的连接
connection = (HttpURLConnection) url.openConnection();
//设置网络请求
connection.setRequestMethod("GET");
//开始读取服务器端数据,到了指定时间还没有读到数据,则报超时异常
connection.setReadTimeout(50000);
//要取得长度,要求http请求不要gzip压缩
connection.setRequestProperty("Accept-Encoding", "identity"); // 添加这行代码

//建立实际的连接
connection.connect();
new Thread(new Runnable()
@Override
public void run()
int total_length = 0;
BufferedOutputStream bos = null;
BufferedInputStream bis = null;
try
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK)
InputStream is = connection.getInputStream();
bis = new BufferedInputStream(is);
// File cacheDir = getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
//创建文件路径
File cacheDir = new File(getApplicationContext().getExternalFilesDir(null).getPath());
if (!cacheDir.exists())
cacheDir.mkdirs();

//创建文件夹
//通过输出流,下载到指定的本地文件目录
File file = new File(cacheDir, appName + ".apk");
if (!file.exists())
file.createNewFile();

//检查SD卡的状态是否可用
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
Toast.makeText(UpdateService.this, "SD卡不可用~", Toast.LENGTH_SHORT).show();


FileOutputStream fos = new FileOutputStream(file);
bos = new BufferedOutputStream(fos);

//获取文件流大小,更新进度
byte[] buffer = new byte[1024 * 8];
int len;
int pro1 = 0;
long file_length = connection.getContentLength();
while ((len = bis.read(buffer)) != -1)
bos.write(buffer, 0, len);
total_length += len;
if (file_length > 0)
pro1 = (int) ((total_length / (float) file_length) * 100);//进度条传递进度
builder.setProgress(100, pro1, false);
builder.setContentText("下载" + pro1 + "%");
notificationManager.notify(NEW_MESSAGE_ID, builder.build());


builder.setStyle(new NotificationCompat.BigTextStyle().bigText("下载完成,点击安装")); //显示多行文本
notificationManager.notify(NEW_MESSAGE_ID, builder.build());

updateFile = file.getAbsolutePath();
Log.e(TAG, "下载路径:" + updateFile);
handler.sendEmptyMessage(HANDLER_LABEL);


catch (Exception e)
e.printStackTrace();
finally
//关闭资源,先关闭外层流,在关闭内层流
try
if (bis != null)
bis.close();

catch (IOException e)
e.printStackTrace();

try
if (bos != null)
bos.close();

catch (IOException e)
e.printStackTrace();



).start();
catch (Exception e)
e.printStackTrace();

return NEW_MESSAGE_ID;



//创建通知渠道
private void createMessageNotificationChannel()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
CharSequence name = this.getString(R.string.app_name);
NotificationChannel channel = new NotificationChannel(
MESSAGES_CHANNEL,
name,
NotificationManager.IMPORTANCE_HIGH
);
notificationManager = this.getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);


下载完成之后,不需要用户点击通知栏安装,因为当下载完成后,会发送一条消息给handler,再由handler,调用系统安装界面,进行最新APK的安装。

在清单文件AndroidManifest.xml中注册服务,解决一个应用提供自身文件给其它应用使用时,如果给出一个file://格式的URI的话,应用会抛出FileUriExposedException

<service android:name=".service.UpdateService"
android:enabled="true"/>
<!-- 解决Android 7.0 以上报错FileUriExposedException -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.soundproject.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

​android:resource="@xml/file_paths"​​​ 这个资源是在res目录下,创建xml包,之后创建file_paths.xml
file_paths.xml代码如下:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path
name="files"
path="."/>
</paths>

这样就完成了APP内应用更新功能~ 如果有不当之处,可以在评论区指出,一起进步。


以上是关于Android 实现APP内应用更新功能(支持Android7.0以上)的主要内容,如果未能解决你的问题,请参考以下文章

Fuchsia 确认支持安卓 APP;悼念杰出的内核开发者李少华

如何使用Android蓝牙开发

Android 测试,如何从Pc端获取App日志信息?

Android静默安装及开机自启的简单实现

Android实现App版本自动更新

用Wordpress构建App更新和反馈平台(上)