Android靜默安裝的實現(xiàn)方案(一)

作者介紹:高闊(京東金融客戶端研發(fā)工程師峭跳,前360手機助手安裝模塊技術(shù)負責人)

? ? 在Android系統(tǒng)中如何實現(xiàn)靜默安裝蒂破,所謂靜默安裝就是在不展示安裝界面的情況下扒接,后臺悄悄的安裝某個應用精钮,雖然看上去不打擾用戶,但會存在以下兩個風險乞娄。

????第一瞬逊,但是由于在安裝的時候,Installer會展示該應用所申請的權(quán)限仪或,由用戶根據(jù)申請的權(quán)限選擇是否安裝确镊,靜默安裝相當于用戶被動的接受了這些權(quán)限。第二:在未經(jīng)用戶允許的情況下安裝某個應用范删,極易造成病毒的傳播蕾域,符合計算機病毒的傳染特性。很顯然這種做法是非常危險的,所以Android系統(tǒng)不會把此功能提供給普通開發(fā)者旨巷。而在Google Play巨缘,小米應用市場,華為應用市場下載的應用也無需彈出安裝界面采呐,是因為相關(guān)廠家的ROM賦予了自家應用市場靜默安裝權(quán)限若锁。自家的ROM想怎么折騰就怎么折騰,普通開發(fā)者也不需要關(guān)注靜默安裝的功能斧吐,只要把自己的應用上傳的某個應用市場即可

????如360手機助手又固,廣泛的安裝在各型號的手機上,它沒有小米市場煤率,華為市場等手機廠商賦予的特殊權(quán)利仰冠,360手機助手是如何做到的?360手機助手提供了兩個功能:秒裝和智能安裝

一:秒裝

秒裝就是在應用擁有Root權(quán)限的情況下蝶糯,實現(xiàn)靜默安裝(用戶無感知)

方法1:在開發(fā)者安裝安裝應用時洋只,多數(shù)會采取adb shell pm install命令,我們也可以在有root權(quán)限的情況下使用Runtime來調(diào)用此命令裳涛。

我們創(chuàng)建install(String apk_path,int flag)方法

private?void install(String apk_path,String flag) ?{??

String?command?="pm install "+flag +?filePath;??

Process?process?=null;??

DataOutputStream?os?=null;??

try?{??

process?=?Runtime.getRuntime().exec("su");??

os?=new?DataOutputStream(process.getOutputStream());??

os.writeBytes(command?+"\n");??

os.writeBytes("exit\n");??

????????os.flush();??

}catch?(Exception?e)?{??

????????e.printStackTrace();??

????}??

}??

首先組裝一個安裝命令木张,命令的格式就是pm install -r ,-r參數(shù)表示如果要安裝的apk已經(jīng)存在了就覆蓋安裝的意思端三。然后調(diào)用?Runtime.getRuntime().exec("su") 來申請root權(quán)限,隨后執(zhí)行組裝的安裝命令即可鹃彻,這種方法理解起來比較簡單郊闯,但是會存在一定的局限性,因為在中國這個特殊的市場環(huán)境下蛛株,很多的國產(chǎn)ROM廠商已經(jīng)禁用了app來執(zhí)行pm命令团赁,接下來我會著重介紹第二種安裝方法

方法2:

在應用中安裝第三方應用是通過

Intent?intent?=new?Intent(Intent.ACTION_VIEW);??

intent.setDataAndType(Uri.fromFile(new?File(mUrl)),??

"application/vnd.android.package-archive");??

?mContext.startActivity(intent);??

此段代碼實現(xiàn)的,而最終處理安裝的是PackageInstaller程序谨履,既然PackageInstaller可以實現(xiàn)安裝欢摄,那程序在擁有root權(quán)限的情況下,就可以使用期核心安裝方法來實現(xiàn)笋粟,好在android是開源的怀挠,下面分析PackageInstaller代碼:

http://androidxref.com/5.0.0_r2/xref/packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallAppProgress.java


通過以上代碼可得,PackageInstaller最終調(diào)用的是PackageManager的installPackageWithVerificationAndEncryption實現(xiàn)的靜默安裝害捕。但現(xiàn)在又有一個問題擺在我們面前绿淋,應用啟動的進程是一個普通用戶進程,linux里也無法動態(tài)的改變進程的用戶組尝盼,如何在root進程中運行我們編寫的秒裝代碼吞滞?繼續(xù)去android源代碼中找答案

????在Android系統(tǒng)中,所有的應用程序進程以及系統(tǒng)服務進程SystemServer都是由Zygote進程孕育(fork)出來的盾沫,我們知道裁赠,Android系統(tǒng)是基于Linux內(nèi)核的殿漠,而在Linux系統(tǒng)中,所有的進程都是init進程的子孫進程佩捞,也就是說绞幌,所有的進程都是直接或者間接地由init進程fork出來的。Zygote進程也不例外失尖,它是在系統(tǒng)啟動的過程啊奄,由init進程創(chuàng)建的。在系統(tǒng)啟動腳本system/core/rootdir/init.rc文件中掀潮,我們可以看到啟動Zygote進程的腳本命令:

前面的關(guān)鍵字service告訴init進程創(chuàng)建一個名為"zygote"的進程菇夸,這個zygote進程要執(zhí)行的程序是/system/bin/app_process,后面是要傳給app_process的參數(shù)仪吧。因此我們可以得知app_process是可以調(diào)用Java代碼的庄新。

讓我們看下app_process的說明:

root@android:/ # app_process

app_process [vm-options] cmd-dir [options] start-class-name [main-options]

vm-options – VM 選項

cmd-dir –父目錄 (/system/bin)

options –運行的參數(shù) :

–zygote

–start-system-server

–application (api>=14)

–nice-name=nice_proc_name (api>=14)

所以第二種秒裝實現(xiàn)思路應為

1:編寫InstallCommand.java類

public class InstallCommand{

public static void main(String[] args) {

int returnCode = installPackageCore(packageName, strInstallPath, installParam);

}

//核心安裝方法

private static int installPackageCore(String packageName, String path, String installToParam) {

? ? ? ? Object ipm = ReflectUtils.invokeStaticMethod("android.app.ActivityThread", "getPackageManager", null);

? ? ? ? final AtomicInteger resultType = new AtomicInteger(Code.SilentlyInstallCommandCoreInit);

? ? ? ? if (ipm == null) {

? ? ? ? ? ? resultType.set(Code.SilentlyInstallIPackageManagerNULL);

? ? ? ? ? ? return resultType.get();

? ? ? ? }

? ? ? ? Constructor constructor = null;

? ? ? ? try {

? ? ? ? ? ? constructor = ReflectUtils.getDeclaredConstructor("android.app.ApplicationPackageManager", Class.forName("android.app.ContextImpl"), Class.forName("android.content.pm.IPackageManager"));

? ? ? ? } catch (ClassNotFoundException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? log(TAG, "installPackageCore_getDeclaredConstructor:" + e.getMessage());

? ? ? ? }

? ? ? ? if (constructor == null) {

? ? ? ? ? ? resultType.set(Code.SilentlyInstallApplicationPackageManagerConstructorException);

? ? ? ? ? ? return resultType.get();

? ? ? ? }

? ? ? ? Object mApplicationPackageManager = null;

? ? ? ? try {

? ? ? ? ? ? Object context = null;

? ? ? ? ? ? if (Build.VERSION.SDK_INT >= 24) {

? ? ? ? ? ? ? ? context = createFakeContextImpl();

? ? ? ? ? ? ? ? if (context == null) {

? ? ? ? ? ? ? ? ? ? resultType.set(Code.SilentlyInstallCreateFakeContextException);

? ? ? ? ? ? ? ? ? ? return resultType.get();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? mApplicationPackageManager = constructor.newInstance(context, ipm);

? ? ? ? } catch (InstantiationException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? log(TAG, "installPackageCore_ConstructApplicationPackageManager:" + e.getMessage());

? ? ? ? } catch (IllegalAccessException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? log(TAG, "installPackageCore_ConstructApplicationPackageManager:" + e.getMessage());

? ? ? ? } catch (InvocationTargetException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? log(TAG, "installPackageCore_ConstructApplicationPackageManager:" + e.getMessage());

? ? ? ? }

? ? ? ? if (mApplicationPackageManager == null) {

? ? ? ? ? ? resultType.set(Code.SilentlyInstallConstructApplicationPackageManagerException);

? ? ? ? ? ? return resultType.get();

? ? ? ? }

? ? ? ? final Method method;

? ? ? ? final CountDownLatch countDownLatch = new CountDownLatch(1);

? ? ? ? try {

? ? ? ? ? ? method = ReflectUtils.getMethod(mApplicationPackageManager.getClass().getName(), "installPackage", Uri.class, Class.forName("android.content.pm.IPackageInstallObserver"), int.class, String.class);

? ? ? ? ? ? method.setAccessible(true);

? ? ? ? ? ? Object IPackageInstallObserver = WrapIPackageHelper.wrapIPackageInstallObserverStub(new IPackageInstallObserver.Stub() {

? ? ? ? ? ? ? ? @Override

? ? ? ? ? ? ? ? public void packageInstalled(String packageName, int returnCode) throws RemoteException {

? ? ? ? ? ? ? ? ? ? log(TAG, String.format("IPackageInstallObserver packageInstalled packageName:%s,returnCode:%s", packageName, returnCode));

? ? ? ? ? ? ? ? ? ? resultType.set(returnCode);

? ? ? ? ? ? ? ? ? ? countDownLatch.countDown();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? });

? ? ? ? ? ? method.invoke(mApplicationPackageManager, Uri.fromFile(new File(path)), IPackageInstallObserver, parseInstallParam(installToParam), packageName);

? ? ? ? ? ? countDownLatch.await(90, TimeUnit.SECONDS);

? ? ? ? } catch (ClassNotFoundException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? log(TAG, "installPackageCore_installPackage:" + e.getMessage());

? ? ? ? ? ? resultType.set(Code.SilentlyInstallClassNotFoundException);

? ? ? ? } catch (InvocationTargetException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? log(TAG, "installPackageCore_installPackage:" + e.getMessage());

? ? ? ? ? ? resultType.set(Code.SilentlyInstallInvocationTargetException);

? ? ? ? } catch (IllegalAccessException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? log(TAG, "installPackageCore_installPackage:" + e.getMessage());

? ? ? ? ? ? resultType.set(Code.SilentlyInstallIllegalAccessException);

? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? log(TAG, "installPackageCore_installPackage:" + e.getMessage());

? ? ? ? ? ? resultType.set(Code.SilentlyInstallInterruptedException);

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? log(TAG, "installPackageCore_installPackage:" + e.getMessage());

? ? ? ? ? ? resultType.set(Code.SilentlyInstallOtherException);

? ? ? ? }

? ? ? ? return resultType.get();

? ? }

? ? @TargetApi(24)

? ? private static Object createFakeContextImpl() {

? ? ? ? Looper.prepare();

? ? ? ? Constructor constructor = ReflectUtils.getDeclaredConstructor("android.app.ActivityThread");

? ? ? ? Object mActivityThread = null;

? ? ? ? try {

? ? ? ? ? ? mActivityThread = constructor.newInstance();

? ? ? ? } catch (InstantiationException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? log(TAG, "createFakeContextImpl:" + e);

? ? ? ? } catch (IllegalAccessException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? log(TAG, "createFakeContextImpl:" + e);

? ? ? ? } catch (InvocationTargetException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? StringBuffer s = new StringBuffer(e.getTargetException().toString() + "\n");

? ? ? ? ? ? StackTraceElement elements[] = e.getTargetException().getStackTrace();

? ? ? ? ? ? for (StackTraceElement element : elements) {

? ? ? ? ? ? ? ? s.append(element.toString()).append("\n");

? ? ? ? ? ? }

? ? ? ? ? ? log(TAG, "createFakeContextImpl:" + s.toString());

? ? ? ? }

? ? ? ? if (mActivityThread == null) {

? ? ? ? ? ? mActivityThread = ReflectUtils.invokeStaticMethod("android.app.ActivityThread", "systemMain", null);

? ? ? ? }

? ? ? ? if (mActivityThread != null) {

? ? ? ? ? ? Object mContext = ReflectUtils.invokeMethod(mActivityThread, "getSystemContext", null);

? ? ? ? ? ? if (mContext == null) {

? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? mContext = ReflectUtils.invokeStaticMethod("android.app.ContextImpl", "createSystemContext", new Class[]{Class.forName("android.app.ActivityThread")}, mActivityThread);

? ? ? ? ? ? ? ? } catch (Exception e) {

? ? ? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? log(TAG, "createFakeContextImpl mContext:" + mContext);

? ? ? ? ? ? return mContext;

? ? ? ? }

? ? ? ? return null;

? ? }

}


2:在通過Runtime.getRuntime().exec("su") 調(diào)用app_process啟動InstallCommand.java進行秒裝

未完待續(xù)(下一章會講解通過輔助功能進行智能安裝)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市薯鼠,隨后出現(xiàn)的幾起案子择诈,更是在濱河造成了極大的恐慌,老刑警劉巖出皇,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件羞芍,死亡現(xiàn)場離奇詭異,居然都是意外死亡郊艘,警方通過查閱死者的電腦和手機荷科,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纱注,“玉大人畏浆,你說我怎么就攤上這事∧” “怎么了刻获?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瞎嬉。 經(jīng)常有香客問我蝎毡,道長,這世上最難降的妖魔是什么佑颇? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任顶掉,我火速辦了婚禮,結(jié)果婚禮上挑胸,老公的妹妹穿的比我還像新娘痒筒。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布簿透。 她就那樣靜靜地躺著移袍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪老充。 梳的紋絲不亂的頭發(fā)上葡盗,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天,我揣著相機與錄音啡浊,去河邊找鬼觅够。 笑死,一個胖子當著我的面吹牛巷嚣,可吹牛的內(nèi)容都是我干的喘先。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼廷粒,長吁一口氣:“原來是場噩夢啊……” “哼窘拯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起坝茎,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤涤姊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嗤放,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體思喊,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年次酌,在試婚紗的時候發(fā)現(xiàn)自己被綠了搔涝。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡和措,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蜕煌,到底是詐尸還是另有隱情派阱,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布斜纪,位于F島的核電站贫母,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏盒刚。R本人自食惡果不足惜腺劣,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望因块。 院中可真熱鬧橘原,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至芋酌,卻和暖如春增显,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脐帝。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工同云, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人堵腹。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓炸站,卻偏偏與公主長得像,于是被迫代替她去往敵國和親秸滴。 傳聞我的和親對象是個殘疾皇子武契,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容