android 静默升级 卸载功能实现

news2025/7/4 20:12:50

一、近期需要实现一个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

码云:地址

欢迎加入一起迭代.
加入地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/7182.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何利用 promise 影响代码的执行顺序?

如何利用 promise 影响代码的执行顺序&#xff1f; 我们写代码的时候&#xff0c;经常会遇到这样的场景。2个不同组件&#xff0c;它们的生命周期本身都是相互独立&#xff0c;毫无关联的&#xff0c;但是它们调用的接口&#xff0c;存在相互依赖的情况。 我举个例子&#xf…

windows中Mysql数据库的安装和卸载

以安装包“mysql-5.7.35-win32.zip”为例&#xff0c;推荐安装5.7最新版本 一、安装 1、根据自己操作系统版本下载32位或64位的安装包&#xff0c;也可去官网下载&#xff0c;建议下载如下图压缩包 将下载的解压包解压&#xff0c;目录结构如下&#xff1a; 2、新建文件“my.…

IC工程师入门必学,Verilog零基础入门教程

近年来&#xff0c;IC行业发展火热&#xff0c;薪资待遇高&#xff0c;发展前景好&#xff0c;所以成了很多人转行的首选。但IC行业入行门槛高&#xff0c;需要具备相关的知识技能。比如工程师必须至少掌握一种HDL语言。 一般在HDL语言中&#xff0c;Verilog相对来说更加易学、…

负载均衡架构设计技巧

负载均衡算法 轮询&随机 基本原理 轮询&#xff1a;将请求依次发给服务器 随机&#xff1a;将请求随机发给服务器 适用场景 通用&#xff0c;无状态的负载均衡 优缺点 实现简单不会判断服务器状态&#xff0c;除非服务器连接丢失 问题场景 某个服务器当前因为触发…

Java面向对象---尚硅谷Java入门视频学习

1.类和对象 1.1创建过程 对象是将内存地址赋值给了变量&#xff0c;所以变量其实引用了内存中的对象&#xff0c;所以称之为引用变量&#xff0c;而变量的类型&#xff08;即类&#xff09;称之为引用数据类型。 堆&#xff08;Heap&#xff09;&#xff0c;此内存区域的唯…

关于pytorch的数据处理-数据加载Dataset

目录 1. 数据加载 2. Dataset __init__ __getitem__ __len__ 测试一下 完整代码 3. Dataset - ImageFolder 1. 数据加载 最近在使用 Unet 做图像分割&#xff0c;设计到 处理数据有关的工作&#xff0c;查了点资料&#xff0c;做一些简单的总结 在pytorch 中&#x…

Thinkphp安装报错解决办法

跟着官方文档的步骤安装thinkphp报错该如何解决&#xff1a; 前言 ThinkPHP无需安装过程&#xff0c;但是需要把ThinkPHP框架放入WEB运行环境&#xff08;前提是你的WEB运行环境没有问题&#xff09;。 一、Thinkphp安装以及报错解决方式 Thinkphp官网&#xff1a;安装ThinkP…

Mysql为何不推荐写多表SQL

前言 在大部分情况下&#xff0c;单表并不是比多表快单表优势在于理解成本与可控性有时候你觉得单表SQL不好写的时候&#xff0c;你改更新的是表结构 现状 在我们学习MySql的路程之中&#xff0c;估计不少人告诫我们不要写长语句。 至于为什么&#xff0c;确实很少人提起。 …

2.6 用一套万能文案公式来拆解4个小红书爆文案例【玩赚小红书】

公式细分人群他们的痛点数字干货分享情感共鸣 我们一个一个来看。 ​ ​ 一、《9平次卧小房间&#xff0c;再见了传统榻榻米&#xff08;附户型图&#xff09;》 家装类&#xff1a;避坑/攻略/小价钱装出大效果/装修效果拔群 标题直接点出了目标人群的需求&#xff1a;如何…

XSS进阶之CSP绕过

目录预备知识实验目的实验环境实验步骤一实验步骤二实验步骤三预备知识 1.了解Javascript、PHP和CSP的一些特性&#xff0c;比如“strict-dynamic”。 2.CSP&#xff1a;实质就是白名单制度&#xff0c;它规定哪些外部资源可以加载和执行。它的实现和执行全部由浏览器完成。资…

python实战指西<1>pygame安装,以及vscode

目录 1&#xff0c;安装pygame 1.1&#xff0c;&#xff08;如果前一个没反应的化&#xff09; 1.2如果飘红字 1&#xff0c;检查是否开了网络代理&#xff08;不要开&#xff09; 2&#xff0c;检查是否有pip模块更新需要 2.这里顺便记录一下vscode 蛇蛇的环境搭载 2.1首…

Postgresql实验系列(4)SIMD提升线性搜索性能24.5%(附带PG SIMD完整用例)

概要 接上一篇《Postgresql引入SIMD指令集》 PG引入SIMD执行集后具体有多大性能提升&#xff1f;本篇抽取PG的simd库&#xff0c;对比线性搜索场景的性能&#xff1a; 测试场景&#xff08;文章最后提供完整程序&#xff09; 构造一个存有14亿数字的数组 uint32 cnt 14100…

【python3】3.函数、类、模块

2022.11.15 本学习内容总结于莫烦python:3.函数、类、模块 https://mofanpy.com/tutorials/python-basic/interactive-python/function1. Function 函数 我常会重复写一些功能&#xff0c;比如查询文件时间&#xff0c;查询文件名字等等.后续我只需要引用到这个功能&#xff0…

Flink架构重要概念解析-超详理解

文章目录&#x1f48e; 1.1 系统架构1.1.1 整体构成1.1.2 作业管理器&#xff08;JobManager&#xff09;1.1.3 任务管理器&#xff08;TaskManager&#xff09;&#x1f680;1.2 作业提交流程1.2.1 高层级抽象视角1.2.2 独立模式&#xff08;Standalone&#xff09;1.2.3 YARN…

网页数据采集系统-怎样利用爬虫爬网站数据

随着社会不停地发展。人们也是越来越离不开互联网&#xff0c;今天小编就给大家盘点一下免费的网页数据采集系统&#xff0c;只需要点几下鼠标就能轻松爬取数据&#xff0c;不管是导出excel还是自动发布到网站都支持。详细参考图片一、二、三、四&#xff01; 企业人员 通过爬…

【直播预告】相机模型与标定——Real world超级公开课

导言 《Realworld超级公开课》是奥比中光3D视觉开发者社区打造的品牌活动之一&#xff0c;聚焦于3D视觉传感技术。每期课程邀请奥比中光及生态合作伙伴的技术专家&#xff0c;以线上线下相结合的授课形式&#xff0c;面向高校与人工智能企业的开发者&#xff0c;分享3D视觉技术…

线程的“结束”

【一道概率很高的面试题】&#xff1a; 如何优雅的结束一个线程&#xff1f; 上传一个大文件&#xff0c;正在处理费时的计算&#xff0c;如何优雅的结束这个线程呢&#xff1f; 【stop方法】&#xff1a; 【为何不建议使用stop呢&#xff1f;】&#xff1a; 因为很容易产生…

【附源码】计算机毕业设计JAVA成绩分析系统

【附源码】计算机毕业设计JAVA成绩分析系统 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JAVA mybati…

ORA-01940 无法删除当前已连接的用户之解决方案(脚本)

第一部分&#xff1a;配置数据库连接 1. 安装ODBC yum -y install unixODBC unixODBC-devel 2. 安装Oracle-instantclient #以下所有操作使用root账号执行 #创建目录 mkdir -p /opt/oracle cd /opt/oracle #下载odbc安装包 wget https://download.oracle.com/otn_software…

计算机毕业设计ssm+vue基本微信小程序的好物推荐分享系统

项目介绍 我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,好物分享系统小程序被用户普遍使用,为方便用户能够可以随时进行好物分享系统小程序的数据信息管理,特开发了基于好物分…