Android 无需root实现APK静默安装

转载请注明出处:http://blog.csdn.net/yyh352091626/article/details/50533137

Android的静默安装似乎是一个很有趣很诱人的东西,但是,用普通做法,如果手机没有root权限的话,似乎很难实现静默安装,因为Android并不提供显示的Intent调用,一般是通过以下方式安装apk:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. Intent intent = new Intent(Intent.ACTION_VIEW);  
  2. intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");  
  3. startActivity(intent);  
但是,这并没有真正的实现静默安装,因为有用户界面,会让用户知道。那么,怎么在后台悄悄的安装APK呢?只能试图去看看Android系统源码正常安装APK的过程,我这边下载的源码是Android5.0系统的,5个G的大小,但是可能由于Android5.0有一些安全方面的更新,跟之前的版本还是有一定的差距的,但是,学会一个之后再去学另一个相似的过程,那就简单许多了,就像学会了C语言,再学Java,也并非什么难事~

Android系统把所有的Permission(权限)依据其潜在风险划分为四个等级,即"normal"、 "dangerous"、 "signature"、 "signatureOrSystem"。APK的安装对应的权限是 INSTALL_PACKAGES,权限等级属于后两者。所以,最终想实现APK的静默安装,必然需要一些特殊的处理,执行安装的这个进程,须为系统进程。

那么,我们就来看看Android自身是如何实现安装APK的。安装的命令是pm install... 我们定位到系统源码的/frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.Java这个文件,他实现了pm命令,我们看runInstall方法,这就是APK的安装过程。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private void runInstall() {  
  2.     int installFlags = 0;  
  3.     int userId = UserHandle.USER_ALL;  
  4.     String installerPackageName = null;  
  5.   
  6.     String opt;  
  7.   
  8.     String originatingUriString = null;  
  9.     String referrer = null;  
  10.     String abi = null;  
  11.   
  12.     while ((opt=nextOption()) != null) {  
  13.         if (opt.equals("-l")) {  
  14.             installFlags |= PackageManager.INSTALL_FORWARD_LOCK;  
  15.         } else if (opt.equals("-r")) {  
  16.             installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;  
  17.         } else if (opt.equals("-i")) {  
  18.             installerPackageName = nextOptionData();  
  19.             if (installerPackageName == null) {  
  20.                 System.err.println("Error: no value specified for -i");  
  21.                 return;  
  22.             }  
  23.         } else if (opt.equals("-t")) {  
  24.             installFlags |= PackageManager.INSTALL_ALLOW_TEST;  
  25.         } else if (opt.equals("-s")) {  
  26.             // Override if -s option is specified.  
  27.             installFlags |= PackageManager.INSTALL_EXTERNAL;  
  28.         } else if (opt.equals("-f")) {  
  29.             // Override if -s option is specified.  
  30.             installFlags |= PackageManager.INSTALL_INTERNAL;  
  31.         } else if (opt.equals("-d")) {  
  32.             installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;  
  33.         } else if (opt.equals("--originating-uri")) {  
  34.             originatingUriString = nextOptionData();  
  35.             if (originatingUriString == null) {  
  36.                 System.err.println("Error: must supply argument for --originating-uri");  
  37.                 return;  
  38.             }  
  39.         } else if (opt.equals("--referrer")) {  
  40.             referrer = nextOptionData();  
  41.             if (referrer == null) {  
  42.                 System.err.println("Error: must supply argument for --referrer");  
  43.                 return;  
  44.             }  
  45.         } else if (opt.equals("--abi")) {  
  46.             abi = checkAbiArgument(nextOptionData());  
  47.         } else if (opt.equals("--user")) {  
  48.             userId = Integer.parseInt(nextOptionData());  
  49.         } else {  
  50.             System.err.println("Error: Unknown option: " + opt);  
  51.             return;  
  52.         }  
  53.     }  
  54.   
  55.     if (userId == UserHandle.USER_ALL) {  
  56.         userId = UserHandle.USER_OWNER;  
  57.         installFlags |= PackageManager.INSTALL_ALL_USERS;  
  58.     }  
  59.   
  60.     final Uri verificationURI;  
  61.     final Uri originatingURI;  
  62.     final Uri referrerURI;  
  63.   
  64.     if (originatingUriString != null) {  
  65.         originatingURI = Uri.parse(originatingUriString);  
  66.     } else {  
  67.         originatingURI = null;  
  68.     }  
  69.   
  70.     if (referrer != null) {  
  71.         referrerURI = Uri.parse(referrer);  
  72.     } else {  
  73.         referrerURI = null;  
  74.     }  
  75.   
  76.     // Populate apkURI, must be present  
  77.     final String apkFilePath = nextArg();  
  78.     System.err.println("\tpkg: " + apkFilePath);  
  79.     if (apkFilePath == null) {  
  80.         System.err.println("Error: no package specified");  
  81.         return;  
  82.     }  
  83.   
  84.     // Populate verificationURI, optionally present  
  85.     final String verificationFilePath = nextArg();  
  86.     if (verificationFilePath != null) {  
  87.         System.err.println("\tver: " + verificationFilePath);  
  88.         verificationURI = Uri.fromFile(new File(verificationFilePath));  
  89.     } else {  
  90.         verificationURI = null;  
  91.     }  
  92.   
  93.     LocalPackageInstallObserver obs = new LocalPackageInstallObserver();  
  94.     try {  
  95.         VerificationParams verificationParams = new VerificationParams(verificationURI,  
  96.                 originatingURI, referrerURI, VerificationParams.NO_UID, null);  
  97.   
  98.         mPm.installPackageAsUser(apkFilePath, obs.getBinder(), installFlags,  
  99.                 installerPackageName, verificationParams, abi, userId); //注意!!最终就是调用这个方法来进行安装的  
  100.   
  101.         synchronized (obs) {  
  102.             while (!obs.finished) {  
  103.                 try {  
  104.                     obs.wait();  
  105.                 } catch (InterruptedException e) {  
  106.                 }  
  107.             }  
  108.             if (obs.result == PackageManager.INSTALL_SUCCEEDED) {  
  109.                 System.out.println("Success");  
  110.             } else {  
  111.                 System.err.println("Failure ["  
  112.                         + installFailureToString(obs)  
  113.                         + "]");  
  114.             }  
  115.         }  
  116.     } catch (RemoteException e) {  
  117.         System.err.println(e.toString());  
  118.         System.err.println(PM_NOT_RUNNING_ERR);  
  119.     }  
  120. }  

知道了这个过程之后,就大概知道怎么做了。既然系统底层把这个API屏蔽了,那就想办法去绕过这层屏蔽,来使用它。首先想到的就是使用AIDL,不知道AIDL这东西的,先问度娘去吧~~在上面的代码中,最终实现安装的那一句话,mPm.installPackageAsUser(...),mPm是个什么东西?不难发现,IPackageManager类型,那么这个类从哪里来?搜寻一下,位于/frameworks/base/core/java/android/content/pm这个包底下,拷贝到我们工程目录底下,包名不能变,只拷贝这一个文件的话,一定是不行了,会报其他的一些aidl找不到,相应地也拷贝过来。Android5.0中,aidl改动还是比较大的,所以要拷贝很多东西过来,还要进行一些改动...我也是花了挺久才改到他没报错。

最终,工程的目录如下所示~~


那么,如何来使用它呢?

1、先获取系统服务android.os.ServiceManager,这个又是隐藏的,怎么办?考验Java水平的时候到了~~没错,用反射机制,来获取ServiceManager类,以及该类里面的方法;

2、有了服务之后,我们就要去拿到IPackageManager这个对象;

3、调用IPackageManager里面的installPackage方法进行安装;

实现代码如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package com.example.autoinstall;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileOutputStream;  
  5. import java.io.IOException;  
  6. import java.io.InputStream;  
  7. import java.io.OutputStream;  
  8. import java.lang.reflect.Method;  
  9.   
  10. import android.app.Activity;  
  11. import android.content.Intent;  
  12. import android.content.pm.IPackageInstallObserver2;  
  13. import android.content.pm.IPackageManager;  
  14. import android.content.pm.VerificationParams;  
  15. import android.net.Uri;  
  16. import android.os.Bundle;  
  17. import android.os.IBinder;  
  18. import android.os.RemoteException;  
  19. import android.view.View;  
  20.   
  21. public class MainActivity extends Activity {  
  22.   
  23.     @Override  
  24.     protected void onCreate(Bundle savedInstanceState) {  
  25.         super.onCreate(savedInstanceState);  
  26.         setContentView(R.layout.activity_main);  
  27.     }  
  28.   
  29.     /** 
  30.      * Button点击事件 
  31.      * @param view 
  32.      */  
  33.     public void install(View view)  
  34.     {  
  35.         String path = "";  
  36.         if (FileUtils.isSdcardReady()) {  
  37.             path = FileUtils.getSdcardPath();  
  38.         } else {  
  39.             path = FileUtils.getCachePath(this);  
  40.         }  
  41.         String fileName = path + "/AidlServerDemo.apk";  
  42.         File file = new File(fileName);  
  43.           
  44.         try {  
  45.             if(!file.exists())  
  46.                 copyAPK2SD(fileName);  
  47.             Uri uri = Uri.fromFile(new File(fileName));  
  48.                         // 通过Java反射机制获取android.os.ServiceManager  
  49.             Class<?> clazz = Class.forName("android.os.ServiceManager");  
  50.             Method method = clazz.getMethod("getService", String.class);  
  51.             IBinder iBinder = (IBinder) method.invoke(null"package");  
  52.             IPackageManager ipm = IPackageManager.Stub.asInterface(iBinder);  
  53.             @SuppressWarnings("deprecation")  
  54.             VerificationParams verificationParams = new VerificationParams(nullnullnull, VerificationParams.NO_UID, null);  
  55.                         // 执行安装(方法及详细参数,可能因不同系统而异)  
  56.             ipm.installPackage(fileName, new PackageInstallObserver(), 2null, verificationParams, "");  
  57.         } catch (Exception e) {  
  58.             // TODO Auto-generated catch block  
  59.             e.printStackTrace();  
  60.         }  
  61.   
  62.     }  
  63.   
  64.     // 用于显示结果  
  65.     class PackageInstallObserver extends IPackageInstallObserver2.Stub {  
  66.   
  67.         @Override  
  68.         public void onUserActionRequired(Intent intent) throws RemoteException {  
  69.             // TODO Auto-generated method stub  
  70.   
  71.         }  
  72.   
  73.         @Override  
  74.         public void onPackageInstalled(String basePackageName, int returnCode, String msg, Bundle extras) throws RemoteException {  
  75.             //returnCode<span style="font-family: Arial, Helvetica, sans-serif;">为1,就是安装成功</span>  
  76.   
  77.   
  78.         }  
  79.     };  
  80.   
  81.     /** 
  82.      * 拷贝assets文件夹的APK插件到SD 
  83.      *  
  84.      * @param strOutFileName 
  85.      * @throws IOException 
  86.      */  
  87.     private void copyAPK2SD(String strOutFileName) throws IOException {  
  88.         FileUtils.createDipPath(strOutFileName);  
  89.         InputStream myInput = this.getAssets().open("AidlServerDemo.apk");  
  90.         OutputStream myOutput = new FileOutputStream(strOutFileName);  
  91.         byte[] buffer = new byte[1024];  
  92.         int length = myInput.read(buffer);  
  93.         while (length > 0) {  
  94.             myOutput.write(buffer, 0, length);  
  95.             length = myInput.read(buffer);  
  96.         }  
  97.         myOutput.flush();  
  98.         myInput.close();  
  99.         myOutput.close();  
  100.     }  
  101. }  

每个版本的系统源码里面的aidl可能会不一样,所以具体调用的方法和参数,还得根据实际情况而定,需要去仔细阅读Pm.java这个文件的源码。

在其他版本可能只需要拷贝这4个文件:PackageManager.java、 IPackageDeleteObserver.aidl 、IPackagerInstallObserver.aidl、 IPackageMoveObserver.aidl

然后,还需在配置清单文件里面添加INSTALL_PACKAGE权限

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <uses-permission android:name="android.permission.INSTALL_PACKAGES"/>  

然后把该应用的uid设置为系统级别的,在manifest标签下添加以下属性

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. android:sharedUserId="android.uid.system"  

仅仅这样的话,还是没法实现静默安装,因为系统并不认为你这个app是系统级别的应用,所以,还应该对该应用的APK进行系统签名(注意:不是那个静默安装的APK,是这个实现静默安装程序的APK)。签名过程如下:

总共需要三个文件:

1、SignApk.jar                      %系统源码%/out/host/Linux-x86/framework/signapk.jar

2、platform.x509.pem          %系统源码%/build/target/product/security/platform.x509.pem

3、platform.pk8                    %系统源码%/build/target/product/security/platform.pk8

打开终端,执行命令 java -jar SignApk.jar platform.x509.pem platform.pk8 未签名APK 签名后APK,例如

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. java -jar SignApk.jar platform.x509.pem  platform.pk8 AutoInstall.apk AutoInstall_new.apk  

之后,把签名过后的APK安装到手机上,打开,点击静默安装,在去程序页看看,发现安装成功~~

       


测试的时候发现一个问题,就是这样的方法生成的APK只有在Android原生系统或者是自己编译的系统中才可以用,因为这样的系统才可以拿到platform.pk8 和platform.x509.pem这两个文件。比如在华为或小米的系统上,就无法安装了。用原始的Android中的key 来签名,程序在模拟器上运行OK(可能会出现重启的情况),不过放到G3手机上安装直接提示"Package ... has no signatures that match those in shared user android.uid.system",第三方Rom这样做也是处于保护系统安全的角度来考虑。 

本文主要是提供了一种实现静默安装的思路,但是具体怎么做到兼容各个系统,举一反三,还需要各位看官去阅读Android系统源码。

另外,由于被墙的缘故,很多人都无法下载系统源码,这里再顺便附上Android5.0系统源码的云盘地址:链接: http://pan.baidu.com/s/1dElkPlZ 密码: 4pue    

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值