靜默安裝获讳,就是指在程序安裝時,用戶并不會感知到安裝的過程活喊,自己就安裝完成了丐膝。一些系統(tǒng)自帶應用市場會具有靜默安裝的功能,比如小米的應用市場钾菊。在一些非系統(tǒng)自帶的應用市場帅矗,要想完成靜默安裝,就必須具有root權限煞烫』氪耍可見權限的重要性,在系統(tǒng)的支持下红竭,你可以做到很多別人做不到的事情尤勋。當然喘落,像360手機衛(wèi)士茵宪,應用寶,豌豆莢之類的非系統(tǒng)支持的應用市場瘦棋,大多使用了智能安裝稀火,仍然會彈出系統(tǒng)安裝彈窗,但是會迅速自動點擊安裝按鈕赌朋。對大多數(shù)用戶來說凰狞,還是未root用戶比較多,因為這種方式也是很常見的沛慢。
一.靜默安裝
靜默安卓有兩個前提條件赡若,一個是手機必須具有root權限,一個是系統(tǒng)是4.2及以上团甲∮舛看一下靜默安裝的代碼:
/**
* 靜默安裝
* @param apkPath apk文件路徑
*/
public static boolean install(String apkPath) {
boolean result = false;
DataOutputStream dataOutputStream = null;
BufferedReader errorStream = null;
try {
Process process = Runtime.getRuntime().exec("su");//申請root權限
dataOutputStream = new DataOutputStream(process.getOutputStream());
String command = "pm install -r " + apkPath + "\n";//拼接 pm install 命令,執(zhí)行。-r表示若存在則覆蓋安裝
dataOutputStream.write(command.getBytes(Charset.forName("utf-8")));
dataOutputStream.flush();
dataOutputStream.writeBytes("exit\n");
dataOutputStream.flush();
process.waitFor();//安裝過程是同步的身腻,安裝完成后再讀取結果
errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String message = "";
String line;
while ((line = errorStream.readLine()) != null) {
message += line;
}
Log.e("silentInstall", message);
if (!message.contains("Failure")) {
result = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (dataOutputStream != null) {
dataOutputStream.close();
}
if (errorStream != null) {
errorStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return result;
}
代碼不多产还,核心就是調用了這條命令 pm install -r <apkPath>
,顯然和我們在adb中安裝apk一樣。-r
代表若目標apk存在則覆蓋安裝嘀趟。首先通過Runtime.getRuntime().exec("su")
申請root權限脐区,否則是沒有辦法成功執(zhí)行pm命令的。執(zhí)行完成后通過讀取安裝結果她按,判斷是否安裝成功牛隅。我們也可以先判斷一下當前是否具有root權限,再去決定是否靜默安裝酌泰。
/**
* 判斷手機是否擁有Root權限倔叼。
* @return 有root權限返回true,否則返回false宫莱。
*/
public static boolean isRoot() {
boolean bool = false;
try {
bool = new File("/system/bin/su").exists() || new File("/system/xbin/su").exists();
} catch (Exception e) {
e.printStackTrace();
}
return bool;
}
二.智能安裝
智能安裝利用輔助功能AccessibilityService
來實現(xiàn)對安裝界面的自動點擊丈攒。什么是AccessibilityService
呢?Accessibility services should only be used to assist users with disabilities in using Android devices and apps. They run in the background and receive callbacks by the system when AccessibilityEvents are fired.
它是被設計出來幫助一些無法正常使用安卓設備和app的殘疾人的授霸,運行在后臺巡验,當系統(tǒng)發(fā)生一些AccessibilityEvent
時會產生回調。通過這些回調碘耳,我們可以通過代碼做一些事情显设。這里我們要做的就是,當系統(tǒng)安裝彈窗出現(xiàn)時辛辨,通過AccessibilityService
自動點擊安裝或者確定按鈕捕捂。
首先是做一個配置,在res/xml
目錄下新建accessibility_service_config.xml
文件斗搞,內容如下:
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:packageNames="com.android.packageinstaller"
android:description="@string/accessibility_service_description"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFlags="flagDefault"
android:accessibilityFeedbackType="feedbackGeneric"
android:canRetrieveWindowContent="true"/>
-
packageNames
,Comma separated package names from which this serivce would like to receive events (leave out for all packages).指定我們的Service監(jiān)聽哪個包名下的事件指攒。這里的com.android.packageinstaller
就是指安卓的安裝界面。 -
description
,當在輔助功能界面點擊你自己的app后僻焚,會顯示這些文字允悦。你可以描述你開啟輔助功能的目的,為自己狡辯一下虑啤。 -
accessibilityEventTypes
,The event types this serivce would like to receive as specified in AccessibilityEvent.我們可以在監(jiān)聽窗口中模擬哪些事件隙弛。typeAllMask
代表所有。 -
accessibilityFlags
,Additional flags as specified in AccessibilityServiceInfo.一些附加參數(shù)狞山,默認即可全闷。 -
accessibilityFeedbackType
,無障礙服務的反饋方式,比如針對殘疾人可以語音反饋萍启,這里并不需要总珠。 -
canRetrieveWindowContent
,Attribute whether the accessibility service wants to be able to retrieve the active window content. 我們是否可以檢索窗口中的內容,當然應該是可以的。
然后我們需要繼承AccessibilityService
,重寫相關方法實現(xiàn)智能安裝的具體邏輯姚淆。如下所示:
/**
* Created by Lu
* on 2017/1/17 17:54.
*/
public class MyAccessibilityService extends AccessibilityService {
Map<Integer, Boolean> handleMap = new HashMap<>();
/**
* 當窗口有活動時孕蝉,會回調此方法
* @param event
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityNodeInfo nodeInfo = event.getSource();
if (nodeInfo != null) {
int eventType = event.getEventType();
if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED || eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
if (handleMap.get(event.getWindowId()) == null) {
boolean handled = iterateNodesAndHandle(nodeInfo);
if (handled) {
handleMap.put(event.getWindowId(), true);
}
}
}
}
}
/**
* 遞歸處理節(jié)點信息
* 節(jié)點名稱為Button,內容為 安裝腌逢,確定降淮,完成的,模擬點擊
* 節(jié)點名稱是ScrollView搏讶,模擬滑動到底部
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
private boolean iterateNodesAndHandle(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo != null) {
int childCount = nodeInfo.getChildCount();
if ("android.widget.Button".equals(nodeInfo.getClassName())) {
String nodeContent = nodeInfo.getText().toString();
if ("安裝".equals(nodeContent) || "確定".equals(nodeContent) || "完成".equals(nodeContent)) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
return true;
}
} 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;
}
@Override
public void onInterrupt() {
}
}
每次當有新的AccessibilityEvent時佳鳖,就會回調onAccessibilityEvent
方法。這里我們處理了兩種事件類型媒惕,AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
和AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
系吩,分別代表窗體狀態(tài)的變化和窗體內容的變化,符合條件的節(jié)點再對節(jié)點內容進行分析:
- 當節(jié)點名稱是
Button
時妒蔚,且節(jié)點內容是安裝
||確定
||完成
時穿挨,模擬點擊事件AccessibilityNodeInfo.ACTION_CLICK
- 當節(jié)點名稱是
ScrollView
時,這時候是在顯示權限列表肴盏,一些系統(tǒng)會要求顯示完全部權限科盛,這時候模擬上滑。
既然是一個Service菜皂,就要去注冊它贞绵。這里大多是固定寫法。
<service android:name=".MyAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config"/>
</service>
在使用時恍飘,先提醒用戶開啟相應輔助功能榨崩,如下代碼,跳轉到輔助功能設置界面:
Intent intent=new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
然后安裝apk章母,
Uri uri=Uri.fromFile(new File(path));
Intent intent=new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri,"application/vnd.android.package-archive");
startActivity(intent);
前提是用戶確實開啟了輔助功能母蛛,這時候才會自動智能安裝。
不管是哪種方式胳施,缺陷都是很大的溯祸。靜默安裝需要足夠的權限肢专,智能安裝需要用戶去開啟輔助功能舞肆,很多用戶應該都不會去操作的〔┱龋或許也正想看看你申請了哪些該死的權限椿胯。面對安卓雜亂的生態(tài)環(huán)境,希望在某些小小的方面可以達成一致性剃根,就像最近Google提出要強制統(tǒng)一通知中心哩盲。如果安卓能有一個統(tǒng)一的通知機制,當然是指在國內。就會少了多少服務相互喚醒廉油,就不會有那么多廠商去標榜自己的通知到達率惠险。
Android靜默安裝實現(xiàn)方案,仿360手機助手秒裝和智能安裝功能 ——————郭霖
https://developer.android.com/guide/topics/ui/accessibility/services.html
有任何疑問抒线,歡迎加群討論:261386924