一、近期需要实现一个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();
try {
PackageInfo appPackageInfo, apkPackageInfo;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
appPackageInfo = pm.getPackageInfo(appInfo.getPackageName(), PackageManager.GET_SIGNING_CERTIFICATES);
apkPackageInfo = pm.getPackageArchiveInfo(file.getAbsolutePath(), PackageManager.GET_SIGNING_CERTIFICATES);
} else {
appPackageInfo = pm.getPackageInfo(appInfo.getPackageName(), PackageManager.GET_SIGNATURES);
apkPackageInfo = pm.getPackageArchiveInfo(file.getAbsolutePath(), PackageManager.GET_SIGNATURES);
}
if (appPackageInfo != null && apkPackageInfo != null &&
!compareSharedUserId(appPackageInfo.sharedUserId, apkPackageInfo.sharedUserId)) {
LogUtils.wTag(TAG, "Apk sharedUserId is not match",
"app shellUid: " + appPackageInfo.sharedUserId,
"apk shellUid: " + apkPackageInfo.sharedUserId);
return false;
}
} catch (Throwable ignored) {
}
}
// try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//由于调用PackageInstaller安装失败的情况下, 重复安装会导致内存占用无限增长的问题.
//所以在安装之前需要判断当前包名是否有过失败记录, 如果以前有过失败记录, 则不能再使用该方法进行安装
if (sPreferences == null) {
sPreferences = context.getSharedPreferences(SP_NAME_PACKAGE_INSTALL_RESULT, Context.MODE_PRIVATE);
}
String packageName = apkInfo.getPackageName();
boolean canInstall = sPreferences.getBoolean(packageName, true);
if (canInstall) {
boolean success = installByPackageInstaller(context, file, apkInfo);
sPreferences.edit().putBoolean(packageName, success).apply();
if (success) {
LogUtils.iTag(TAG, "Install Success[PackageInstaller]: " + file.getAbsolutePath());
return true;
}
}
}
if (installByReflect(context, file)) {
if (sPreferences != null) {
sPreferences.edit().putBoolean(apkInfo.getPackageName(), true).apply();
}
LogUtils.iTag(TAG, "Install Success[Reflect]", file.getPath());
return true;
}
if (installByShell(file, DeviceUtils.isDeviceRooted())) {
if (sPreferences != null) {
sPreferences.edit().putBoolean(apkInfo.getPackageName(), true).apply();
}
LogUtils.iTag(TAG, "Install Success[Shell]", file.getPath());
return true;
}
// } catch (InterruptedException e) {
// throw e;
// } catch (Throwable e) {
// e.printStackTrace();
// LogUtils.wTag(TAG, e);
// }
LogUtils.iTag(TAG, "Install Failure: " + file.getAbsolutePath());
return false;
}
/**
* 卸载
* PackageInstaller-->反射-->Shell
*/
@RequiresPermission(Manifest.permission.DELETE_PACKAGES)
public static synchronized boolean uninstall(Context context, String packageName) {
if (TextUtils.isEmpty(packageName)) {
return false;
}
//如果未安装, 直接返回成功即可
if (!AppUtils.isAppInstalled(packageName)) {
return true;
}
//如果是系统app, 则不支持卸载
AppUtils.AppInfo appInfo = AppUtils.getAppInfo(packageName);
if (appInfo != null && appInfo.isSystem()) {
LogUtils.iTag(TAG, "Uninstall Failure[System App]: " + packageName);
return false;
}
context = context.getApplicationContext();
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (uninstallByPackageInstaller(context, packageName)) {
LogUtils.iTag(TAG, "Uninstall Success[PackageInstaller]: " + packageName);
return true;
}
}
if (uninstallByReflect(context, packageName)) {
LogUtils.iTag(TAG, "Uninstall Success[Reflect]: " + packageName);
return true;
}
if (uninstallByShell(packageName, DeviceUtils.isDeviceRooted())) {
LogUtils.iTag(TAG, "Uninstall Success[Shell]: " + packageName);
return true;
}
} catch (Throwable ignored) {
}
LogUtils.iTag(TAG, "Uninstall Failure: " + packageName);
return false;
}
/**
* 通过Shell命令静默安装
*
* @param file apk文件
* @param isRoot 设备是否有root权限,
* 如果没有root权限, 在Android7.0及以上设备需要声明 android:sharedUserId="android.uid.shell"
* Android 9.0 设备可能不支持shell命令安装
* @return 是否安装成功
*/
private static boolean installByShell(File file, boolean isRoot) {
String cmd = "pm install -r '" + file.getAbsolutePath() + "'";
return executeShell(cmd, isRoot) || executeShell(cmd, !isRoot);
}
private static boolean uninstallByShell(String packageName, boolean isRoot) {
String cmd = "pm uninstall '" + packageName + "'";
return executeShell(cmd, isRoot) || executeShell(cmd, !isRoot);
}
private static boolean executeShell(String cmd, boolean isRoot) {
LogUtils.iTag(TAG, "ShellCommand: " + cmd, "isRoot: " + isRoot);
ShellUtils.CommandResult result = ShellUtils.execCmd(cmd, isRoot);
LogUtils.iTag(TAG, "ShellCommand Result: " + result.toString());
return result.successMsg != null && result.successMsg.toLowerCase(Locale.US).contains("success");
}
/**
* 调用反射方式安装, 通过PackageManager#installPackage方法进行安装, 该方法在7.0已经移除
*/
private static boolean installByReflect(Context context, File file) throws InterruptedException {
LogUtils.iTag(TAG, "InstallByReflect", file.getPath());
Method installer = getInstallPackageMethod();
if (installer == null) {
return false;
}
final AtomicBoolean result = new AtomicBoolean(false);
final CountDownLatch countDownLatch = new CountDownLatch(1);
IPackageInstallObserver observer = new IPackageInstallObserver.Stub() {
@Override
public void packageInstalled(String packageName, int returnCode) {
try {
result.set(returnCode == 1);
} finally {
countDownLatch.countDown();
}
}
};
try {
installer.invoke(
context.getPackageManager(),
UriUtils.file2Uri(file),
observer,
0x00000002,//flag=2表示如果存在则覆盖升级)
context.getPackageName()
);
} catch (IllegalAccessException | InvocationTargetException ignored) {
countDownLatch.countDown();
}
countDownLatch.await();
return result.get();
}
/**
* 调用反射方式卸载, 通过PackageManager#deletePackage, 该方法在7.0已经移除
*/
private static boolean uninstallByReflect(Context context, String packageName) throws InterruptedException {
LogUtils.iTag(TAG, "UninstallByReflect", packageName);
Method deleter = getDeletePackageMethod();
if (deleter == null) {
return false;
}
final AtomicBoolean result = new AtomicBoolean(false);
final CountDownLatch countDownLatch = new CountDownLatch(1);
IPackageDeleteObserver observer = new IPackageDeleteObserver.Stub() {
@Override
public void packageDeleted(String packageName, int returnCode) {
try {
result.set(returnCode == 1);
} finally {
countDownLatch.countDown();
}
}
};
try {
deleter.invoke(
context.getPackageManager(),
packageName, observer, 0x00000002);
} catch (IllegalAccessException | InvocationTargetException ignored) {
countDownLatch.countDown();
}
countDownLatch.await();
return result.get();
}
/**
* 通过流的形式进行静默安装
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
private static boolean installByPackageInstaller(Context context, File file, AppUtils.AppInfo apkInfo) throws InterruptedException {
LogUtils.iTag(TAG, "InstallByPackageInstaller", file.getPath());
OutputStream out = null;
InputStream in = null;
PackageInstaller.Session session = null;
int sessionId = -1;
boolean success = false;
PackageManager pm = context.getPackageManager();
PackageInstaller installer = pm.getPackageInstaller();
try {
//初始化安装参数
PackageInstaller.SessionParams params =
new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.setSize(file.length());
params.setAppIcon(ImageUtils.drawable2Bitmap(apkInfo.getIcon()));
params.setAppLabel(apkInfo.getName());
params.setAppPackageName(apkInfo.getPackageName());
sessionId = installer.createSession(params);
//sessionId 会返回一个正数非零的值, 如果小于0, 表示会话开启错误
if (sessionId > 0) {
InstallReceiver callback = new InstallReceiver(context, true, file.getAbsolutePath());
session = installer.openSession(sessionId);
out = session.openWrite(file.getName(), 0, file.length());
in = new FileInputStream(file);
int len;
byte[] buffer = new byte[8192];
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
session.fsync(out);
in.close();
out.close();
session.commit(callback.getIntentSender());
success = callback.isSuccess();
}
} catch (InterruptedException e) {
throw e;
} catch (Throwable e) {
e.printStackTrace();
LogUtils.wTag(TAG, e);
} finally {
//如果会话已经开启, 但是没有成功, 则需要将会话进行销毁
try {
if (sessionId > 0 && !success) {
if (session != null) {
session.abandon();
}
installer.abandonSession(sessionId);
}
} catch (Throwable ignored) {
}
CloseUtils.closeIOQuietly(in, out, session);
}
return success;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@RequiresPermission(Manifest.permission.DELETE_PACKAGES)
private static boolean uninstallByPackageInstaller(Context context, String packageName) {
LogUtils.iTag(TAG, "UninstallByPackageInstaller", packageName);
try {
InstallReceiver callback = new InstallReceiver(context, false, packageName);
PackageInstaller installer = Utils.getApp().getPackageManager().getPackageInstaller();
installer.uninstall(packageName, callback.getIntentSender());
return callback.isSuccess();
} catch (Throwable ignored) {
}
return false;
}
@Nullable
private static Method getInstallPackageMethod() {
if (sInstallPackage != null) {
return sInstallPackage;
}
try {
//noinspection JavaReflectionMemberAccess
sInstallPackage = PackageManager.class.getMethod("installPackage",
Uri.class, IPackageInstallObserver.class, int.class, String.class);
return sInstallPackage;
} catch (NoSuchMethodException ignored) {
}
return null;
}
@Nullable
private static Method getDeletePackageMethod() {
if (sDeletePackage != null) {
return sDeletePackage;
}
try {
//noinspection JavaReflectionMemberAccess
sDeletePackage = PackageManager.class.getMethod("deletePackage",
String.class, IPackageDeleteObserver.class, int.class);
return sDeletePackage;
} catch (NoSuchMethodException ignored) {
}
return null;
}
/**
* 安装/卸载回调广播
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private static final class InstallReceiver extends BroadcastReceiver {
private final String ACTION = InstallReceiver.class.getName() + SystemClock.elapsedRealtimeNanos();
private final Context mContext;
private final String mOperate;
private final String mParam;
/**
* 用于将异步转同步
*/
private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
private boolean mSuccess = false;
private InstallReceiver(Context context, boolean isInstall, String param) {
this.mContext = context.getApplicationContext();
this.mOperate = isInstall ? "Install" : "Uninstall";
this.mParam = param;
this.mContext.registerReceiver(this, new IntentFilter(ACTION));
}
private boolean isSuccess() throws InterruptedException {
try {
//安装最长等待2分钟.
mCountDownLatch.await(2L, TimeUnit.MINUTES);
return mSuccess;
} finally {
mContext.unregisterReceiver(this);
}
}
private IntentSender getIntentSender() {
return PendingIntent
.getBroadcast(mContext, ACTION.hashCode(), new Intent(ACTION).setPackage(mContext.getPackageName()),
PendingIntent.FLAG_UPDATE_CURRENT)
.getIntentSender();
}
@Override
public void onReceive(Context context, Intent intent) {
try {
int status = -200;
if (intent == null) {
mSuccess = false;
} else {
status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
mSuccess = status == PackageInstaller.STATUS_SUCCESS;
}
LogUtils.iTag(TAG, mParam, mOperate + " Result: " + mSuccess + "[" + status + "]");
} finally {
mCountDownLatch.countDown();
}
}
}
private static boolean compareSharedUserId(String appUid, String apkUid) {
return TextUtils.equals(appUid, apkUid) || (appUid != null && appUid.equalsIgnoreCase(apkUid));
}
}
四、HttpUtil 调用到了.
package com.example.tiaoshiapkjingmo.utils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class HttpUtil {
private static ExecutorService executorService = Executors.newSingleThreadExecutor();
public static ExecutorService getExecutorService() {
return executorService;
}
public interface HttpCallbackListener {
void onFinish(String response);
void onError(Exception e);
}
//子线程中无法返回数据,所以要使用接口
public static void sendHttpRequest(final String address, final HttpCallbackListener listener) {
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
try {
URL url = new URL(address);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(3000);
connection.setReadTimeout(3000);
connection.setDoInput(true);
connection.setDoOutput(true);
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
if (listener != null) {
listener.onFinish(response.toString());
}
} catch (Exception e) {
if (listener != null) {
listener.onError(e);
}
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}).start();
}
}
五、DownloadHelper 类
package com.example.tiaoshiapkjingmo.utils;
import android.annotation.SuppressLint;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import com.blankj.utilcode.util.FileUtils;
import com.example.tiaoshiapkjingmo.App;
import java.io.File;
import static android.os.Environment.DIRECTORY_DOWNLOADS;
public class DownloadHelper {
private DownloadManager mDownloadManager;
private long mDownloadId;
private String mFilePath;
private String mAPKName;
private CallBack mCallBack;
private static DownloadHelper mHelper;
private DownloadHelper() {
}
public static DownloadHelper instance() {
if (mHelper == null) {
mHelper = new DownloadHelper();
}
return mHelper;
}
//下载apk
public void downloadAPK(String url, String name, CallBack back) {
mCallBack = back;
if (FileUtils.delete(new File(Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS).getAbsolutePath() + File.separator + name))) {
Log.e("Tag", "downloadAPK: 删除成功");
} else {
Log.e("Tag", "downloadAPK: 删除失败");
}
mAPKName = name;
Log.e("Tag", "downloadAPK: 开始下载");
//创建下载任务
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
//移动网络情况下是否允许漫游
request.setAllowedOverRoaming(true);
//在通知栏中显示,默认就是显示的
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
request.setMimeType("application/vnd.android.package-archive");
request.setTitle("新版本Apk");
request.setDescription("正在下载安装包...");
request.setVisibleInDownloadsUi(true);
mFilePath = Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS).getAbsolutePath();
//设置下载的路径
request.setDestinationInExternalPublicDir(DIRECTORY_DOWNLOADS, mAPKName);
//获取DownloadManager
mDownloadManager = (DownloadManager) App.getApp().getSystemService(Context.DOWNLOAD_SERVICE);
if (mDownloadManager != null) {
//将下载请求加入下载队列,加入下载队列后会给该任务返回一个long型的id,通过该id可以取消任务,重启任务、获取下载的文件等等
mDownloadId = mDownloadManager.enqueue(request);
//注册广播接收者,监听下载状态
IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
filter.addAction(DownloadManager.ACTION_NOTIFICATION_CLICKED);
App.getApp().registerReceiver(receiver, filter);
}
/*Uri uri = Uri.parse("content://downloads/all_downloads/" + mDownloadId);;
VLog.e("Tag", "uri: " + uri.toString());
mActivity.getContentResolver().registerContentObserver(uri, true, new DownloadContentObserver());*/
}
//广播监听下载的各个状态
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DownloadManager.ACTION_NOTIFICATION_CLICKED.equals(intent.getAction())) {
Log.e("Tag", "onReceive: Clicked");
} else {
checkStatus();
}
}
};
private Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0) {
Log.e("Tag", "当前进度: " + ((int) (msg.arg1 * 100f / msg.arg2)) + "%" + "status: " + msg.obj);
}
}
};
class DownloadContentObserver extends ContentObserver {
DownloadContentObserver() {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
updateView();
}
}
private void updateView() {
int[] bytesAndStatus = getBytesAndStatus(mDownloadId);
int currentSize = bytesAndStatus[0];//当前大小
int totalSize = bytesAndStatus[1];//总大小
int status = bytesAndStatus[2];//下载状态
Message.obtain(handler, 0, currentSize, totalSize, status).sendToTarget();
}
@SuppressLint("Range")
private int[] getBytesAndStatus(long downloadId) {
int[] bytesAndStatus = new int[]{-1, -1, 0};
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
try (Cursor c = mDownloadManager.query(query)) {
if (c != null && c.moveToFirst()) {
bytesAndStatus[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
bytesAndStatus[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
bytesAndStatus[2] = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
}
} catch (Exception ignored) {
}
return bytesAndStatus;
}
//检查下载状态
private void checkStatus() {
DownloadManager.Query query = new DownloadManager.Query();
//通过下载的id查找
query.setFilterById(mDownloadId);
Cursor c = mDownloadManager.query(query);
if (c.moveToFirst()) {
@SuppressLint("Range")
int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
switch (status) {
//下载暂停
case DownloadManager.STATUS_PAUSED:
Log.e("Tag", "checkStatus: 下载暂停");
break;
//下载延迟
case DownloadManager.STATUS_PENDING:
Log.e("Tag", "checkStatus: 下载延迟");
break;
//正在下载
case DownloadManager.STATUS_RUNNING:
Log.e("Tag", "checkStatus: 下载中");
break;
//下载完成
case DownloadManager.STATUS_SUCCESSFUL:
Log.e("Tag", "checkStatus: 下载完成");
downloadApkSuccess();
break;
//下载失败
case DownloadManager.STATUS_FAILED:
Log.e("Tag", "checkStatus: 下载失败");
downloadApkFail();
break;
}
}
c.close();
}
private void downloadApkSuccess() {
mCallBack.downApkSuccess(mFilePath, mAPKName);
}
private void downloadApkFail() {
mCallBack.downApkFail();
}
public interface CallBack {
void downApkSuccess(String path, String apkName);
void downApkFail();
}
}
七、IPackageDeleteObserver接口
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: E:\\WorkSpace_Xiexin\\Android\\Src\\Demo\\app\\src\\main\\aidl\\android\\content\\pm\\IPackageDeleteObserver.aidl
*/
package com.example.tiaoshiapkjingmo;
/**
* API for deletion callbacks from the Package Manager.
* <p>
* {@hide}
*/
public interface IPackageDeleteObserver extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements IPackageDeleteObserver {
private static final String DESCRIPTOR = "android.content.pm.IPackageDeleteObserver";
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an android.content.pm.IPackageDeleteObserver interface,
* generating a proxy if needed.
*/
public static IPackageDeleteObserver asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof IPackageDeleteObserver))) {
return ((IPackageDeleteObserver) iin);
}
return new Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_packageDeleted: {
data.enforceInterface(DESCRIPTOR);
String _arg0;
_arg0 = data.readString();
int _arg1;
_arg1 = data.readInt();
this.packageDeleted(_arg0, _arg1);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements IPackageDeleteObserver {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public void packageDeleted(String packageName, int returnCode) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(packageName);
_data.writeInt(returnCode);
mRemote.transact(Stub.TRANSACTION_packageDeleted, _data, null, android.os.IBinder.FLAG_ONEWAY);
} finally {
_data.recycle();
}
}
}
static final int TRANSACTION_packageDeleted = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public void packageDeleted(String packageName, int returnCode) throws android.os.RemoteException;
}
八、IPackageInstallObserver 接口
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: E:\\WorkSpace_Xiexin\\Android\\Src\\Demo\\app\\src\\main\\aidl\\android\\content\\pm\\IPackageInstallObserver.aidl
*/
package com.example.tiaoshiapkjingmo;
/**
* API for installation callbacks from the Package Manager.
* @hide
*/
public interface IPackageInstallObserver extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements IPackageInstallObserver {
private static final String DESCRIPTOR = "android.content.pm.IPackageInstallObserver";
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an android.content.pm.IPackageInstallObserver interface,
* generating a proxy if needed.
*/
public static IPackageInstallObserver asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof IPackageInstallObserver))) {
return ((IPackageInstallObserver) iin);
}
return new Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_packageInstalled: {
data.enforceInterface(DESCRIPTOR);
String _arg0;
_arg0 = data.readString();
int _arg1;
_arg1 = data.readInt();
this.packageInstalled(_arg0, _arg1);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements IPackageInstallObserver {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public void packageInstalled(String packageName, int returnCode) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(packageName);
_data.writeInt(returnCode);
mRemote.transact(Stub.TRANSACTION_packageInstalled, _data, null, android.os.IBinder.FLAG_ONEWAY);
} finally {
_data.recycle();
}
}
}
static final int TRANSACTION_packageInstalled = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public void packageInstalled(String packageName, int returnCode) throws android.os.RemoteException;
}
九、App类
package com.example.tiaoshiapkjingmo;
import android.app.Application;
import android.content.Context;
/**
* @ProjectName : TiaoShiapkjingmo
* @Author : 白月初
* @Time : 2022/11/15 15:21
* @Description : 描述
*/
public class App extends Application {
private static Context mApp;
@Override
public void onCreate() {
super.onCreate();
mApp=this;
}
public static Context getApp() {
return mApp;
}
}
十、广播卸载自启动 InstallReceiver
package com.example.tiaoshiapkjingmo.receiver;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.widget.Toast;
import com.blankj.utilcode.util.AppUtils;
import com.example.tiaoshiapkjingmo.MainActivity;
/**
* @ProjectName : TiaoShiapkjingmo
* @Author : 白月初
* @Time : 2022/11/10 16:15
* @Description : 描述
*/
public class InstallReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
String localPkgName = context.getPackageName();
//取得MyReceiver所在的App的包名
Uri data = intent.getData();
String installedPkgName = data.getSchemeSpecificPart();
//取得安装的Apk的包名,只在该app覆盖安装后自启动
if ((action.equals(Intent.ACTION_PACKAGE_ADDED) || action.equals(Intent.ACTION_PACKAGE_REPLACED)) && installedPkgName.equals(localPkgName)) {
/**
* 启动activity
*/
Intent mIntent = new Intent( );
mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//获取包名
String appPackageName = AppUtils.getAppPackageName();
ComponentName comp = new ComponentName(appPackageName, String.valueOf(MainActivity.class));
mIntent.setComponent(comp);
mIntent.setAction("android.intent.action.VIEW");
context.startActivity(mIntent);
}
}
}
十一、调用了工具类 build.gradle 下面加utilcode 工具类 感兴趣可以了解一下
implementation 'com.blankj:utilcode:1.23.5'
十二、清单文件AndroidManifest.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"
package="com.example.tiaoshiapkjingmo">
<uses-permission android:name="android.permission.DELETE_PACKAGES"
tools:ignore="ProtectedPermissions" />
<!-- 记得加系统配置这个 android:sharedUserId="android.uid.system"-->
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:sharedUserId="android.uid.system"
android:supportsRtl="true"
android:theme="@style/Theme.TiaoShiapkjingmo">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 注册广播 -->
<receiver android:name=".receiver.InstallReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
<!-- 启动服务-->
<service android:name=".service.UpdateService">
<intent-filter>
<action android:name="com.cheerslife.updateservice.updateservice.ApkService" />
</intent-filter>
</service>
</application>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.RESTART_PACKAGES" />
<uses-permission
android:name="android.permission.INSTALL_PACKAGES"
tools:ignore="ProtectedPermissions" />
<!-- 添加网络权限-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
</manifest>
十三、自己看把大体就这么多 卸载以后自启广播好像有点问题,android 10以后好像自启动广播失效欢迎大佬来吐槽修改,网上的大部分都不是这有坑就是那有坑,最起码这个还是靠谱的.
十四、别的不敢说最起码apk安装下载 卸载什么的都是没问题的.
源码地址:提取码:1234码云:地址
欢迎加入一起迭代.
加入地址