Android实践 -- Android静默安装和卸载

App的静默安装和卸载

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

简单思路如下:

  1. 通过反射获得安装接口installPackage和 卸载接口 deletePackage
  2. 在自己的包中引入两个接口IPackageInstallObserverIPackageDeleteObserver的空实现
  3. 调用安装卸载的方法,回调上面的两个接口
  4. 添加权限 <uses-permission android:name="android.permission.DELETE_PACKAGES"/>
    <uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
  5. 进行系统签名
  6. 将应用push到系统中,作为系统应用

PackageManager中的提供的接口如下

  1. 安装接口
// @SystemApi
public abstract void installPackage(
      Uri packageURI, IPackageInstallObserver observer, int flags,
      String installerPackageName);
  1. 卸载接口
// @SystemApi
public abstract void deletePackage(
      String packageName, IPackageDeleteObserver observer, int flags);

引入两个回掉的空实现

在自己应用的工程中新建一个包android.content.pm,并添加两个文件

  • IPackageDeleteObserver.java
package android.content.pm;
public interface IPackageDeleteObserver extends android.os.IInterface {
  public abstract static class Stub extends android.os.Binder implements android.content.pm.IPackageDeleteObserver {
      public Stub() {
          throw new RuntimeException("Stub!");
      }

      public static android.content.pm.IPackageDeleteObserver asInterface(android.os.IBinder obj) {
          throw new RuntimeException("Stub!");
      }

      public android.os.IBinder asBinder() {
          throw new RuntimeException("Stub!");
      }

      public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
              throws android.os.RemoteException {
          throw new RuntimeException("Stub!");
      }
  }

  public abstract void packageDeleted(java.lang.String packageName, int returnCode)
          throws android.os.RemoteException;
}
  • IPackageInstallObserver.java
package android.content.pm;
public interface IPackageInstallObserver extends android.os.IInterface {

  public abstract static class Stub extends android.os.Binder implements android.content.pm.IPackageInstallObserver {
      public Stub() {
          throw new RuntimeException("Stub!");
      }

      public static android.content.pm.IPackageInstallObserver asInterface(android.os.IBinder obj) {
          throw new RuntimeException("Stub!");
      }

      public android.os.IBinder asBinder() {
          throw new RuntimeException("Stub!");
      }

      public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
              throws android.os.RemoteException {
          throw new RuntimeException("Stub!");
      }
  }

  public abstract void packageInstalled(java.lang.String packageName, int returnCode)
          throws android.os.RemoteException;
}

自己定义个接口回调

  • OnPackagedObserver.java
package com.example;
public interface OnPackagedObserver {

  public void packageInstalled(String packageName, int returnCode);
  public void packageDeleted(String packageName,int returnCode);
}

实现方法

  • 反射接口
PackageManager pm = context.getPackageManager();

Class<?>[] types = new Class[] {Uri.class, IPackageInstallObserver.class, int.class, String.class};
Class<?>[] uninstalltypes = new Class[] {String.class, IPackageDeleteObserver.class, int.class};

  Method method = pm.getClass().getMethod("installPackage", types);
  Method uninstallmethod = pm.getClass().getMethod("deletePackage", uninstalltypes);
  • 实现回调接口
private OnPackagedObserver onInstalledPackaged;
class PackageInstallObserver extends IPackageInstallObserver.Stub {

public void packageInstalled(String packageName, int returnCode) throws RemoteException {
  if (onInstalledPackaged != null) {
    onInstalledPackaged.packageInstalled(packageName, returnCode);
  }
}
}

class PackageDeleteObserver extends IPackageDeleteObserver.Stub {

public void packageDeleted(String packageName, int returnCode) throws RemoteException {
  if (onInstalledPackaged != null) {
    onInstalledPackaged.packageDeleted(packageName, returnCode);
  }
}
}
  • 实现
    卸载接口只需要提供要卸载的应用的包名packagename ,安装提供了三个接口
public void uninstallPackage(String packagename) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
    uninstallmethod.invoke(pm, new Object[] {packagename, observerdelete, 0});
}
public void installPackage(String apkFile) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
  installPackage(new File(apkFile));
}

public void installPackage(File apkFile) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
  if (!apkFile.exists()) throw new IllegalArgumentException();
  Uri packageURI = Uri.fromFile(apkFile);
  installPackage(packageURI);
}

public void installPackage(Uri apkFile) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
  method.invoke(pm, new Object[] {apkFile, observer, INSTALL_REPLACE_EXISTING, null});
}

签名

生成一个apk文件,需要对这个apk文件进行系统签名,由于<uses-permission android:name="android.permission.DELETE_PACKAGES"/>
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/> 是系统应用需要的权限,在开发应用时,如果加载AndroidManifest.xml
中会编译不过,需要先用工具 apktool 工具先把apk文件解压出来,用编辑器在AndroidManifest.xml中加入上面的两个权限,然后在用工具apktool重新打包

具体的使用方法参考 [Android实践 -- Apktool 的使用](http://www.jianshu.com/p/1896307da564)
  • 解压
apktool d test.apk
  • 修改之后,重新打包
apktool b test

签名之后的文件,需要在进行系统签名
具体的使用方法请参考 Android实践 -- 对apk进行系统签名

java -jar signapk.jar platform.x509.pem platform.pk8 test.apk test_signed.apk

将签名之后的文件 push到手机中,需要root权限

具体的代码实现

源码

附录,安装卸载错误码速查

回调中的returnCodePackageManager中的相关的定义如下:

  • 安装错误码
public static final int INSTALL_SUCCEEDED = 1;
public static final int INSTALL_FAILED_ALREADY_EXISTS = -1;
public static final int INSTALL_FAILED_INVALID_APK = -2;
public static final int INSTALL_FAILED_INVALID_URI = -3;
public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4;
public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5;
public static final int INSTALL_FAILED_NO_SHARED_USER = -6;
public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7;
public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8;
public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9;
public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10;
public static final int INSTALL_FAILED_DEXOPT = -11;
public static final int INSTALL_FAILED_OLDER_SDK = -12;
public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13;
public static final int INSTALL_FAILED_NEWER_SDK = -14;
public static final int INSTALL_FAILED_TEST_ONLY = -15;
public static final int INSTALL_FAILED_CPU_ABI_INCOMPATIBLE = -16;
public static final int INSTALL_FAILED_MISSING_FEATURE = -17;
public static final int INSTALL_FAILED_CONTAINER_ERROR = -18;
public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19;
public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20;
public static final int INSTALL_FAILED_VERIFICATION_TIMEOUT = -21;
public static final int INSTALL_FAILED_VERIFICATION_FAILURE = -22;
public static final int INSTALL_FAILED_PACKAGE_CHANGED = -23;
public static final int INSTALL_FAILED_UID_CHANGED = -24;
public static final int INSTALL_FAILED_VERSION_DOWNGRADE = -25;
public static final int INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE = -26;
public static final int INSTALL_PARSE_FAILED_NOT_APK = -100;
public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101;
public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102;
public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103;
public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104;
public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105;
public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106;
public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107;
public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108;
public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109;
public static final int INSTALL_FAILED_INTERNAL_ERROR = -110;
public static final int INSTALL_FAILED_USER_RESTRICTED = -111;
public static final int INSTALL_FAILED_DUPLICATE_PERMISSION = -112;
public static final int INSTALL_FAILED_NO_MATCHING_ABIS = -113;
public static final int NO_NATIVE_LIBRARIES = -114;
public static final int INSTALL_FAILED_ABORTED = -115;
  • 卸载错误码
public static final int DELETE_SUCCEEDED = 1;
public static final int DELETE_FAILED_INTERNAL_ERROR = -1;
public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2;
public static final int DELETE_FAILED_USER_RESTRICTED = -3;
public static final int DELETE_FAILED_OWNER_BLOCKED = -4;
public static final int DELETE_FAILED_ABORTED = -5;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,570评论 25 707
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,209评论 0 17
  • APK安装流程系列文章整体内容如下: APK安装流程详解0——前言APK安装流程详解1——有关"安装ing"的实体...
    隔壁老李头阅读 8,332评论 3 26
  • 没有你,良辰美景更与何人说?没有你,我变得会说会笑,浑身带着骄傲! 初见你,是个夏天;又见你,已是初秋。 “嘿,好...
    Z霖阅读 434评论 2 1