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

Android 9 (P)开发适配指南系列博客目录:

Adnroid 9 (P) recovery升级Map of '@/cache/recovery/block.map’failed问题分析指南
Android 9 (P)版本解决VNDK library: XXX’s ABI has EXTENDING CHANGES
Android 9 (P)非SDK API限制调用开发指南
Android 9 (P)适配以太网功能开发指南
Android 9 (P)在user模式下无法使用fastboot烧录怎么破
Android 9 (P)静默安装/卸载App适配终极指南


引言

   公司最近上马了Android 9和10的平台,我们也得哼哧哼哧的进行相关的开发。我只能说谷歌的工程师为了KPI考核对Android修改的老开心了,可苦了我们啊。这不今天在进行Android的静默安装的API封装,尼玛原来的相关接口都没有了。那么今天要说的就是在Android P上面怎么实施静默安装/卸载接口的封装。这个感觉直接上代码有点太残暴了,还是先分析下基本面,然后上封装好的接口比较好。


一. 前期知识准备

在开始上代码之前,让我们来先说说Android P的安装和卸载原理:
Android P 上采用类似 socket 的方式与 server 端通信完成安装,其中Session是重点

其中有如下几个点是我们要重点关注的如下:

PackageInstaller

  • 根据官方文档,PackageInstaller 提供了安装、更新以及卸载等功能,其中包括单 APK 和多 APK 安装。
    具体的安装行为是通过 PackageInstaller 内部的 Session 完成的。所有的应用都有权限创建这个 Session,但是可能会需要用户的确认才能完成安装(权限不足)。

Session

  • 创建 Session 可以为其指定参数 SessionParams,其中一个作用就是要全部替换还是局部替换 MODE_FULL_INSTALL 和 MODE_INHERIT_EXISTING

如何安装?

  • 通过 IO 流的方式向 Session 内输送 apk 数据。具体代码可以看下文。需要注意的是,PackageInsatller 对于安装结果回调没有采用普通的函数回调,而是采用 Intent 的方式完成回调,比如 广播等(可以参考下面两个实例)。

二. 开干

静默安装时比较高的权限,一般应用是不能的,所以必须具备system权限,这个是前提。好了不多说啥了,直接上代码。

2.1 实例代码

/** Copyright (C) 2017 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.xxx.android9_api;// Need the following import to get access to the app resources, since this
// class is in a sub-package.
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageInstaller;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;/*** Demonstration of package installation and uninstallation using the package installer Session* API.** @see InstallApk for a demo of the original (non-Session) API.*/
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";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.install_apk_session_api);// Watch for button clicks.Button button = (Button) findViewById(R.id.install);button.setOnClickListener(new OnClickListener() {public void onClick(View v) {PackageInstaller.Session session = null;try {//获取PackageInstaller对象PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);//创建一个Sessionint sessionId = packageInstaller.createSession(params);//建立和PackageManager的socket通道,Android中的通信不仅仅有Binder还有很多其它的session = packageInstaller.openSession(sessionId);//将App的内容通过session传输addApkToInstallSession("HelloActivity.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).//开启安装session.commit(statusReceiver);} catch (IOException e) {throw new RuntimeException("Couldn't install package", e);} catch (RuntimeException e) {if (session != null) {session.abandon();}throw e;}}});Button uninstall = (Button)findViewById(R.id.uninstall);uninstall.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View arg0) {// TODO Auto-generated method stubuninstall("com.example.android.helloactivity");}});}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@Overrideprotected 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);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());}}

2.2 代码分析

好了实例代码,已经编写OK,让我们简单分析一下,及其步骤:
安装流程

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

卸载流程:比较简单就不细述了。

这里需要注意的是这个测试Activiyt的Mode必须是此处一定要运行单例模式或者singleTop模式,否则会一直创建该Activity。

2.3 运行实例演示

运行实例,静默安装和卸载成功。

msm8953_64:/ # logcat  -s install
--------- beginning of main
--------- beginning of system
01-15 02:10:45.258  7087  7087 E install : Intent { act=com.xxx.install flg=0x10000000 cmp=com.xxx.android9_api/.InstallApkSessionApi (has extras) }
01-15 02:10:45.259  7087  7087 E install : com.xxx.install
01-15 02:10:45.304  7087  7087 E install : Install succeeded!
01-15 02:10:48.340  7087  7087 E install : Intent { act=com.xxx.uninstall flg=0x10000000 cmp=com.xxx.android9_api/.InstallApkSessionApi (has extras) }
01-15 02:10:48.340  7087  7087 E install : com.xxx.uninstall
01-15 02:10:48.372  7087  7087 E install : Uninstall succeeded!


三.进阶版本

上边的是通过广播异步的完成静默安装和卸载,那么有没有同步的呢!我这暴脾气就不信了,没有同步的方式可以做到。我头悬梁,锥刺股终于实现了。下面提供一下我封装的接口,供各位参考,希望能对大家有帮助。

package com.xxxdroid.compat;import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;import android.annotation.SuppressLint;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;public class PackageMgrCompat {private static final String TAG = "PackageMgrCompat_v28_later";private Context mContext;private PackageManager mPackageManager;public PackageMgrCompat(Context context) {mContext = context;mPackageManager = context.getPackageManager();}@SuppressLint("InlinedApi")public boolean deletePackage(String pkgName, int unInstallFlags) {final LocalIntentReceiver localReceiver = new LocalIntentReceiver();getPi().uninstall(pkgName,unInstallFlags | PackageManager.DELETE_ALL_USERS,localReceiver.getIntentSender());final Intent result = localReceiver.getResult();synchronized (localReceiver) {final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,PackageInstaller.STATUS_FAILURE);if (status != PackageInstaller.STATUS_SUCCESS) {Log.e(TAG, "UnInstallation should have succeeded, but got code "+ status);return false;} else {Log.e(TAG, "UnInstallation  have succeeded");return true;}}}@SuppressLint("InlinedApi")public int installPackage(File apkFilePath) {Log.w(TAG, "installPackage pkg: " + apkFilePath);PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);PackageInstaller.Session session = null;// 创建一个Sessiontry {int sessionId = getPi().createSession(params);// 建立和PackageManager的socket通道,Android中的通信不仅仅有Binder还有很多其它的session = getPi().openSession(sessionId);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();return PackageManager.INSTALL_FAILED_INVALID_APK;}addApkToInstallSession(apkFilePath, session);final LocalIntentReceiver localReceiver = new LocalIntentReceiver();session.commit(localReceiver.getIntentSender());final Intent result = localReceiver.getResult();synchronized (localReceiver) {final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,PackageInstaller.STATUS_FAILURE);if (session != null) {session.close();}if (status != PackageInstaller.STATUS_SUCCESS) {Log.e(TAG, "Installation should have succeeded, but got code "+ status);return status;} else {Log.e(TAG, "Installation  have succeeded");return status;}}}private boolean addApkToInstallSession(File apkFilePath,PackageInstaller.Session session) {InputStream in = null;OutputStream out = null;boolean success = false;try {out = session.openWrite("base.apk", 0, apkFilePath.length());in = new FileInputStream(apkFilePath);int total = 0, c;byte[] buffer = new byte[1024 * 1024];while ((c = in.read(buffer)) != -1) {total += c;out.write(buffer, 0, c);}session.fsync(out);Log.d(TAG, "streamed " + total + " bytes");success = true;} catch (IOException e) {e.printStackTrace();} finally {if (null != session) {session.close();}try {if (null != out) {out.close();}if (null != in) {in.close();}} catch (IOException e) {e.printStackTrace();}}return success;}private static class LocalIntentReceiver {private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {@Overridepublic void send(int code, Intent intent, String resolvedType,IBinder whitelistToken, IIntentReceiver finishedReceiver,String requiredPermission, Bundle options) {try {mResult.offer(intent, 5, TimeUnit.SECONDS);} catch (InterruptedException e) {throw new RuntimeException(e);}}};public IntentSender getIntentSender() {return new IntentSender((IIntentSender) mLocalSender);}public Intent getResult() {try {return mResult.take();} catch (InterruptedException e) {throw new RuntimeException(e);}}}private PackageManager getPm() {return mContext.getPackageManager();}private PackageInstaller getPi() {return getPm().getPackageInstaller();}
}

3.2 运行实例演示

运行实例,静默安装和卸载成功。

λ adb logcat  -s PackageMgrCompat_v28_later
--------- beginning of main
--------- beginning of system
01-15 07:59:49.035  6251  6251 W PackageMgrCompat_v28_later: installPackage pkg: /data/HelloActivity.apk
01-15 07:59:49.329  6251  6251 D PackageMgrCompat_v28_later: streamed 12770 bytes
01-15 07:59:50.801  6251  6251 E PackageMgrCompat_v28_later: Installation  have succeeded
01-15 07:59:53.111  6251  6251 E PackageMgrCompat_v28_later: deletePackage not implemented yet!
01-15 07:59:53.593  6251  6251 E PackageMgrCompat_v28_later: UnInstallation  have succeeded

写在最后

文章至此,关于Android P静默安装和卸载的封装就结束了,如果想更详细的了解那么就只能跟读Android FrameWork的源码了。我也在跟进中,各位如果有兴趣也可以一起聊聊。如果对各位有帮助的话麻烦点个赞或者拍个砖。

Android 9 P静默安装/卸载App适配终极指南相关推荐

  1. android fota服务商,android开发实现静默安装(fota升级)

    android开发实现静默安装(root权限) 方式是将应用设置为内置的系统应用,注意事system/app目录下面,采用copy2SystemApp()方法就可以,注意chmod 777的权限,若是 ...

  2. APP静默安装卸载管理器实现与上架到应用宝和豌豆荚

    APP静默安装卸载管理器是本花了两天时间开发的第一个上架到应用宝和豌豆荚Android App 程序, 并集成了豌豆荚广告SDK, 腾讯广告SDK还在审批中.APP静默安装卸载管理器主要包含如下功能 ...

  3. 静默删除oracle,静默安装卸载 ORACLE

    静默安装: 使用 dbca.rsp 模板 # 修改第78 行的全局数据库的名字=SID+主机域名 DBNAME="orcl.sczq" # 修改第149行的ORACLE SID S ...

  4. android studio AVD模拟器安装某些app出现 “app not installed(未安装应用程序)”的问题

    android studio AVD模拟器安装某些app出现 "app not installed(未安装应用程序)"的问题 参考: 安装apk时出现INSTALL_FAILED_ ...

  5. android_ android apk analyzer(libchecker apk分析器):分析Android手机上已安装的app(库/基础组件分析/开发技术)/从酷安市场下载安装包

    android apk analyzer(libchecker apk分析器):分析Android手机上已安装的app(库/基础组件分析/开发技术-) download app(apk) Releas ...

  6. 记录开发经历-----Android静默安装卸载

    App的静默安装和卸载(有系统签名) Android系统本身提供了安装卸载功能,但是api接口是@hide的,不是公开的接口,所以在应用级别是无法实现静默安装和卸载的,要实现静默安装和卸载需要是系统应 ...

  7. android 获取已安装 错误代码,android获取手机已经安装的app信息

    Android获取手机已安装APP(系统/非系统) 效果图 主体代码 private ListView mlistview; private ListpackageInfoList; private ...

  8. android实现后台静默安装,Android 静默安装实现方法

    Android静默安装的方法,静默安装就是绕过安装程序时的提示窗口,直接在后台安装. 注意:静默安装的前提是设备有ROOT权限. 代码如下: /** * 静默安装 * @param file * @r ...

  9. android 10.0 预制不可卸载app(RK 展讯 MTK平台都适用)

    1.概述 在10.0的系统产品进行定制化开发新手来说,对怎么预制apk感觉很陌生,但是也很简单的,可以把预制的apk放在vendor 目录下 也可以放在package/app下 配置好mk 让系统能编 ...

最新文章

  1. @Component、@Repository、@Service、@Controller区别
  2. 机器学习(五)——缓解过拟合
  3. .Net连接Sybase数据库的几种方法[转]
  4. 文字投影_店铺门口投影灯,引领店铺新潮流
  5. php脚本启动,有没有办法启动一个PHP脚本并获得状态?
  6. java 数据库操作教程_java操作数据库的基本方法
  7. 华为交换机端口不配置access_华为交换机如何配置端口组?华为交换机端口组-百度经验...
  8. 一行 Python 代码能实现有趣功能
  9. 大一c语言要学什么,c语言学习计划
  10. 关于流媒体压缩的问题xvidcore的问题
  11. VScode 下载、安装和设置中文界面
  12. Google play谷歌应用商店 APP上包上架的一些策略和技巧
  13. IntelliJ IDEA 汉化包-支持2018和2019版本
  14. 服务器网站dns服务器,国内各个域名服务商主要dns服务器
  15. 机器视觉编码靶标自动提取和解码Coded Target/  Marker Detector
  16. 《wifi加密破解论文》翻译介绍-wifi不再安全
  17. 2022电子邮箱大全,国内企业邮箱注册大全有哪些?
  18. [DZ X2.5实用教程] DZ X2.5(Discuz!)论坛-QQ企业OR域名邮箱作为发信邮箱设置教程
  19. 华为KubeEdge在边缘计算的实践
  20. j2ee开发的各种技术

热门文章

  1. scratch 学习网址:
  2. CSS - label文字自动换行
  3. 关于74LS04反相器振荡电路调试的一些问题
  4. 双 JK 触发器 74LS112 逻辑功能。真值表_数电实验 | 时序逻辑电路
  5. GDR-Net: Geometry-Guided Direct Regression Network for Monocular 6D Object Pose Estimati
  6. Neo4j 新手入门指南
  7. 【NLP】文本分类TorchText实战-AG_NEWS 新闻主题分类任务(PyTorch版)
  8. sql 纵向求和_SQL语句(行列转换以及字符串求和)
  9. 59 Three.js 渲染两个场景和使用不同的相机,渲染在一个场景里面
  10. GPU CPU NPU