欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android无需root实现apk的静默安装

程序员文章站 2023-12-21 21:12:58
android的静默安装似乎是一个很有趣很诱人的东西,但是,用普通做法,如果手机没有root权限的话,似乎很难实现静默安装,因为android并不提供显示的intent调用...

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

intent intent = new intent(intent.action_view); 
intent.setdataandtype(uri.fromfile(file), "application/vnd.android.package-archive"); 
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的安装过程。

private void runinstall() { 
 int installflags = 0; 
 int userid = userhandle.user_all; 
 string installerpackagename = null; 
 
 string opt; 
 
 string originatinguristring = null; 
 string referrer = null; 
 string abi = null; 
 
 while ((opt=nextoption()) != null) { 
  if (opt.equals("-l")) { 
   installflags |= packagemanager.install_forward_lock; 
  } else if (opt.equals("-r")) { 
   installflags |= packagemanager.install_replace_existing; 
  } else if (opt.equals("-i")) { 
   installerpackagename = nextoptiondata(); 
   if (installerpackagename == null) { 
    system.err.println("error: no value specified for -i"); 
    return; 
   } 
  } else if (opt.equals("-t")) { 
   installflags |= packagemanager.install_allow_test; 
  } else if (opt.equals("-s")) { 
   // override if -s option is specified. 
   installflags |= packagemanager.install_external; 
  } else if (opt.equals("-f")) { 
   // override if -s option is specified. 
   installflags |= packagemanager.install_internal; 
  } else if (opt.equals("-d")) { 
   installflags |= packagemanager.install_allow_downgrade; 
  } else if (opt.equals("--originating-uri")) { 
   originatinguristring = nextoptiondata(); 
   if (originatinguristring == null) { 
    system.err.println("error: must supply argument for --originating-uri"); 
    return; 
   } 
  } else if (opt.equals("--referrer")) { 
   referrer = nextoptiondata(); 
   if (referrer == null) { 
    system.err.println("error: must supply argument for --referrer"); 
    return; 
   } 
  } else if (opt.equals("--abi")) { 
   abi = checkabiargument(nextoptiondata()); 
  } else if (opt.equals("--user")) { 
   userid = integer.parseint(nextoptiondata()); 
  } else { 
   system.err.println("error: unknown option: " + opt); 
   return; 
  } 
 } 
 
 if (userid == userhandle.user_all) { 
  userid = userhandle.user_owner; 
  installflags |= packagemanager.install_all_users; 
 } 
 
 final uri verificationuri; 
 final uri originatinguri; 
 final uri referreruri; 
 
 if (originatinguristring != null) { 
  originatinguri = uri.parse(originatinguristring); 
 } else { 
  originatinguri = null; 
 } 
 
 if (referrer != null) { 
  referreruri = uri.parse(referrer); 
 } else { 
  referreruri = null; 
 } 
 
 // populate apkuri, must be present 
 final string apkfilepath = nextarg(); 
 system.err.println("\tpkg: " + apkfilepath); 
 if (apkfilepath == null) { 
  system.err.println("error: no package specified"); 
  return; 
 } 
 
 // populate verificationuri, optionally present 
 final string verificationfilepath = nextarg(); 
 if (verificationfilepath != null) { 
  system.err.println("\tver: " + verificationfilepath); 
  verificationuri = uri.fromfile(new file(verificationfilepath)); 
 } else { 
  verificationuri = null; 
 } 
 
 localpackageinstallobserver obs = new localpackageinstallobserver(); 
 try { 
  verificationparams verificationparams = new verificationparams(verificationuri, 
    originatinguri, referreruri, verificationparams.no_uid, null); 
 
  mpm.installpackageasuser(apkfilepath, obs.getbinder(), installflags, 
    installerpackagename, verificationparams, abi, userid); //注意!!最终就是调用这个方法来进行安装的 
 
  synchronized (obs) { 
   while (!obs.finished) { 
    try { 
     obs.wait(); 
    } catch (interruptedexception e) { 
    } 
   } 
   if (obs.result == packagemanager.install_succeeded) { 
    system.out.println("success"); 
   } else { 
    system.err.println("failure [" 
      + installfailuretostring(obs) 
      + "]"); 
   } 
  } 
 } catch (remoteexception e) { 
  system.err.println(e.tostring()); 
  system.err.println(pm_not_running_err); 
 } 
} 

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

Android无需root实现apk的静默安装

那么,如何来使用它呢?

  • 1、先获取系统服务android.os.servicemanager,这个又是隐藏的,怎么办?考验java水平的时候到了~~没错,用反射机制,来获取servicemanager类,以及该类里面的方法;
  • 2、有了服务之后,我们就要去拿到ipackagemanager这个对象;
  • 3、调用ipackagemanager里面的installpackage方法进行安装;

实现代码如下:

package com.example.autoinstall; 
 
import java.io.file; 
import java.io.fileoutputstream; 
import java.io.ioexception; 
import java.io.inputstream; 
import java.io.outputstream; 
import java.lang.reflect.method; 
 
import android.app.activity; 
import android.content.intent; 
import android.content.pm.ipackageinstallobserver2; 
import android.content.pm.ipackagemanager; 
import android.content.pm.verificationparams; 
import android.net.uri; 
import android.os.bundle; 
import android.os.ibinder; 
import android.os.remoteexception; 
import android.view.view; 
 
public class mainactivity extends activity { 
 
 @override 
 protected void oncreate(bundle savedinstancestate) { 
  super.oncreate(savedinstancestate); 
  setcontentview(r.layout.activity_main); 
 } 
 
 /** 
  * button点击事件 
  * @param view 
  */ 
 public void install(view view) 
 { 
  string path = ""; 
  if (fileutils.issdcardready()) { 
   path = fileutils.getsdcardpath(); 
  } else { 
   path = fileutils.getcachepath(this); 
  } 
  string filename = path + "/aidlserverdemo.apk"; 
  file file = new file(filename); 
   
  try { 
   if(!file.exists()) 
    copyapk2sd(filename); 
   uri uri = uri.fromfile(new file(filename)); 
      // 通过java反射机制获取android.os.servicemanager 
   class<?> clazz = class.forname("android.os.servicemanager"); 
   method method = clazz.getmethod("getservice", string.class); 
   ibinder ibinder = (ibinder) method.invoke(null, "package"); 
   ipackagemanager ipm = ipackagemanager.stub.asinterface(ibinder); 
   @suppresswarnings("deprecation") 
   verificationparams verificationparams = new verificationparams(null, null, null, verificationparams.no_uid, null); 
      // 执行安装(方法及详细参数,可能因不同系统而异) 
   ipm.installpackage(filename, new packageinstallobserver(), 2, null, verificationparams, ""); 
  } catch (exception e) { 
   // todo auto-generated catch block 
   e.printstacktrace(); 
  } 
 
 } 
 
 // 用于显示结果 
 class packageinstallobserver extends ipackageinstallobserver2.stub { 
 
  @override 
  public void onuseractionrequired(intent intent) throws remoteexception { 
   // todo auto-generated method stub 
 
  } 
 
  @override 
  public void onpackageinstalled(string basepackagename, int returncode, string msg, bundle extras) throws remoteexception { 
   //returncode<span style="font-family: arial, helvetica, sans-serif;">为1,就是安装成功</span> 
 
 
  } 
 }; 
 
 /** 
  * 拷贝assets文件夹的apk插件到sd 
  * 
  * @param stroutfilename 
  * @throws ioexception 
  */ 
 private void copyapk2sd(string stroutfilename) throws ioexception { 
  fileutils.createdippath(stroutfilename); 
  inputstream myinput = this.getassets().open("aidlserverdemo.apk"); 
  outputstream myoutput = new fileoutputstream(stroutfilename); 
  byte[] buffer = new byte[1024]; 
  int length = myinput.read(buffer); 
  while (length > 0) { 
   myoutput.write(buffer, 0, length); 
   length = myinput.read(buffer); 
  } 
  myoutput.flush(); 
  myinput.close(); 
  myoutput.close(); 
 } 
} 

每个版本的系统源码里面的aidl可能会不一样,所以具体调用的方法和参数,还得根据实际情况而定,需要去仔细阅读pm.java这个文件的源码。
在其他版本可能只需要拷贝这4个文件:packagemanager.java、 ipackagedeleteobserver.aidl 、ipackagerinstallobserver.aidl、 ipackagemoveobserver.aidl
然后,还需在配置清单文件里面添加install_package权限

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

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

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,例如
java -jar signapk.jar platform.x509.pem  platform.pk8 autoinstall.apk autoinstall_new.apk 

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

       Android无需root实现apk的静默安装

 Android无需root实现apk的静默安装

更多内容可以参考专题《android安装配置教程》进行学习。

本文主要是提供了一种实现静默安装的思路,但是具体怎么做到兼容各个系统,举一反三,

上一篇:

下一篇: