当谈到在Android应用中实现后台更新并安装时,使用Service是一种强大的方法。通过Service,您可以在应用处于后台或者甚至是关闭状态时执行更新操作,这为用户提供了无缝的体验,同时也确保了应用始终保持最新版本。
-
实现后台更新:使用Service可以在后台执行更新任务,而不会打断用户正在进行的操作。这种无感知的更新方式可以提升用户体验,避免了在更新时弹出窗口或者干扰用户的使用。
-
灵活的更新策略:通过Service,您可以灵活地控制更新策略。例如,可以在应用启动时检查更新,或者通过定时任务在后台自动检查更新,以确保用户始终使用最新版本的应用程序。
-
安全更新:借助Service,您可以确保应用更新的安全性。通过使用加密技术或者验证机制,您可以防止恶意软件篡改应用更新,从而保护用户的设备和数据安全。
-
无需用户干预的安装:使用Service进行更新后,应用可以自动下载更新文件并完成安装,无需用户手动干预。这种自动化的更新过程节省了用户的时间和精力,提高了更新的便捷性。
-
用户通知和反馈:在更新过程中,您可以利用Service发送通知,向用户展示更新进度或者更新完成的提示。此外,还可以通过Service收集用户反馈或者更新日志,以改进后续版本的更新策略。
-
话不多说先上图:
-
-
要实现这样的效果总体来说还是比较简单的,代码如下:
MainActivity.java
public class MainActivity extends AppCompatActivity {
public static String title="新版本2.0"
,content="测试内容"
,url="http://files.diablos.cn/2024/03/10/18fbba9e10ee43acb432bfcb19044c13.jpg";
// 请求权限的请求代码
private static final int PERMISSION_REQUEST_CODE = 100;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkAndRequestPermissions();
Button button=findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog();
}
});
}
private void dialog(){
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(title);
builder.setMessage(content);
builder.setPositiveButton("更新", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// 当用户点击对话框上的确定按钮时执行的操作
//启动service后台服务
Intent serviceIntent = new Intent(MainActivity.this, MyService.class);
startService(serviceIntent);
}
});
builder.setNegativeButton("忽略", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
// 检查并请求所需权限
private void checkAndRequestPermissions() {
// 检查通知权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!NotificationManagerCompat.from(this).areNotificationsEnabled()) {
// 如果通知权限未启用,引导用户到设置页面启用
Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
startActivity(intent);
}
}
}
// 处理权限请求的结果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限请求成功
Toast.makeText(this, "Permission granted.", Toast.LENGTH_SHORT).show();
} else {
// 权限请求失败
Toast.makeText(this, "Permission denied.", Toast.LENGTH_SHORT).show();
}
}
}
}
下载类 Download.java
public class Download {
// 下载回调接口
ondown dow;
// 下载状态信息
String ruest;
// 文件总长度和当前已下载长度
int length=0, len=0;
// 下载回调接口定义
public static interface ondown {
// 下载中回调方法,参数为当前已下载长度和文件总长度
public void downing(int len, int oklen);
// 下载完成回调方法,参数为下载状态信息
public void downok(String ruest);
}
// 设置下载回调接口
public void setondown(ondown dow) {
this.dow = dow;
}
// 下载文件方法
public void dowmFile(final String URL, final String path) {
new Thread() {
public void run() {
String url=URL;
try {
// 创建目标文件路径
File ff=new File(path);
if (!ff.getParentFile().exists()) {
ff.getParentFile().mkdirs();
}
// 对URL进行编码处理
String rgx="(?<==)(.|\n)+?(?=&|$)";
Matcher su= Pattern.compile(rgx).matcher(url);
while (su.find()) {
String o=su.group();
url = url.replace(o, URLEncoder.encode(o));
}
String[] one={"{","}"};
for (String two:one) {
url = url.replace(two, URLEncoder.encode(two));
}
// 打开URL连接并获取输入流
URL UR=new URL(url);
URLConnection op=UR.openConnection();
InputStream input=op.getInputStream();
byte[] b=new byte[1024 * 1024];
int le=0;
// 获取文件总长度
length = op.getContentLength();
len = 0;
// 创建输出流,写入文件
FileOutputStream hj=new FileOutputStream(path);
while ((le = input.read(b)) != -1) {
len += le;
// 更新下载进度
if (dow != null) {
Message me=new Message();
me.what = 0;
hh.sendMessage(me);
}
hj.write(b, 0, le);
}
input.close();
hj.flush();
hj.close();
ruest = "下载完成";
} catch (Exception e) {
ruest = e.toString();
}
// 发送下载完成消息
Message me=new Message();
me.what = 1;
hh.sendMessage(me);
}
}.start();
}
// 处理下载进度和下载完成消息的Handler
Handler hh=new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
// 调用回调接口,通知下载进度
dow.downing(length, len);
break;
case 1:
// 调用回调接口,通知下载完成
dow.downok(ruest);
break;
}
}
};
}
然后最重要的部分MyService.java
public class MyService extends Service {
private static final String TAG = "MyService";
private static final int notificationid = 100001;
private Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
ifapk();//判断apk是否已经下载
}
};
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "Service created");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "Service started");
// 在示例中,仅模拟任务执行
new Thread() {
@Override
public void run() {
super.run();
Message msg = new Message();
handler.sendMessage(msg);
stopSelf(); // 任务完成后停止Service
}
}.start();
return START_STICKY; // 标记Service被系统关闭后自动重启
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "Service destroyed");
// 取消通知栏
}
@Override
public IBinder onBind(Intent intent) {
return null; // 此处不需要绑定Service,因此返回null
}
/**
* 判断是否已经下载
*/
private void ifapk(){
File apkfile = new File(getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) + "/" + title + ".apk");
if(apkfile.exists()){
//已经下载,直接安装
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//安装完成后打开新版本
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 给目标应用一个临时授权
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//判断版本大于等于7.0
//如果SDK版本>=24,即:Build.VERSION.SDK_INT >= 24,使用FileProvider兼容安装apk
String packageName = getApplicationContext().getApplicationContext().getPackageName();
String authority = new StringBuilder(packageName).append(".fileProvider").toString();
Uri apkUri = FileProvider.getUriForFile(this, authority, apkfile);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(apkfile), "application/vnd.android.package-archive");
}
getApplicationContext().startActivity(intent);
android.os.Process.killProcess(android.os.Process.myPid());//安装完之后会提示”完成” “打开”。
}else {
//未下载
sendNotification(title,content);//发送通知栏
downloadapk();//下载
}
}
/**
* 下载apk
*/
private void downloadapk(){
Download down = new Download();
down.setondown(new Download.ondown() {
@Override
public void downing(int len, int oklen) {
//下载ing
updateNotificationProgress((int) (((double) oklen / (double) len) * 100));
}
@Override
public void downok(String ruest) {
//下载完成判断apk
ifapk();
}
});
down.dowmFile(url, getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) + "/" + title + ".apk");///mnt/sdcard
}
/**
* 更新通知栏
* @param title
* @param message
*/
public void sendNotification(String title,String message) {
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE);
// // 1. 创建一个通知(必须设置channelId)
Notification notification = new Notification.Builder(this, notificationid + "")
.setContentTitle(title)
.setContentText(message)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_launcher_foreground)
//.setLargeIcon(BitmapFactory.decodeResource(this.getResources(),R.drawable.cjss)
.setContentIntent(pendingIntent) // 设置点击通知时的操作
.setAutoCancel(true)
.build();
// 2. 获取系统的通知管理器
android.app.NotificationManager notificationManager = (android.app.NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// 3. 创建NotificationChannel(这里传入的channelId要和创建的通知channelId一致,才能为指定通知建立通知渠道)
NotificationChannel channel = new NotificationChannel(notificationid+"","测试渠道名称", android.app.NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
// 4. 发送通知
notificationManager.notify(notificationid, notification);
}
/**
* 更新下载进度
* @param progress
*/
private void updateNotificationProgress(int progress) {
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE);
Notification notification = new Notification.Builder(this, notificationid + "")
.setContentTitle(title)
.setContentText("下载进度:" + progress + "%")
.setProgress(100, progress, false)
.setSmallIcon(R.drawable.ic_launcher_foreground)
//.setLargeIcon(BitmapFactory.decodeResource(this.getResources(),R.drawable.cjss)
.setContentIntent(pendingIntent) // 设置点击通知时的操作
.setAutoCancel(true)
.build();
// 2. 获取系统的通知管理器
android.app.NotificationManager notificationManager = (android.app.NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// 3. 创建NotificationChannel(这里传入的channelId要和创建的通知channelId一致,才能为指定通知建立通知渠道)
NotificationChannel channel = new NotificationChannel(notificationid+"","测试渠道名称", android.app.NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
// 发送更新后的通知
notificationManager.notify(notificationid, notification);
}
}
主要代码基本就是上面这些啦,但是还有一点点的配置文件也是很重要的。
(res/xml/)network.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
(res/xml/)filepath.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--安装包文件存储路径-->
<external-files-path
name="my_download"
path="Download" />
<external-path
name="."
path="." />
</paths>
最后就是AndroiManifest.xml
相关权限
<!-- 允许联网 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- 开机自启 -->
<uses-permission android:name="android.permission.VIBRATE" /> <!-- 通知栏 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" /> <!-- 获取GSM(2g)、WCDMA(联通3g)等网络状态的信息 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 获取wifi网络状态的信息 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- 保持CPU 运转,屏幕和键盘灯有可能是关闭的,用于文件上传和下载 -->
<uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- 获取sd卡写的权限,用于文件上传和下载 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 允许读取手机状态 用于创建BmobInstallation -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- 安装APK权限 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.REPLACE_EXISTING_PACKAGE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /><!--安卓13通知-->
其中权限部分不用说了吧,主要是 android:networkSecurityConfig="@xml/network" 和android:requestLegacyExternalStorage="true"用于安卓应用程序的网络安全配置文件(直接一点就是访问能访问http协议)
以及元数据部分
<meta-data
android:name="com.google.android.actions"
android:resource="@xml/filepath" />
最后是最最重要的别忘记申明 MyService 类作为应用程序的服务组件!
- android:enabled: 表示系统是否可以实例化并运行服务。这里设置为 "true",允许系统根据需要启动服务。
- android:exported: 指定服务是否可以供其他应用程序的组件访问和交互。将其设置为 "true" 表示服务可以被其他应用程序的组件访问。
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>
(需要配置的地方有3处我就直接整个贴出来了)
AndroiManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 允许联网 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- 开机自启 -->
<uses-permission android:name="android.permission.VIBRATE" /> <!-- 通知栏 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" /> <!-- 获取GSM(2g)、WCDMA(联通3g)等网络状态的信息 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 获取wifi网络状态的信息 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- 保持CPU 运转,屏幕和键盘灯有可能是关闭的,用于文件上传和下载 -->
<uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- 获取sd卡写的权限,用于文件上传和下载 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 允许读取手机状态 用于创建BmobInstallation -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- 安装APK权限 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.REPLACE_EXISTING_PACKAGE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /><!--安卓13通知-->
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
tools:targetApi="31">
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>
<meta-data
android:name="com.google.android.actions"
android:resource="@xml/filepath" />
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
好了到这里就基本实现了app更新迭代的功能了,有任何问题欢迎在评论区指出。