最近好長時間都沒有寫blog了,主要是因為最近工作上的事以及下載Android源碼的事耽誤的(下載源碼這件事會在後續的blog中寫道,這個真的很有意義呀~~),那麼今天來寫點什麼呢?主要的靈感來自於早上看新聞看到一篇文章說有一款應用在後台中卸載使用者手機中的所有瀏覽器的app,不會被使用者察覺,但是最後百度瀏覽器還是用反偵察技術找到這個邪惡的應用然後將其告上法庭了。那麼我們就來看看怎麼能夠實現應用的靜態安裝和卸載呢?就是不讓使用者知道,下面就來一步一步的介紹一下實現步驟:
一、訪問隱藏的API方式進行靜態預設安裝和卸載1.系統安裝程式android內建了一個安裝程式---
/system/app/PackageInstaller.apk.大多數情況下,我們手機上安裝應用都是通過這個apk來安裝的。代碼使用也非常簡單:
/* 安裝apk */public static void installApk(Context context, String fileName) {Intent intent = new Intent();intent.setAction(Intent.ACTION_VIEW);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.setDataAndType(Uri.parse("file://" + fileName),"application/vnd.android.package-archive");context.startActivity(intent);}/* 卸載apk */public static void uninstallApk(Context context, String packageName) {Uri uri = Uri.parse("package:" + packageName);Intent intent = new Intent(Intent.ACTION_DELETE, uri);context.startActivity(intent);}通過發一個Intent,把應用所在的路徑封裝整uri.之後預設啟動了PackageInstaller.apk來安裝程式了。
但是此種情況下,僅僅是個demo而已,很難達到開發人員的需求。如:
1).介面不好
2).被使用者知曉
3).什麼時候安裝完了,卸載完了呢?
當然這裡關於第三點的話,為了達到自己的需求,相信很多人都會接著來監聽系統安裝卸載的廣播,繼續接下來的代碼邏輯。
監聽系統發出的安裝廣播
在安裝和卸載完後,android系統會發一個廣播
android.intent.action.PACKAGE_ADDED(安裝)
android.intent.action.PACKAGE_REMOVED(卸載)
咱們就監聽這廣播,來做響應的邏輯處理。實現代碼:
public class MonitorSysReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent){ //接收安裝廣播 if (intent.getAction().equals("android.intent.action.PACKAGE_ADDED")) { //TODO } //接收卸載廣播 if (intent.getAction().equals("android.intent.action.PACKAGE_REMOVED")) { //TODO } }}
AndroidManifest.xml檔案中的配置:
到此,確實安裝卸載的整體流程都知道了,但是這個效果肯定是無法達到項目的需求。
一般這種
市集類(豌豆莢)的項目,肯定是會要自訂提示框效果的安裝卸載功能,而不是調用系統的安裝程式。
那咱就要想法子實現靜默安裝、卸載咯。
下面這種調用系統隱藏api介面來實現靜默安裝卸載,是比較福士靠譜的,實現自訂的提示介面。
(關於這個調用系統隱藏的api介面,我們在之前講到如何擷取手機電量的一篇文章中介紹過了
http://blog.csdn.net/jiangwei0910410003/article/details/25994337)
2.系統隱藏的api隱藏api,顧名思義,普通情況下肯定是調用不到的。翻翻源碼
\frameworks\base\core\java\android\content\pm目錄下PackageManager.java,應該發現
在注釋行裡有加上@hide聲明。調用的安裝下載介面如下:
安裝介面:
public abstract void installPackage(Uri packageURI,IPackageInstallObserver observer, int flags,String installerPackageName);
卸載介面:
public abstract void deletePackage(String packageName,IPackageDeleteObserver observer, int flags);
並且都是抽象方法,需要咱們實現。
看參數裡IPackageInstallObserver observer一個aidl回調通知介面,目前的目錄中找到這介面:
package android.content.pm;/** * API for installation callbacks from the Package Manager. * @hide */oneway interface IPackageInstallObserver { void packageInstalled(in String packageName, int returnCode);}
好吧,這裡有現成的乾貨,咱拿過來直接用唄(當然如果沒有源碼的那就算了,那能實現的只是demo)。具體步驟:
從源碼中拷貝要使用的aidl回調介面:
IPackageInstallObserver.aidl、IPackageDeleteObserver.aidl當然完全可以拷貝整個pm目錄,這樣就不會報錯了。
作者項目裡面用到了pm,所以把PackageManager.java以及涉及到的一些檔案也拷貝過來了,不然eclipse報找不到PackageManager對象。結構如下:
<喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+16KjujxzdHJvbmc+tMu0prXEsPzD+2FuZHJvaWQuY29udGVudC5wbdK7tqjSqrrN1LTC68S/wry94bm50rvWwjwvc3Ryb25nPqOssrvIu9S0wuvA77Hg0uu74czhyr7V0rK7tb1haWRsvdO/2qGj0rvH0LOv1LTC67Hg0uu/tMbrPGJyPgq0y7Sm09Ay1ta3vcq9yrXP1qO6PGJyPgoxLtaxvdPWu8ihSVBhY2thZ2VEZWxldGVPYnNlcnZlci5haWRsus1JUGFja2FnZXJJbnN0YWxsT2JzZXJ2ZXIuYWlkbKGiSVBhY2thZ2VNb3ZlT2JzZXJ2ZXIuYWlkbLXI0qrKudPDtcS907/ao6zIu7rzzai5/WJpbmRTZXJ2aWNlwLS6zc+1zbPBrL3Tt/7O8aOsyLu689axvdO199PDvdO/2ry0v8mjqNXi1tbDu9PQt73Kvdf31d/Du8rUuf2jrLK7uf3UrcDtyc/AtMu106a4w8rHv8nQ0LXEo6yz/bfHz7XNs8O709DV4rj2U2VydmljZcq1z9bV4rj2vdO/2qGj09DQ6MfztcS/ydLUye6+v8/Co6k8YnI+CjIu1/fV37TLtKa1xLe9t6jKx9axvdO/vbG0wcvUtMLrUGFja2FnZU1hbmFnZXIuamF2YbXIzsS8/rn9wLSjrLK7uf2/v7n9wLTWrrrzZWNsaXBzZbvhzOHKvtK70Km907/atO3O86OstavV4sDv1/fV37DRyc/D5sTHvLi49i5qYXZhzsS8/ra8t8W/1cHLo6zS8s6q08Oyu7W9o6zWu8rHzqrBy7Hg0uu5/bLFv72xtMHLxMfDtLbgzsS8/qGj1+688rWltcS+zcrH1rG907+9sbQ0uPbOxLz+vLS/yaO6PGJyPgpQYWNrYWdlTWFuYWdlci5qYXZhPGJyPgpJUGFja2FnZURlbGV0ZU9ic2VydmVyLmFpZGw8YnI+CklQYWNrYWdlckluc3RhbGxPYnNlcnZlci5haWRsPGJyPgpJUGFja2FnZU1vdmVPYnNlcnZlci5haWRsPGJyPgrIu7rzsNFQYWNrYWdlTWFuYWdlci5qYXZh1tCxqLXE0uyzo7XEvdO/2ra816LKzbX0vLS/yTxicj4KyrXP1rvYtfe907/ao6y0+sLryOfPwqO6PGJyPgo8L3A+CjxwPrCy17C1xLvYtfe907/ao7o8L3A+CjxwPjwvcD4KPHByZSBjbGFzcz0="brush:java;">/*靜默安裝回調*/class MyPakcageInstallObserver extends IPackageInstallObserver.Stub{@Overridepublic void packageInstalled(String packageName, int returnCode) {if (returnCode == 1) {Log.e("DEMO","安裝成功");new ToastThread(InstallActivity.this,"安裝成功").start();}else{Log.e("DEMO","安裝失敗,返回碼是:"+returnCode);new ToastThread(InstallActivity.this,"安裝失敗,返回碼是:"+returnCode).start();}}}
安裝的方法:
String fileName = Environment.getExternalStorageDirectory() + File.separator + "baidu"+File.separator +"UC.apk";Uri uri = Uri.fromFile(new File(fileName));int installFlags = 0;PackageManager pm = getPackageManager();try {PackageInfo pi = pm.getPackageInfo("com.UCMobile",PackageManager.GET_UNINSTALLED_PACKAGES);if(pi != null) {installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;}} catch (NameNotFoundException e) {}MyPakcageInstallObserver observer = new MyPakcageInstallObserver();pm.installPackage(uri, observer, installFlags, "com.UCMobile");從代碼中可以看到我們想安裝的是從SD卡中的baidu檔案夾中的UC.apk(所以在測試程式的時候需要將UC.apk拷貝到SD卡中的baidu檔案夾中,或者你可以自訂一個檔案的存放目錄)
卸載原理是一樣的
卸載的回調介面:
/* 靜默卸載回調 */class MyPackageDeleteObserver extends IPackageDeleteObserver.Stub {@Overridepublic void packageDeleted(String packageName, int returnCode) {if (returnCode == 1) {Log.e("DEMO","卸載成功...");new ToastThread(InstallActivity.this,"卸載成功...").start();}else{Log.e("DEMO","卸載失敗...返回碼:"+returnCode);new ToastThread(InstallActivity.this,"卸載失敗...返回碼:"+returnCode).start();}}}
卸載的功能:
PackageManager pm = InstallActivity.this.getPackageManager();IPackageDeleteObserver observer = new MyPackageDeleteObserver();pm.deletePackage("com.UCMobile", observer, 0);這裡我們一定要傳一個包名。
自此,靜默安裝卸載代碼實現。最後在AndroidManifast.xml中要註冊許可權和添加為系統使用者組
...
這裡要注意的地方有兩個:第一個必須要添加:
android:sharedUserId="android.uid.system"
這個是將其應用註冊成系統使用者組中
還要添加安裝和卸載的許可權:
這時候上面的工作都做完之後,那麼我們就來用Eclipse運行一下吧:
好吧,貌似不是那麼的順利,出現一個安裝錯誤,其實這個資訊去百度一下之後會找到很多的相關資料,因為這個問題有很多人遇到過,所以解決的方法也是很多的,就是需要將我們的應用簽名成系統級的即可,那麼我們該怎麼辦呢?
網上普遍的兩種方法:
第一種是:得到platform.pem,platform.x509.pem,signapk.jar這三個檔案進行簽名:簽名的命令很簡單:
java -jar signapk.jar platform.x509.pem platform.pem 需要簽名的apk 簽名之後的apk
是我簽名的操作:
第二種方法:很麻煩的,為什麼說麻煩呢?因為需要下載原始碼,然後將我們的應用拷貝到指定的目錄即可:
1.如果你有系統源碼,最簡單的就是將eclipse裡面的應用拷貝到系統裡面,然後編譯系統,會產生out/target/product/generic/system/app/abc.apk,這個應用就是經過系統簽名了。具體方法如下:
將應用檔案夾複製到源碼目錄:packages/experimental/project_name/裡面,並且只保留src、res、libs、androidmanifest.xml這三個檔案,裡面建立一個Android.mk檔案,內容如下:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := abc
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
然後進行編譯即可
其實我們在將第一種方法的時候有一個問題就是那三個簽名的檔案到哪裡能搞到,這個嗎?可以到網上去搜尋的,但是本人在下載la下來然後進行簽名,結果總是失敗,於是,我就放棄了從網上去下載,那麼還有什麼方法可以得到呢?好吧,還得去下載Android源碼,編譯之後可以在指定的目錄中找到這三個檔案
但是還沒有完,不管是第一種方法還是第二種方法,我在實驗的過程中還是失敗,原因是什麼呢?因為我們如果用這三個檔案進行簽名或者把工程放到源碼中進行編譯的話,都必須要注意一點:就是簽名的檔案的系統版本必須和安裝到裝置的系統的版本一樣,說白一點:現在假如簽名的那三個檔案是從下載的編譯之後的源碼中得到的,那麼這三個檔案的版本就是這個Android源碼的版本:比如是4.4版本的,那麼我們用這三個檔案進行簽名之後的apk包只能安裝到系統版本為4.4的機子上,同理如果我們將工程直接放到Android源碼中進行編譯的話,假如Android源碼的系統版本是4.4,那麼編譯之後的apk也只能安裝到系統版本為4.4的機子上。
所以我們從網上下載到的簽名的三個檔案進行簽名之後總是失敗,所以這種方式總是不靠譜的,因為我們從網上下載的簽名檔案肯定不知道他的版本號碼,但是我們可以將簽名之後的包安裝到每個版本的系統中進行嘗試,所以最靠譜的方式還是我們下載源碼然後得到這三個簽名的檔案即可,因為我們自己下載的源碼肯定知道版本的。
這三個檔案對應的目錄是:
signapk.jar: 源碼目錄/out/host/linux-x86/framework/signapk.jar
platform.pk8和platform.x509.pem: 源碼目錄/build/target/product/security/platform.pk8&platform.x509.pem
當我們使用第一種方法的時候,我們需要用Eclipse中匯出一個未簽名的包,這個很簡單了,這裡就不介紹了~~
啟動並執行結果:
因為手頭上沒有4.4系統的機子,又不想刷機,所以就用模擬器了。在這個過程中安裝和卸載可能要一段時間,所以要等待一會,不要著急~~
那麼我們就介紹了使用隱藏的api來進行預設的安裝和卸載app,
Demo工程:
http://download.csdn.net/detail/jiangwei0910410003/7584423
匯入工程之後,需要做的幾步:
在SD卡中拷貝一個UC.apk到baidu檔案夾(需要建立的)
匯出未進行簽名的包
下載簽名的工具:
http://download.csdn.net/detail/jiangwei0910410003/7584441
按照上面的簽名步驟進行操作
然後找到一個系統是4.4版本的測試機或者模擬器進行安裝包(因為我的簽名檔案的版本是4.4)
二、通過命令進行靜態預設安裝和卸載
下面我們再來看一下如何使用命令的方式來實現預設的安裝和卸載app,我們知道可以使用命令:
adb install apk檔案
adb uninstall 包名
進行安裝和卸載操作。
我們在代碼中可以使用Runtime類進行操作:
Runtime.getRuntime().exec("adb uninstall com.UCMobile");或者ProcessBuilder:
new ProcessBuilder().command("adb","uninstall","com.UCMobile").start();
看到這兩個類的區別在於,第一種是將命令一起寫完,第二種是將命令用空格分隔,然後將其當做參數進行傳遞的,其實command方法的參數是一個String可變參數類型的
但是我們執行上面的代碼發現總是執行失敗,結果才發現,這些命令執行的目錄不是在shell層的,但是手機中執行的命令必須要在shell層中的,所以會有問題的,但是我們可以使用pm命令,比如我們使用PC端的命令列進行安裝檔案:
>adb shell
>pm install apk檔案
其實上面的兩行命令和
>adb install apk檔案
的效果一樣
但是如果想在手機上執行安裝檔案只能執行命令:pm install apk檔案
(可以把pm命令的作用想成是脫離shell層來執行命令的)
不多說了,下面就來看一下代碼吧:
/* * m命令可以通過adb在shell中執行,同樣,我們可以通過代碼來執行 */ public static String execCommand(String ...command) { Process process=null; InputStream errIs=null; InputStream inIs=null; String result=""; try { process=new ProcessBuilder().command(command).start(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int read = -1; errIs=process.getErrorStream(); while((read=errIs.read())!=-1){ baos.write(read); } inIs=process.getInputStream(); while((read=inIs.read())!=-1){ baos.write(read); } result=new String(baos.toByteArray()); if(inIs!=null) inIs.close(); if(errIs!=null) errIs.close(); process.destroy(); } catch (IOException e) { result = e.getMessage(); } return result; }
調用這個方法:
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);final String path = Environment.getExternalStorageDirectory() + File.separator + "baidu"+File.separator + "360MobileSafe.apk";Button btn1 = (Button) findViewById(R.id.btn1);btn1.setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View v) {//安裝apk,filePath為apk檔案路徑,如/mnt/sdcard/ApiDemos.apkString result = execCommand("pm","install","-f",path);Toast.makeText(MainActivity.this, "安裝結果:"+result, Toast.LENGTH_LONG).show();}});Button btn2 = (Button) findViewById(R.id.btn2);btn2.setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View v) {//卸載apk,packageName為包名,如com.example.android.apisString result = execCommand("pm","uninstall", "com.qihoo360.mobilesafe");Toast.makeText(MainActivity.this, "卸載結果:"+result, Toast.LENGTH_LONG).show();}});}
在AndroidManifest.xml中的配置:
也有:
android:sharedUserId="android.uid.system"
這個在前面已經說過了,所以我們現在做的還是將其進行系統簽名,具體方法就不多說了,同上
下面是啟動並執行結果圖:
Demo工程:
http://download.csdn.net/detail/jiangwei0910410003/7584429
下載之後的工程匯入之後需要進行的幾步操作和前面的是一模一樣的,這裡就不做解釋了~~
總結:上面就介紹了實現靜態安裝和卸載的功能,其實還有其他的方法,比如我們可以將需要預設安裝的apk檔案直接拷貝到目錄:
/data/app,因為我們知道所有使用者安裝的應用都是放在這個目錄中的,但是經過測試發現,預設安裝是沒有問題的,直接將其拷貝
即可,但是在進行卸載的時候有點問題,我們直接刪除apk檔案的話,手機中還是有這個應用的表徵圖的,但是我們點擊開啟的時候會
出現死機的情況,就相當於我們在Window系統中誤刪了一個檔案之後打不開程式的結果一樣,所以這種卸載還是不靠譜的,不建議
使用,而且這種方式的話還需要擷取手機的root許可權
待解決的問題:
1. 上面我們可以看到我們使用的是4.4版本進行簽名的,只能安裝到系統是4.4版本的機子上,這個有待考證,因為我們開發的應用不可能只能裝到指定的版本中,必須是所有的系統都能進行安裝(比如豌豆莢就可以),所以我就做了一個假設,是不是低版本的簽名檔案進行系統簽名之後的apk可以安裝到高版本系統中,那麼這樣就好辦了,我們可以下載Android2.2(因為現在市面上最低的版本是2.2了)源碼,得到他的簽名檔案,或者從網上搜尋到這個版本的簽名檔案(至今未找到,如果有找到的請分享,謝謝!!)
2. 還有一個問題就是上述的測試環境都是在手機或者模擬器都是root過了,有待考證的是沒有root的手機會不會安裝時出現問題~~
上述的blog是糾結了我幾個月的時間才解決的,所以我真心想說的一句話就是:做事千萬不要放棄,辦法總比問題多,一定要堅持~~,其實上面遇到的最大的問題就是在下載源碼過程(沒有技術可言,就是考驗我們的耐心,同時網路也是一個重要的原因,這裡由於某些原因,我下載的是4.4版本的源碼,共13G,這裡就不公開了,如果真心需要請留言,我可以共用一下~~),還有就是編譯過程,我花費了一周的時間,因為在這個過程中可以說是千難萬險,什麼問題都有,我重新編譯好幾次~~