前言:最近公司项目版本需要把以前的版本更新代码更新一下,换成Service和前台通知栏形式的,所以在此记录一下。
目录
2.创建Service开启前台通知栏,并且执行下载任务,将下载进度通知到通知栏,下载完成后进行安装
2.开启一个透明InstallApkActivity执行安装Apk操作
3.注意事项:因为24版本之后读取文件需要配置FileProvider,不然无法读取到apk文件
在res目录下建一个xml文件夹,然后创建file_path.xml文件
1.检测版本更新(省略)
这一步一般是通过后台接口对比当前APP版本是否低于服务器版本,从而获取最新版本的下载链接。这一步比较简单所以省略了。
2.创建Service开启前台通知栏,并且执行下载任务,将下载进度通知到通知栏,下载完成后进行安装
完整的Service代码
package com.qtz.online.service;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import com.qtz.online.R;
import com.qtz.online.common.AppService;
import com.qtz.online.event_bus.DownloadApkMessage;
import com.qtz.online.mvp.activitys.InstallApkActivity;
import com.qtz.online.network.callback.ServiceDownloadObserver;
import com.qtz.online.network.client.DownLoadClient;
import com.qtz.online.utils.LogUtil;
import org.greenrobot.eventbus.EventBus;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import okhttp3.ResponseBody;
/**
* @author Created by PengGuiChu on 2020/12/19 19:32.
* @explain 版本更新服务
*/
public class DownloadApkService extends Service {
private static final String TAG=DownloadApkService.class.getSimpleName();
private String url;//下载
private String fileDir;
private String fileName;
private NotificationManager notificationManager;
private NotificationCompat.Builder notificationBuilder;
private static final String name="download_apk";
private static final String id="Download_APK";
private static final int startForegroundId=777;
private int downLoadProgress;
@Override
public void onCreate() {
super.onCreate();
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationBuilder = getNotificationBuilder(notificationManager,getString(R.string.version_upgrade),getString(R.string.downloading,"0%"));
notificationBuilder.setProgress(100,0,false);
startForeground(startForegroundId,notificationBuilder.build());
// notificationManager.notify(2,notificationBuilder.build());
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent!=null){
url=intent.getStringExtra("url");
fileDir=intent.getStringExtra("fileDir");
fileName=intent.getStringExtra("fileName");
//该下载请求是我项目的下载请求,需要替换成你项目的下载文件的请求
downloadFile(DownLoadClient.getService(AppService.class).download(url), new ServiceDownloadObserver<ResponseBody>(fileDir,fileName) {
@Override
public void onDownloadStart() {
}
@Override
public void onDownloading(long progress, long total) {
int down= (int) (progress*100/total);
if (down!=downLoadProgress){
downLoadProgress=down;
if (notificationBuilder!=null){
notificationBuilder.setContentText(getString(R.string.downloading,downLoadProgress+"%"));
notificationBuilder.setProgress(100, downLoadProgress,false);
}
if (notificationManager!=null){
notificationManager.notify(startForegroundId,notificationBuilder.build());
}
}
}
@Override
public void onDownloadSuccess(ResponseBody responseBody, String filePath) {
LogUtil.d(TAG,"下载完成,开启一个空白Activity执行安装窗口,保证用户在任何地方都能开启安装步骤");
// EventBus.getDefault().post(new DownloadApkMessage(0,filePath));
Intent intent=new Intent(getApplicationContext(), InstallApkActivity.class);
intent.putExtra("filePath",filePath);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
stopForeground(true);
stopSelf();
}
@Override
public void onDownloadError(String msg) {
LogUtil.d(TAG,"下载出错");
EventBus.getDefault().post(new DownloadApkMessage(1,msg));
stopForeground(true);
stopSelf();
}
});
}
return super.onStartCommand(intent, flags, startId);
}
private NotificationCompat.Builder getNotificationBuilder(NotificationManager notificationManager, String title, String message) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(DownloadApkService.id, DownloadApkService.name,
NotificationManager.IMPORTANCE_LOW);
//闪光灯
channel.enableLights(false);
//是否允许震动
channel.enableVibration(false);
//设置可绕过 请勿打扰模式
channel.setBypassDnd(false);
channel.setSound(null,null);
notificationManager.createNotificationChannel(channel);
}
NotificationCompat.Builder notification = new NotificationCompat.Builder(this, DownloadApkService.id);
notification.build().flags= Notification.FLAG_ONGOING_EVENT;
notification.setContentTitle(title);
notification.setDefaults(NotificationCompat.DEFAULT_VIBRATE);
notification.setContentText(message);
notification.setSmallIcon(R.mipmap.ic_launcher);
notification.setAutoCancel(false);
notification.setCategory(Notification.CATEGORY_PROGRESS);
return notification;
}
protected void downloadFile(Observable<ResponseBody> downloadObservable, final ServiceDownloadObserver<ResponseBody> downloadObserver) {
downloadObservable.subscribeOn(Schedulers.io())//请求网络 在调度者的io线程
.observeOn(Schedulers.io()) //指定线程保存文件
.doOnNext(downloadObserver::saveFile)
.observeOn(AndroidSchedulers.mainThread()) //在主线程中更新ui
.subscribe(downloadObserver);
}
}
Service所用到的变量补充
<string name="version_upgrade">发现新版本</string>
<string name="downloading">下载中…%1$s</string>
2.开启一个透明InstallApkActivity执行安装Apk操作
Activity代码
package com.qtz.online.mvp.activitys;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.qtz.online.R;
import com.qtz.online.utils.InstallApkUtil;
import java.io.File;
public class InstallApkActivity extends AppCompatActivity {
private InstallApkUtil installApkUtil;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_install_apk);
String filePath = getIntent().getStringExtra("filePath");
initInstallApkUtil(new File(filePath));
}
private void initInstallApkUtil(File file) {
if (installApkUtil == null) {
installApkUtil = new InstallApkUtil(InstallApkActivity.this, file.getAbsolutePath());
}
installApkUtil.installApk();
}
//更具用户授权回调执行安装操作
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 101) {
if (installApkUtil != null) {
installApkUtil.installApk();
}
}else if (requestCode==102){
finish();
}
}
}
工具类代码
package com.qtz.online.utils;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
import com.qtz.online.BuildConfig;
import com.qtz.online.base.BaseActivity;
import com.qtz.online.view.ChoiceDialog;
import org.greenrobot.eventbus.EventBus;
import java.io.File;
/**
* @author Created by PengGuiChu on 2020/12/19 15:53.
* @explain
*/
public class InstallApkUtil {
private AppCompatActivity baseActivity;
private String apkPath;
private ChoiceDialog choiceDialog;
public InstallApkUtil(AppCompatActivity baseActivity, String apkPath) {
this.baseActivity = baseActivity;
this.apkPath=apkPath;
}
public void installApk() {
if (Build.VERSION.SDK_INT >= 26) {
//来判断应用是否有权限安装apk
boolean installAllowed = baseActivity.getPackageManager().canRequestPackageInstalls();
//有权限
if (installAllowed) {
//安装apk
install();
} else {
//这里弹出一个选择对话框,提示用户需要授权第三方安装应用,需要自己实现
if (choiceDialog==null){
choiceDialog = new ChoiceDialog(baseActivity, new ChoiceDialog.ClauseDialogCallBack() {
@Override
public void onExit() {
baseActivity.finish();
}
@Override
public void onAgree() {
Uri packageURI = Uri.parse("package:" + baseActivity.getPackageName());
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI);
baseActivity.startActivityForResult(intent, 101);
}
}, "安装应用需要打开未知来源权限,请去设置中开启权限", "权限申请", "取消", "前往");
}
choiceDialog.show();
}
} else {
install();
}
}
public void install() {
if (Build.VERSION.SDK_INT >= 24) {
Uri apkUri = FileProvider.getUriForFile(baseActivity,
BuildConfig.APPLICATION_ID + ".myprovider", new File(apkPath));
Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive");
baseActivity.startActivityForResult(installIntent,102);
} else {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.parse("file://" + apkPath), "application/vnd.android.package-archive");
baseActivity.startActivityForResult(intent,102);
}
}
}
3.注意事项:因为24版本之后读取文件需要配置FileProvider,不然无法读取到apk文件
在res目录下建一个xml文件夹,然后创建file_path.xml文件
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-cache-path path="apk" name="."/>
</paths>
因为我是把apk下载在了Android/Data/包名/cache/apk目录下所以这样配置,这里需要根据你的具体情况进行配置
<root-path/>
代表设备的根目录new File("/");<files-path/>
代表context.getFilesDir()<cache-path/>
代表context.getCacheDir()<external-path/>
代表Environment.getExternalStorageDirectory()<external-files-path>
代表context.getExternalFilesDirs()<external-cache-path>
代表getExternalCacheDirs()
AndroidManifest.xml配置
<provider
android:name=".utils.OnlineFileProvider"
android:authorities="${applicationId}.myprovider"
android:exported="false"
android:grantUriPermissions="true">
<!-- 元数据 -->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider>
OnlineFileProvider类代码
package com.qtz.online.utils;
import androidx.core.content.FileProvider;
/**
* @author Created by PengGuiChu on 2020/12/19 16:39.
* @explain
*/
public class OnlineFileProvider extends FileProvider {
}