Android 靜默安裝和智能安裝的實(shí)現(xiàn)方法
1 簡(jiǎn)介
最近研究了Android的靜默安裝和智能安裝,于是寫(xiě)博客記錄一下。靜默安裝就是無(wú)聲無(wú)息的在后臺(tái)安裝apk,沒(méi)有任何界面提示。智能安裝就是有安裝界面,但全部是自動(dòng)的,不需要用戶(hù)去點(diǎn)擊。
首先強(qiáng)調(diào)兩點(diǎn):靜默安裝必須要root權(quán)限 智能安裝必須要用戶(hù)手動(dòng)開(kāi)啟無(wú)障礙服務(wù)
2 原理
靜默安裝、卸載的原理就是利用pm install命令來(lái)安裝apk,pm uninstall 來(lái)卸載apk. 智能安裝是利用android系統(tǒng)提供的無(wú)障礙服務(wù)AccessibilityService,來(lái)模擬用戶(hù)點(diǎn)擊,從而自動(dòng)安裝.
3 pm命令介紹
(1) pm install
pm install 命令的用法及參數(shù)解釋如下:
<code class="hljs haml">pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f] PATH Options: -l: install the package with FORWARD_LOCK. -r: reinstall an exisiting app, keeping its data. -t: allow test .apks to be installed. -i: specify the installer package name. -s: install package on sdcard. -f: install package on internal flash.</code>
(2) pm uninstall
pm uninstall 命令的用法及參數(shù)解釋如下:
<code class="hljs livecodeserver">pm uninstall [-k] PACKAGE Options: -k: keep the data and cache directories around.</code>
上面英語(yǔ)很簡(jiǎn)單,不解釋了.
4 靜默安裝
為了方便演示,我把愛(ài)奇藝的安裝包重命名為test.apk后放在了sdcard上。你可以自己去愛(ài)奇藝官網(wǎng)去下載,也可以自己找一個(gè)apk放到sdcard上,但是要知道apk的包名,后面卸載的時(shí)候要用到。
先上代碼:
<code class="hljs cs">//靜默安裝 private void installSlient() { String cmd = "pm install -r /mnt/sdcard/test.apk"; Process process = null; DataOutputStream os = null; BufferedReader successResult = null; BufferedReader errorResult = null; StringBuilder successMsg = null; StringBuilder errorMsg = null; try { //靜默安裝需要root權(quán)限 process = Runtime.getRuntime().exec("su"); os = new DataOutputStream(process.getOutputStream()); os.write(cmd.getBytes()); os.writeBytes("\n"); os.writeBytes("exit\n"); os.flush(); //執(zhí)行命令 process.waitFor(); //獲取返回結(jié)果 successMsg = new StringBuilder(); errorMsg = new StringBuilder(); successResult = new BufferedReader(new InputStreamReader(process.getInputStream())); errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream())); String s; while ((s = successResult.readLine()) != null) { successMsg.append(s); } while ((s = errorResult.readLine()) != null) { errorMsg.append(s); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (os != null) { os.close(); } if (process != null) { process.destroy(); } if (successResult != null) { successResult.close(); } if (errorResult != null) { errorResult.close(); } } catch (Exception e) { e.printStackTrace(); } } //顯示結(jié)果 tvTest.setText("成功消息:" + successMsg.toString() + "\n" + "錯(cuò)誤消息: " + errorMsg.toString()); }</code>
這段代碼就是在程序中執(zhí)行pm命令,和在adb下執(zhí)行 pm install -r /mnt/sdcard/test.apk
效果是一樣的, 關(guān)鍵的代碼是 Runtime.getRuntime().exec(“su”)
,這段代碼會(huì)要求獲取root權(quán)限,所以你的手機(jī)必須root,不想root的話,直接用模擬器也可以。
通過(guò) Runtime.getRuntime().exec(“su”)
獲取到 process 對(duì)象后就可以寫(xiě)入命令了,每寫(xiě)入一條命令就要換行,寫(xiě)入 ‘\n' 即可,最后寫(xiě)入exit后離開(kāi)命令執(zhí)行的環(huán)境.
5 靜默卸載
靜默卸載和靜默安裝是一樣的,只是命令不同,靜默卸載需要用到包名,同樣,靜默卸載也需要root權(quán)限
看代碼:
<code class="hljs java">//愛(ài)奇藝apk的包名 private static final String PACKAGE_NAME = "com.qiyi.video"; //靜默卸載 private void uninstallSlient() { String cmd = "pm uninstall " + PACKAGE_NAME; Process process = null; DataOutputStream os = null; BufferedReader successResult = null; BufferedReader errorResult = null; StringBuilder successMsg = null; StringBuilder errorMsg = null; try { //卸載也需要root權(quán)限 process = Runtime.getRuntime().exec("su"); os = new DataOutputStream(process.getOutputStream()); os.write(cmd.getBytes()); os.writeBytes("\n"); os.writeBytes("exit\n"); os.flush(); //執(zhí)行命令 process.waitFor(); //獲取返回結(jié)果 successMsg = new StringBuilder(); errorMsg = new StringBuilder(); successResult = new BufferedReader(new InputStreamReader(process.getInputStream())); errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream())); String s; while ((s = successResult.readLine()) != null) { successMsg.append(s); } while ((s = errorResult.readLine()) != null) { errorMsg.append(s); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (os != null) { os.close(); } if (process != null) { process.destroy(); } if (successResult != null) { successResult.close(); } if (errorResult != null) { errorResult.close(); } } catch (Exception e) { e.printStackTrace(); } } //顯示結(jié)果 tvTest.setText("成功消息:" + successMsg.toString() + "\n" + "錯(cuò)誤消息: " + errorMsg.toString()); }</code>
和靜默安裝一樣的代碼就不解釋了。還有,如果你不知道一個(gè)apk的包名,那么請(qǐng)反編譯后去看AndroidManifest.xml文件,如果這個(gè)文件打開(kāi)全是亂碼,說(shuō)明是被混淆過(guò)的,那么直接安裝它,然后到/data/data下面去找它的包,當(dāng)然,手機(jī)得root才能進(jìn)/data/data目錄。
6 智能安裝
智能安裝就稍微麻煩點(diǎn)了,原理是用到了android提供的AccessibilityService服務(wù),這個(gè)服務(wù)可以獲取屏幕上的節(jié)點(diǎn),一個(gè)節(jié)點(diǎn)也就是一個(gè)view,我們寫(xiě)的xml文件中每個(gè)標(biāo)簽就是一個(gè)節(jié)點(diǎn),然后可以模擬用戶(hù)的操作,對(duì)這些節(jié)點(diǎn)進(jìn)行點(diǎn)擊、滑動(dòng)等操作。我們就是利用這個(gè)原理,來(lái)自動(dòng)點(diǎn)擊安裝按鈕的,當(dāng)然使用這個(gè)服務(wù)必須用戶(hù)手動(dòng)開(kāi)啟無(wú)障礙服務(wù)。下面我們來(lái)看具體的實(shí)現(xiàn)方法。
(1) 創(chuàng)建AccessibilityService配置文件
在res目錄下創(chuàng)建xml目錄,然后在xml目錄下創(chuàng)建一個(gè)accessibility_service_config.xml文件,內(nèi)容如下
res/xml/accessibility_service_config.xml:
<code class="hljs xml" data-filtered="filtered"></accessibility-service></code>
accessibilityEventTypes:指定我們?cè)诒O(jiān)聽(tīng)窗口中可以模擬哪些事件,typeAllMask表示所有的事件都能模擬.
accessibilityFeedbackType:指定無(wú)障礙服務(wù)的反饋方式.
canRetrieveWindowContent:指定是否允許我們的程序讀取窗口中的節(jié)點(diǎn)和內(nèi)容,當(dāng)然是true.
description: 當(dāng)用戶(hù)手動(dòng)配置服務(wù)時(shí),會(huì)顯示給用戶(hù)看.
packageNames: 指定我們要監(jiān)聽(tīng)哪個(gè)應(yīng)用程序下的窗口活動(dòng),這里寫(xiě)com.android.packageinstaller表示監(jiān)聽(tīng)Android系統(tǒng)的安裝界面。
其余參數(shù)照寫(xiě)即可。
res/strings.xml:
<code class="hljs xml"><resources> <string name="app_name">SlientInstallTest</string> <string name="desc">智能安裝app功能演示</string> </resources></code>
(2) 創(chuàng)建AccessibilityService服務(wù)
<code class="hljs java">public class MyAccessibilityService extends AccessibilityService { private static final String TAG = "[TAG]"; private Map<integer, boolean=""> handleMap = new HashMap<>(); @Override public void onAccessibilityEvent(AccessibilityEvent event) { AccessibilityNodeInfo nodeInfo = event.getSource(); if (nodeInfo != null) { int eventType = event.getEventType(); if (eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED || eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { if (handleMap.get(event.getWindowId()) == null) { boolean handled = iterateNodesAndHandle(nodeInfo); if (handled) { handleMap.put(event.getWindowId(), true); } } } } } @Override public void onInterrupt() { } //遍歷節(jié)點(diǎn),模擬點(diǎn)擊安裝按鈕 private boolean iterateNodesAndHandle(AccessibilityNodeInfo nodeInfo) { if (nodeInfo != null) { int childCount = nodeInfo.getChildCount(); if ("android.widget.Button".equals(nodeInfo.getClassName())) { String nodeCotent = nodeInfo.getText().toString(); Log.d(TAG, "content is: " + nodeCotent); if ("安裝".equals(nodeCotent) || "完成".equals(nodeCotent) || "確定".equals(nodeCotent)) { nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK); return true; } } //遇到ScrollView的時(shí)候模擬滑動(dòng)一下 else if ("android.widget.ScrollView".equals(nodeInfo.getClassName())) { nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); } for (int i = 0; i < childCount; i++) { AccessibilityNodeInfo childNodeInfo = nodeInfo.getChild(i); if (iterateNodesAndHandle(childNodeInfo)) { return true; } } } return false; } }</integer,></code>
當(dāng)進(jìn)入apk安裝界面就會(huì)回調(diào)onAccessibilityEvent()這個(gè)方法,我們只關(guān)心TYPE_WINDOW_CONTENT_CHANGED和TYPE_WINDOW_STATE_CHANGED兩個(gè)事件,為了防止重復(fù)處理事件,用一個(gè)map來(lái)過(guò)濾事件,后面遞歸遍歷節(jié)點(diǎn),找到'安裝' ‘完成' ‘確定' 的按鈕,就點(diǎn)擊,由于安裝界面需要滾動(dòng)一下才能出現(xiàn)安裝按鈕,所以遇到ScrollView的時(shí)候就滾動(dòng)一下.
(3) 在AndroidManifest中配置服務(wù)
<code class="hljs xml"><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"> <intent-filter> <category android:name="android.intent.category.LAUNCHER"> </category></action></intent-filter> </activity> <service android:label="智能安裝App" android:name=".MyAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> </action></intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config"> </meta-data></service> </application></uses-permission></code>
重點(diǎn)是后面的service標(biāo)簽:
android:label:這個(gè)就是用戶(hù)看到的無(wú)障礙服務(wù)的名稱(chēng)
android:permission: 需要用到BIND_ACCESSIBILITY_SERVICE這個(gè)權(quán)限.
action: android.accessibilityservice.AccessibilityService 有了這個(gè)action,用戶(hù)才能在設(shè)置里面看到我們的服務(wù),否則用戶(hù)無(wú)法開(kāi)啟我們寫(xiě)的服務(wù),也就不能進(jìn)到我們寫(xiě)的MyAccessibilityService里面了.所以,注意不要寫(xiě)錯(cuò)了,如果你發(fā)現(xiàn)無(wú)障礙服務(wù)里面沒(méi)有我們寫(xiě)的服務(wù),請(qǐng)檢查這里.
(4) 調(diào)用智能安裝代碼
前面準(zhǔn)備工作完畢后,現(xiàn)在要用了,調(diào)用智能安裝的代碼如下:
<code class="hljs cs"> //智能安裝 private void smartInstall() { Uri uri = Uri.fromFile(new File("/mnt/sdcard/test.apk")); Intent localIntent = new Intent(Intent.ACTION_VIEW); localIntent.setDataAndType(uri, "application/vnd.android.package-archive"); startActivity(localIntent); }</code>
(5) 手動(dòng)配置智能安裝服務(wù)
代碼運(yùn)行之后,還要用戶(hù)選擇開(kāi)啟智能安裝服務(wù),讓用戶(hù)自己去找是不明智的,因此,我們要主動(dòng)跳到配置界面,代碼如下:
<code class="hljs cs">//跳轉(zhuǎn)到開(kāi)啟智能安裝服務(wù)的界面 Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); startActivity(intent);</code>
配置如下圖:
看到了嗎,上面顯示的就是Service里面的label的值,如果你沒(méi)有上面的選項(xiàng),請(qǐng)檢查AndroidManifest里面Service的配置.
點(diǎn)擊'智能安裝App',開(kāi)啟服務(wù),如下圖:
其中的提示文字就是我們?cè)趓es/xml/accessibility_service_config.xml文件中配置的description屬性
7 只能我們寫(xiě)的app可以自動(dòng)安裝
這樣寫(xiě)完代碼可以運(yùn)行,點(diǎn)擊按鈕自動(dòng)安裝sdcard上的test.apk.但是你會(huì)發(fā)現(xiàn),所有apk都會(huì)自動(dòng)安裝,這就不符合我們的要求了,我們要求只能通過(guò)我們寫(xiě)的app來(lái)自動(dòng)安裝,其他apk還是要用戶(hù)手動(dòng)去點(diǎn)。怎么解決這個(gè)問(wèn)題呢?
思路是:在MainActivity中創(chuàng)建一個(gè)public static boolean flag,在MyAccessibilityService的onAccessibilityEvent()中加一個(gè)flag判斷,然后調(diào)用智能安裝前flag設(shè)為true,創(chuàng)建apk安裝事件的廣播接收器,當(dāng)apk安裝完成后,設(shè)置falg為false,這樣其他apk就不能自動(dòng)安裝了,就解決了這個(gè)問(wèn)題
下面上完整代碼.
8 完整代碼
app/MainActivity.java:
<code class="hljs java">public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final String TAG = "[TAG][MainActivity]"; private static final String PACKAGE_NAME = "com.qiyi.video"; private String apkPath = "/mnt/sdcard/test.apk"; public static boolean flag = false;//控制只能自己的app才能執(zhí)行智能安裝 private TextView tvTest; private MyInstallReceiver receiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvTest = (TextView) findViewById(R.id.tv_test); findViewById(R.id.btn_install).setOnClickListener(this); findViewById(R.id.btn_uninstall).setOnClickListener(this); findViewById(R.id.btn_set).setOnClickListener(this); findViewById(R.id.btn_smart_install).setOnClickListener(this); //注冊(cè)apk安裝監(jiān)聽(tīng) receiver = new MyInstallReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction("android.intent.action.PACKAGE_ADDED"); filter.addAction("android.intent.action.PACKAGE_REMOVED"); filter.addDataScheme("package"); this.registerReceiver(receiver, filter); } @Override public void onClick(View v) { switch (v.getId()) { //靜默安裝 case R.id.btn_install: installSlient(); break; //靜默卸載 case R.id.btn_uninstall: uninstallSlient(); break; //設(shè)置無(wú)障礙服務(wù) case R.id.btn_set: //跳轉(zhuǎn)到開(kāi)啟無(wú)障礙服務(wù)的界面 Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); startActivity(intent); break; //智能安裝 case R.id.btn_smart_install: //控制只能自己的app才能智能安裝 flag = true; smartInstall(); break; } } //靜默安裝 private void installSlient() { String cmd = "pm install -r /mnt/sdcard/test.apk"; Process process = null; DataOutputStream os = null; BufferedReader successResult = null; BufferedReader errorResult = null; StringBuilder successMsg = null; StringBuilder errorMsg = null; try { //靜默安裝需要root權(quán)限 process = Runtime.getRuntime().exec("su"); os = new DataOutputStream(process.getOutputStream()); os.write(cmd.getBytes()); os.writeBytes("\n"); os.writeBytes("exit\n"); os.flush(); //執(zhí)行命令 process.waitFor(); //獲取返回結(jié)果 successMsg = new StringBuilder(); errorMsg = new StringBuilder(); successResult = new BufferedReader(new InputStreamReader(process.getInputStream())); errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream())); String s; while ((s = successResult.readLine()) != null) { successMsg.append(s); } while ((s = errorResult.readLine()) != null) { errorMsg.append(s); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (os != null) { os.close(); } if (process != null) { process.destroy(); } if (successResult != null) { successResult.close(); } if (errorResult != null) { errorResult.close(); } } catch (Exception e) { e.printStackTrace(); } } //顯示結(jié)果 tvTest.setText("成功消息:" + successMsg.toString() + "\n" + "錯(cuò)誤消息: " + errorMsg.toString()); } //靜默卸載 private void uninstallSlient() { String cmd = "pm uninstall " + PACKAGE_NAME; Process process = null; DataOutputStream os = null; BufferedReader successResult = null; BufferedReader errorResult = null; StringBuilder successMsg = null; StringBuilder errorMsg = null; try { //卸載也需要root權(quán)限 process = Runtime.getRuntime().exec("su"); os = new DataOutputStream(process.getOutputStream()); os.write(cmd.getBytes()); os.writeBytes("\n"); os.writeBytes("exit\n"); os.flush(); //執(zhí)行命令 process.waitFor(); //獲取返回結(jié)果 successMsg = new StringBuilder(); errorMsg = new StringBuilder(); successResult = new BufferedReader(new InputStreamReader(process.getInputStream())); errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream())); String s; while ((s = successResult.readLine()) != null) { successMsg.append(s); } while ((s = errorResult.readLine()) != null) { errorMsg.append(s); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (os != null) { os.close(); } if (process != null) { process.destroy(); } if (successResult != null) { successResult.close(); } if (errorResult != null) { errorResult.close(); } } catch (Exception e) { e.printStackTrace(); } } //顯示結(jié)果 tvTest.setText("成功消息:" + successMsg.toString() + "\n" + "錯(cuò)誤消息: " + errorMsg.toString()); } //智能安裝 private void smartInstall() { Uri uri = Uri.fromFile(new File(apkPath)); Intent localIntent = new Intent(Intent.ACTION_VIEW); localIntent.setDataAndType(uri, "application/vnd.android.package-archive"); startActivity(localIntent); } //監(jiān)聽(tīng)apk安裝 private class MyInstallReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals("android.intent.action.PACKAGE_ADDED")) { // install String packageName = intent.getDataString(); Log.i(TAG, "安裝了 :" + packageName); //安裝完畢,設(shè)置flag,從而使得其余的apk不能自動(dòng)安裝 flag = false; } if (intent.getAction().equals("android.intent.action.PACKAGE_REMOVED")) { // uninstall String packageName = intent.getDataString(); Log.i(TAG, "卸載了 :" + packageName); } } } @Override protected void onDestroy() { super.onDestroy(); if (receiver != null) { unregisterReceiver(receiver); } } }</code>
界面上就三個(gè)按鈕
res/layout/activity_main.xml:
<code class="hljs xml"><!--?xml version="1.0" encoding="utf-8"?--> <relativelayout android:layout_height="match_parent" android:layout_width="match_parent" android:paddingbottom="@dimen/activity_vertical_margin" android:paddingleft="@dimen/activity_horizontal_margin" android:paddingright="@dimen/activity_horizontal_margin" android:paddingtop="@dimen/activity_vertical_margin" xmlns:android="https://schemas.android.com/apk/res/android" xmlns:tools="https://schemas.android.com/tools"> <textview android:id="@+id/tv_test" android:layout_height="wrap_content" android:layout_width="match_parent" android:text=""><button android:id="@+id/btn_install" android:layout_centerinparent="true" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="安裝"></button><button android:id="@+id/btn_uninstall" android:layout_below="@id/btn_install" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="卸載"></button><button android:id="@+id/btn_set" android:layout_below="@id/btn_uninstall" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="開(kāi)啟智能安裝功能"></button></textview></relativelayout></code><button android:id="@+id/btn_smart_install" android:layout_below="@id/btn_set" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="智能安裝"><code class="hljs xml"> </code></button>
服務(wù)配置文件
res/xml/accessibility_service_config.xml
<code class="hljs xml" data-filtered="filtered"></accessibility-service></code>
智能安裝服務(wù)
app/MyAccessibilityService.java:
<code class="hljs java">public class MyAccessibilityService extends AccessibilityService { private static final String TAG = "[TAG]"; private Map<integer, boolean=""> handleMap = new HashMap<>(); @Override public void onAccessibilityEvent(AccessibilityEvent event) { AccessibilityNodeInfo nodeInfo = event.getSource(); if (nodeInfo != null && MainActivity.flag) { int eventType = event.getEventType(); if (eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED || eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { if (handleMap.get(event.getWindowId()) == null) { boolean handled = iterateNodesAndHandle(nodeInfo); if (handled) { handleMap.put(event.getWindowId(), true); } } } } } @Override public void onInterrupt() { } //遍歷節(jié)點(diǎn),模擬點(diǎn)擊安裝按鈕 private boolean iterateNodesAndHandle(AccessibilityNodeInfo nodeInfo) { if (nodeInfo != null) { int childCount = nodeInfo.getChildCount(); if ("android.widget.Button".equals(nodeInfo.getClassName())) { String nodeCotent = nodeInfo.getText().toString(); Log.d(TAG, "content is: " + nodeCotent); if ("安裝".equals(nodeCotent) || "完成".equals(nodeCotent) || "確定".equals(nodeCotent)) { nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK); return true; } } //遇到ScrollView的時(shí)候模擬滑動(dòng)一下 else if ("android.widget.ScrollView".equals(nodeInfo.getClassName())) { nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); } for (int i = 0; i < childCount; i++) { AccessibilityNodeInfo childNodeInfo = nodeInfo.getChild(i); if (iterateNodesAndHandle(childNodeInfo)) { return true; } } } return false; } }</integer,></code>
最后是配置文件AndroidManifest.xml:
<code class="hljs xml"><manifest package="com.slientinstalltest" xmlns:android="https://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"> <intent-filter> <category android:name="android.intent.category.LAUNCHER"> </category></action></intent-filter> </activity> <service android:label="智能安裝App" android:name=".MyAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> </action></intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config"> </meta-data></service> </application> </uses-permission></manifest></code>
注意:請(qǐng)把自己要安裝的apk放到sdcard上,并且修改代碼中的apk路徑和包名
9 運(yùn)行效果
10 總結(jié)
Android智能安裝的原理就是利用了類(lèi)似鉤子的服務(wù),這個(gè)服務(wù)還可以用于微信搶紅包的開(kāi)發(fā),怎么樣,是不是比ios好玩兒的多呢.
相關(guān)文章
android開(kāi)發(fā)教程之a(chǎn)ndroid的handler使用方法
這篇文章主要介紹了android的handler使用方法,大家參考使用吧2014-01-01解決android關(guān)于打開(kāi)虛擬機(jī)時(shí)右側(cè)工具欄不顯示的問(wèn)題
下面小編就為大家分享一篇解決android關(guān)于打開(kāi)虛擬機(jī)時(shí)右側(cè)工具欄不顯示的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01Android手勢(shì)操作簡(jiǎn)單實(shí)例講解
這篇文章主要為大家詳細(xì)介紹了Android手勢(shì)操作簡(jiǎn)單實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09Android開(kāi)發(fā)之MediaPlayer基本使用方法詳解
這篇文章主要介紹了Android開(kāi)發(fā)之MediaPlayer基本使用方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了MediaPlayer中的常用函數(shù)與基本使用技巧,需要的朋友可以參考下2017-05-05Android實(shí)現(xiàn)微信朋友圈圖片和視頻播放
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)微信朋友圈圖片和視頻播放,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05Android應(yīng)用內(nèi)懸浮窗Activity的簡(jiǎn)單實(shí)現(xiàn)
懸浮窗相信大家應(yīng)該都不陌生,下面這篇文章主要給大家介紹了關(guān)于Android應(yīng)用內(nèi)懸浮窗Activity簡(jiǎn)單實(shí)現(xiàn)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-01-01Android控件Spinner實(shí)現(xiàn)下拉列表及監(jiān)聽(tīng)功能
這篇文章主要介紹了Android控件Spinner實(shí)現(xiàn)下拉列表及監(jiān)聽(tīng)功能,這是在Web開(kāi)發(fā)中一個(gè)必不可少的交互性組件,而在Android中的對(duì)應(yīng)實(shí)現(xiàn)就是Spinner。需要的朋友可以參考下2018-07-07