一、近期需要实现一个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 {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);/*** 这个地方对比版本号 然后执行静默升级*///启动服务Intent mService = new Intent(this, UpdateService.class);startService(mService);}
}
这个页面 就啥没写因为主要我们是要试apk静默升级
二、这边用了一个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";@Overridepublic void onCreate() {super.onCreate();//下载网络apk包DownloadHelper.instance().downloadAPK(apkPath, "base", new DownloadHelper.CallBack() {@Overridepublic void downApkSuccess(String path, String apkName) {//下载好的apk地址 和apk路径名silenceInstall(path, apkName);}@Overridepublic 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@Overridepublic 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要低, 则需要进一步校验签名和ShellUIDPackageManager 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() {@Overridepublic 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() {@Overridepublic 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;}@Nullableprivate static Method getInstallPackageMethod() {if (sInstallPackage != null) {return sInstallPackage;}try {//noinspection JavaReflectionMemberAccesssInstallPackage = PackageManager.class.getMethod("installPackage",Uri.class, IPackageInstallObserver.class, int.class, String.class);return sInstallPackage;} catch (NoSuchMethodException ignored) {}return null;}@Nullableprivate static Method getDeletePackageMethod() {if (sDeletePackage != null) {return sDeletePackage;}try {//noinspection JavaReflectionMemberAccesssDeletePackage = 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();}@Overridepublic 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() {@Overridepublic 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;}//下载apkpublic 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);//获取DownloadManagermDownloadManager = (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() {@Overridepublic 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()) {@Overridepublic 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);}@Overridepublic 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.* * {@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);}@Overridepublic android.os.IBinder asBinder() {return this;}@Overridepublic 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;}@Overridepublic android.os.IBinder asBinder() {return mRemote;}public String getInterfaceDescriptor() {return DESCRIPTOR;}@Overridepublic 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);}@Overridepublic android.os.IBinder asBinder() {return this;}@Overridepublic 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;}@Overridepublic android.os.IBinder asBinder() {return mRemote;}public String getInterfaceDescriptor() {return DESCRIPTOR;}@Overridepublic 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;@Overridepublic 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 {@Overridepublic 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
十三、自己看把大体就这么多 卸载以后自启广播好像有点问题,android 10以后好像自启动广播失效欢迎大佬来吐槽修改,网上的大部分都不是这有坑就是那有坑,最起码这个还是靠谱的.
十四、别的不敢说最起码apk安装下载 卸载什么的都是没问题的.
源码地址:提取码:1234码云:地址
欢迎加入一起迭代.
加入地址