一、近期需要实现一个apk静默升级卸载自启动功能,首先需要获取系统root权限才能执行静默升级,下面不墨迹直接上代码. 首先是MainActivity 页面
package com.example.tiaoshiapkjingmo;
import androidx.appcompat.app.AppCompatActivity;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.blankj.utilcode.util.AppUtils;
import com.blankj.utilcode.util.LogUtils;
import com.example.tiaoshiapkjingmo.service.UpdateService;
import com.example.tiaoshiapkjingmo.utils.DownloadHelper;
import com.example.tiaoshiapkjingmo.utils.HttpUtil;
import com.example.tiaoshiapkjingmo.utils.SilentInstallUtils;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/**
* 这个地方对比版本号 然后执行静默升级
*/
//启动服务
Intent mService = new Intent(this, UpdateService.class);
startService(mService);
}
}
这个页面 就啥没写因为主要我们是要试apk静默升级
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
二、这边用了一个Service 下面直接上代码.
package com.example.tiaoshiapkjingmo.service;
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import com.blankj.utilcode.util.AppUtils;
import com.example.tiaoshiapkjingmo.MainActivity;
import com.example.tiaoshiapkjingmo.utils.DownloadHelper;
import com.example.tiaoshiapkjingmo.utils.HttpUtil;
import com.example.tiaoshiapkjingmo.utils.SilentInstallUtils;
import java.io.File;
import androidx.annotation.Nullable;
/**
* @ProjectName : TiaoShiapkjingmo
* @Author : 白月初
* @Time : 2022/11/15 15:43
* @Description : 描述
*/
public class UpdateService extends Service {
//这个是我随便找的一个apk下载地址
public String apkPath = "http://downloads.rongcloud.cn/SealTalk_by_RongCloud_Android_v1_2_17.apk";
@Override
public void onCreate() {
super.onCreate();
//下载网络apk包
DownloadHelper.instance().downloadAPK(apkPath, "base", new DownloadHelper.CallBack() {
@Override
public void downApkSuccess(String path, String apkName) {
//下载好的apk地址 和apk路径名
silenceInstall(path, apkName);
}
@Override
public void downApkFail() {
Log.i("下载APK失败","");
}
});
}
@SuppressLint("LongLogTag")
private void silenceInstall(String path, String apkName ) {
HttpUtil.getExecutorService().execute(() -> {
Log.i("开始静默安装APK","");
try {
//执行静默升级 ture 为成功
boolean installSuccess = SilentInstallUtils.install(UpdateService.this,
path + File.separator + apkName);
//判断
if (installSuccess) {
//获取apk包名
String appPackageName = AppUtils.getAppPackageName();
Intent intent1 =
UpdateService.this.getPackageManager().getLaunchIntentForPackage(appPackageName);
if (intent1 != null) {
intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP);
}
startActivity(intent1);
Log.i("静默安装APK成功!","");
//获取apk包名
String appPackage = AppUtils.getAppPackageName();
//根据包名静默卸载
SilentInstallUtils.uninstall(UpdateService.this,appPackage);
} else {
Log.i("静默安装APK失败: [May be permission refuse!]","");
}
} catch (InterruptedException e) {
e.printStackTrace();
Log.i("静默安装APK失败: " + e.getMessage(),"");
}
});
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
三、SilentInstallUtils 静默升级工具类 其实核心在这里调用反射机制获取到对应的方法.
package com.example.tiaoshiapkjingmo.utils;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.SystemClock;
import android.text.TextUtils;
import com.blankj.utilcode.util.AppUtils;
import com.blankj.utilcode.util.CloseUtils;
import com.blankj.utilcode.util.DeviceUtils;
import com.blankj.utilcode.util.ImageUtils;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.ShellUtils;
import com.blankj.utilcode.util.UriUtils;
import com.blankj.utilcode.util.Utils;
import com.example.tiaoshiapkjingmo.IPackageDeleteObserver;
import com.example.tiaoshiapkjingmo.IPackageInstallObserver;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RequiresPermission;
public final class SilentInstallUtils {
private static final String TAG = "SilentInstallUtils";
private static final String SP_NAME_PACKAGE_INSTALL_RESULT = "package_install_result";
private static volatile Method sInstallPackage;
private static volatile Method sDeletePackage;
private static volatile SharedPreferences sPreferences;
/**
* 静默安装
* 会依次调用Stream-->反射-->Shell
*
* @param apkFile APK文件
* @return 成功或失败
*/
@SuppressLint("PackageManagerGetSignatures")
@RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
public static synchronized boolean install(Context context, String apkFile) throws InterruptedException {
File file;
if (TextUtils.isEmpty(apkFile) || !(file = new File(apkFile)).exists()) {
return false;
}
context = context.getApplicationContext();
//加上apk合法性判断
AppUtils.AppInfo apkInfo = AppUtils.getApkInfo(file);
if (apkInfo == null || TextUtils.isEmpty(apkInfo.getPackageName())) {
LogUtils.iTag(TAG, "apk info is null, the file maybe damaged: " + file.getAbsolutePath());
return false;
}
//加上本地apk版本判断
AppUtils.AppInfo appInfo = AppUtils.getAppInfo(apkInfo.getPackageName());
if (appInfo != null) {
//已安装的版本比apk版本要高, 则不需要安装
if (appInfo.getVersionCode() >= apkInfo.getVersionCode()) {
LogUtils.iTag(TAG, "The latest version has been installed locally: " + file.getAbsolutePath(),
"app info: packageName: " + appInfo.getPackageName() + "; app name: " + appInfo.getName(),
"apk version code: " + apkInfo.getVersionCode(),
"app version code: " + appInfo.getVersionCode());
return true;
}
//已安装的版本比apk要低, 则需要进一步校验签名和ShellUID
PackageManager pm = context.getPackageManager