Android三方应用实现静默安装

code小生,一个专注Android领域的技术平台

作者:最忆是深秋
地址:https://www.jianshu.com/p/681016514bc4
声明:本文已获最忆是深秋授权,转发等请联系原作者授权

背景

一个三方应用想要内置进我们 ROM,并且想要能够静默安装应用。

App的静默安装和卸载

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

思路1

  1. 通过反射获得安装接口installPackage和 卸载接口 deletePackage

  2. 在自己的包中引入两个接口IPackageInstallObserver和IPackageDeleteObserver的空实现

  3. 调用安装卸载的方法,回调上面的两个接口

  4. 添加权限

<uses-permission android:name="android.permission.DELETE_PACKAGES"/>
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>

进行系统签名

  1. 将应用push到系统中,作为系统应用

在PackageManager中的提供的接口如下:

    /**
     * @deprecated replaced by {@link PackageInstaller}
     * @hide
     */

    @Deprecated
    public abstract void installPackage(
            Uri packageURI,
            PackageInstallObserver observer,
            @InstallFlags int flags,
            String installerPackageName)
;
    /**
     * Attempts to delete a package. Since this may take a little while, the
     * result will be posted back to the given observer. A deletion will fail if
     * the calling context lacks the
     * {@link android.Manifest.permission#DELETE_PACKAGES} permission, if the
     * named package cannot be found, or if the named package is a system
     * package.
     *
     * @param packageName The name of the package to delete
     * @param observer An observer callback to get notified when the package
     *            deletion is complete.
     *            {@link android.content.pm.IPackageDeleteObserver#packageDeleted}
     *            will be called when that happens. observer may be null to
     *            indicate that no callback is desired.
     * @hide
     */

    @RequiresPermission(Manifest.permission.DELETE_PACKAGES)
    public abstract void deletePackage(String packageName, IPackageDeleteObserver observer,
            @DeleteFlags 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

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

代码调用

  • 反射调用

    public static final int INSTALL_REPLACE_EXISTING = 0x00000002//如果已经存在的包使用这个flag
    public static final int INSTALL_ALL_USERS = 0x00000040;

    PackageManager pm = getPackageManager();
    Class<?>[] types = new Class[] {Uri.class, IPackageInstallObserver.class, int.class, String.class};
    try {
         Method method = pm.getClass().getMethod("installPackage", types);
         method.invoke(pm,Uri.fromFile(file), new PackageInstallObserver(), INSTALL_ALL_USERS, null);
     }catch (Exception e){
          e.printStackTrace();
     }

    Class<?>[] uninstalltypes = new Class[] {String.class, IPackageDeleteObserver.class, int.class};
    try {
         Method uninstallmethod = pm.getClass().getMethod("deletePackage", uninstalltypes);
         uninstallmethod.invoke(pm, "your packagename"new PackageDeleteObserver(), 0);
    }catch (Exception e){
          e.printStackTrace();
    }
  • 接口实现

    private OnPackagedObserver onInstallOrDeleteObserver = new OnPackagedObserver() {
        @Override
        public void onPackageInstalled(String packageName, int returnCode) {
            Log.d("test","onPackageInstalled");
        }

        @Override
        public void onPackageDeleted(String packageName, int returnCode) {
            Log.d("test","onPackageDeleted");
        }
    };

    class PackageInstallObserver extends IPackageInstallObserver.Stub {

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

    class PackageDeleteObserver extends IPackageDeleteObserver.Stub {

        public void packageDeleted(String packageName, int returnCode) throws RemoteException {
            if (onInstallOrDeleteObserver != null) {
                onInstallOrDeleteObserver.onPackageDeleted(packageName, returnCode);
            }
        }
    }

签名
生成一个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 重新打包

  • 反编译(解压)

apktool  d -f test.apk
  • 修改 AndroidManifest.xml 加入权限声明之后重新打包

apktool  b  test.apk

重新打包的时候很有可能会报错,仔细看下报错的位置,在解压后的文件中找到处理掉,该删的删。这一步成功后我们得到了一个 AndroidManifest中声明了权限的 apk, 这个时候这个apk是没有进行签名的,安装不了。需要再进行系统签名:

java -jar signapk.jar platform.x509.pem platform.pk8 unsign.apk signed.apk

signapk.jar 位于 out/host/linux-86/framework/signapk.jar
platform.x509.pem platform.pk8 位于 build/target/product/security/platform.x509.pem, platform.pk8
最后生成的apk就是已经进行系统签名的apk

思路1 参考博客 https://www.jianshu.com/p/8c7da720062d

按照思路1的做法,我只做到了重新打包,生成了添加了权限后的apk文件,但是再使用上述方法给apk进行签名的时候始终报错,最后是采用mk编译的方法签名成功的。

思路2

其实归根结底,还是三方应用无法拿到 INSTALL_PACKAGES 和DELETE_PACKAGES权限,在AndroidManifest文件中声明的时候会编译不过。那么就可以在framework中权限检测的地方对这个特定包名赋予权限。这样即使没有在AndroidManifest中声明,也可以拿到权限。这样的话,思路1的4,5步操作即可省略。

07-18 11:48:26.705: W/System.err(12203): Caused by: java.lang.SecurityException: Neither user 10124 nor current process has android.permission.INSTALL_PACKAGES.
07-18 11:48:26.705: W/System.err(12203):    at android.os.Parcel.readException(Parcel.java:2004)
07-18 11:48:26.705: W/System.err(12203):    at android.os.Parcel.readException(Parcel.java:1950)
07-18 11:48:26.705: W/System.err(12203):    at android.content.pm.IPackageManager$Stub$Proxy.installPackageAsUser(IPackageManager.java:4092)
07-18 11:48:26.705: W/System.err(12203):    at android.app.ApplicationPackageManager.installCommon(ApplicationPackageManager.java:1828)
07-18 11:48:26.705: W/System.err(12203):    at android.app.ApplicationPackageManager.installPackage(ApplicationPackageManager.java:1809)
07-18 11:48:26.705: W/System.err(12203):    ... 11 more

根据异常的 log跟一下 framework 的代码,最终在AMS的checkComponentPermission方法中来做:

    int checkComponentPermission(String permission, int pid, int uid,
            int owningUid, boolean exported)
 
{
        if (pid == MY_PID) {
            return PackageManager.PERMISSION_GRANTED;
        }
       .............
        if ("android.permission.INSTALL_PACKAGES".equals(permission)) {
            try {
                String callingPkgName = AppGlobals.getPackageManager().getNameForUid(uid);
                if ("your  packagename".equals(callingPkgName)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            } catch (Exception e) {
                e.printStackTrace();
                Log.w(TAG, "install packages getNameForUid exception");
            }

        }
      ..............
      ..............
    }

这样就好啦,省去了 反编译 和 重新打包 以及 重新签名 的过程。

附录

  • Android 8.1 Delete Package返回码对照表

    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;
    public static final int DELETE_FAILED_USED_SHARED_LIBRARY = -6;
  • Android 8.1 Install Package返回码对照表

    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_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_FAILED_SANDBOX_VERSION_DOWNGRADE = -27;
    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 INSTALL_FAILED_INSTANT_APP_INVALID = -116;

小结

据我自己实操:

  • 思路1
    在重新打包 和 系统签名 的过程中都遇到了问题,重新打包的后来把报错的地方删掉打包成功了,但是系统签名按照上面 shell 方式签名始终没有成功。虽然最后使用 mk 方式,源码环境下编译签名成功了,但是思路1 的整个过程还是比较坎坷的

  • 思路2
    比较方便,基本没走弯路,相当于是 framework 给开了口子。

总而言之,在没有和 ROM 厂家合作的情况下,三方应用是不可能实现静默安装的。要么让它成为系统应用,要么让 rom 厂家在 framework 中开口子~

推荐阅读

程序员如何选择未来的职业路线

瓶颈:没有技术深度的苦恼

一个老程序员的建议

640

分享技术我是认真的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值