Android 9 静默安装、卸载App

文章目录

  • 引言
  • 安装流程
  • 实现代码
  • AndroidManifest.xml配置
  • apk运行打包
  • 放到源码目录下重新进行签名
  • 编译
  • 安装日志

转载自 Android 9 P静默安装/卸载App适配终极指南

引言

静默安装是指apk安装不需要用户手动点击,直接安装

安装流程

  1. 通过PackageManagerService获取getPackageInstaller对象
  2. 通过packageInstaller调用openSession创建PackageInstaller.Session
  3. 将要静默安装的app写入Session中
  4. 然后调用Session的commit开始安装

实现代码

public class InstallApkSessionApi extends Activity {
    private static final String PACKAGE_INSTALLED_ACTION =
            "com.xxx.install";
    private static final String PACKAGE_UNINSTALLED_ACTION =
            "com.xxx.uninstall";
    private static final String TAG = "install";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_install_apk_session_api);
        // Watch for button clicks.
        Button button = (Button) findViewById(R.id.btn_install);

        button.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                Log.d(TAG, "click ");
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        PackageInstaller.Session session = null;
                        try {
                            //获取PackageInstaller对象
                            PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
                            PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                                    PackageInstaller.SessionParams.MODE_FULL_INSTALL);

                            //创建一个Session
                            int sessionId = packageInstaller.createSession(params);
                            Log.d(TAG, "sessionId = " + sessionId);
                            //建立和PackageManager的socket通道,Android中的通信不仅仅有Binder还有很多其它的
                            session = packageInstaller.openSession(sessionId);

                            //将App的内容通过session传输
                            addApkToInstallSession("hello.apk", session);

                            // Create an install status receiver.
                            Context context = InstallApkSessionApi.this;
                            Intent intent = new Intent(context, InstallApkSessionApi.class);
                            intent.setAction(PACKAGE_INSTALLED_ACTION);
                            PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
                            IntentSender statusReceiver = pendingIntent.getIntentSender();

                            // Commit the session (this will start the installation workflow).
                            //开启安装
                            Log.d(TAG, "session.commit ");
                            session.commit(statusReceiver);
                        } catch (IOException e) {
                            throw new RuntimeException("Couldn't install package", e);
                        } catch (RuntimeException e) {
                            if (session != null) {
                                session.abandon();
                            }
                            throw e;
                        }
                    }
                });
                thread.start();
            }
        });


        Button uninstall = (Button)findViewById(R.id.btn_uninstall);
        uninstall.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                // TODO Auto-generated method stub
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        uninstall("com.tony.usbcamera.molink");
                    }
                });
                thread.start();
            }
        });
    }

    private void addApkToInstallSession(String assetName, PackageInstaller.Session session)
            throws IOException {
        // It's recommended to pass the file size to openWrite(). Otherwise installation may fail
        // if the disk is almost full.
        try (OutputStream packageInSession = session.openWrite("package", 0, -1);
             InputStream is = getAssets().open(assetName)) {
            byte[] buffer = new byte[16384];
            int n;
            while ((n = is.read(buffer)) >= 0) {
                packageInSession.write(buffer, 0, n);
            }
        }
    }

    // Note: this Activity must run in singleTop launchMode for it to be able to receive the intent
    // in onNewIntent().
    //此处一定要运行单例模式或者singleTop模式,否则会一直创建该Activity
    @Override
    protected void onNewIntent(Intent intent) {
        Bundle extras = intent.getExtras();
        Log.e(TAG, intent.toString());
        if (PACKAGE_INSTALLED_ACTION.equals(intent.getAction())) {
            Log.e(TAG, intent.getAction());
            int status = extras.getInt(PackageInstaller.EXTRA_STATUS);
            Log.d(TAG, "status = " + status);
            String message = extras.getString(PackageInstaller.EXTRA_STATUS_MESSAGE);

            switch (status) {
                case PackageInstaller.STATUS_PENDING_USER_ACTION:
                    // This test app isn't privileged, so the user has to confirm the install.
                    Intent confirmIntent = (Intent) extras.get(Intent.EXTRA_INTENT);
                    startActivity(confirmIntent);
                    break;

                case PackageInstaller.STATUS_SUCCESS:
                    Toast.makeText(this, "Install succeeded!", Toast.LENGTH_SHORT).show();
                    Log.e(TAG,"Install succeeded!");
                    break;

                case PackageInstaller.STATUS_FAILURE:
                case PackageInstaller.STATUS_FAILURE_ABORTED:
                case PackageInstaller.STATUS_FAILURE_BLOCKED:
                case PackageInstaller.STATUS_FAILURE_CONFLICT:
                case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE:
                case PackageInstaller.STATUS_FAILURE_INVALID:
                case PackageInstaller.STATUS_FAILURE_STORAGE:
                    Toast.makeText(this, "Install failed! " + status + ", " + message,
                            Toast.LENGTH_SHORT).show();
                    Log.e(TAG,"Install failed! " + status + ", " + message);
                    break;
                default:
                    Toast.makeText(this, "Unrecognized status received from installer: " + status,
                            Toast.LENGTH_SHORT).show();
                    Log.e(TAG,"Unrecognized status received from installer: " + status);
            }
        }
        else if(PACKAGE_UNINSTALLED_ACTION.equals(intent.getAction())){
            Log.e(TAG, intent.getAction());
            int status = extras.getInt(PackageInstaller.EXTRA_STATUS);
            String message = extras.getString(PackageInstaller.EXTRA_STATUS_MESSAGE);

            switch (status) {
                case PackageInstaller.STATUS_PENDING_USER_ACTION:
                    // This test app isn't privileged, so the user has to confirm the install.
                    Intent confirmIntent = (Intent) extras.get(Intent.EXTRA_INTENT);
                    startActivity(confirmIntent);
                    break;

                case PackageInstaller.STATUS_SUCCESS:
                    Toast.makeText(this, "Uninstall succeeded!", Toast.LENGTH_SHORT).show();
                    Log.e(TAG,"Uninstall succeeded!");
                    break;

                case PackageInstaller.STATUS_FAILURE:
                case PackageInstaller.STATUS_FAILURE_ABORTED:
                case PackageInstaller.STATUS_FAILURE_BLOCKED:
                case PackageInstaller.STATUS_FAILURE_CONFLICT:
                case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE:
                case PackageInstaller.STATUS_FAILURE_INVALID:
                case PackageInstaller.STATUS_FAILURE_STORAGE:
                    Toast.makeText(this, "Install failed! " + status + ", " + message,
                            Toast.LENGTH_SHORT).show();
                    Log.e(TAG,"Uninstall failed! " + status + ", " + message);
                    break;
                default:
                    Toast.makeText(this, "Unrecognized status received from installer: " + status,
                            Toast.LENGTH_SHORT).show();
                    Log.e(TAG,"Unrecognized status received from installer: " + status);
            }
        }
    }



    /**
     * 根据包名卸载应用
     *
     * @param packageName
     */
    public void uninstall(String packageName) {
        Intent broadcastIntent = new Intent(this, InstallApkSessionApi.class);
        broadcastIntent.setAction(PACKAGE_UNINSTALLED_ACTION);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
        packageInstaller.uninstall(packageName, pendingIntent.getIntentSender());
    }

此时运行代码,会报错,找不到安装的apk,需要在 AS的main目录下创建一个 assets文件夹,把要安装的apk复制到这里面

AndroidManifest.xml配置

	android:sharedUserId="android.uid.system"  // 需要加这个

    
    <uses-permission
        android:name="android.permission.INSTALL_PACKAGES"
        tools:ignore="ProtectedPermissions" /> 
    <uses-permission android:name="permission.REQUEST_INSTALL_PACKAGES" />
    <uses-permission android:name="permission.REQUEST_DELETE_PACKAGES" />
    <uses-permission
        android:name="android.permission.DELETE_PACKAGES"
        tools:ignore="ProtectedPermissions" /> 
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> 
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_INSTALL_SESSIONS" />
    
        <activity
            android:name=".InstallApkSessionApi"
            android:launchMode="singleTop"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
        activity>

android:sharedUserId="android.uid.system 是为了把apk打包成一个系统apk
android:launchMode=“singleTop” 需要添加,否则会一直创建该Activity

apk运行打包

放到源码目录下重新进行签名

编写Android.mk

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := Test
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := platform
LOCAL_DEX_PREOPT := false

LOCAL_PROGUARD_ENABLED := disabled
include $(BUILD_PREBUILT)

LOCAL_MODULE 需要和apk的名称一致

编译

source build/envsetup.sh
lunch xxx
make Test -j8

安装日志

2023-07-18 15:08:07.602 3897-3897/com.demo.timedemo D/install: click 
2023-07-18 15:08:08.083 3897-3933/com.demo.timedemo D/install: sessionId = 1472497253
2023-07-18 15:08:08.523 3897-3933/com.demo.timedemo D/install: session.commit 
2023-07-18 15:08:12.678 3897-3897/com.demo.timedemo E/install: Intent { act=com.xxx.install flg=0x10000000 cmp=com.demo.timedemo/.InstallApkSessionApi (has extras) }
2023-07-18 15:08:12.678 3897-3897/com.demo.timedemo E/install: com.xxx.install
2023-07-18 15:08:12.679 3897-3897/com.demo.timedemo D/install: status = 0
2023-07-18 15:08:12.761 3897-3897/com.demo.timedemo E/install: Install succeeded!
2023-07-18 15:26:18.102 3897-3897/com.demo.timedemo E/install: Intent { act=com.xxx.uninstall flg=0x10000000 cmp=com.demo.timedemo/.InstallApkSessionApi (has extras) }
2023-07-18 15:26:18.102 3897-3897/com.demo.timedemo E/install: com.xxx.uninstall
2023-07-18 15:26:18.125 3897-3897/com.demo.timedemo E/install: Uninstall succeeded!

你可能感兴趣的:(Android,基础,android,android,studio)