Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android無需root實現apk的靜默安裝

Android無需root實現apk的靜默安裝

編輯:關於Android編程

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改動還是比較大的,所以要拷貝很多東西過來,還要進行一些改動...我也是花了挺久才改到他沒報錯。
最終,工程的目錄如下所示~~

那麼,如何來使用它呢?

  • 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 >為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安裝到手機上,打開,點擊靜默安裝,在去程序頁看看,發現安裝成功~~

      

 

本文主要是提供了一種實現靜默安裝的思路,但是具體怎麼做到兼容各個系統,舉一反三,

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved