1. 程式人生 > >轉載:Android 靜默安裝和智慧安裝的實現方法

轉載:Android 靜默安裝和智慧安裝的實現方法

1 簡介

最近研究了Android的靜默安裝和智慧安裝,於是寫部落格記錄一下。  靜默安裝就是無聲無息的在後檯安裝apk,沒有任何介面提示。  智慧安裝就是有安裝介面,但全部是自動的,不需要使用者去點選。  首先強調兩點:

  1. 靜默安裝必須要root許可權
  2. 智慧安裝必須要使用者手動開啟無障礙服務

2 原理

  1. 靜默安裝、解除安裝的原理就是利用pm install命令來安裝apk,pm uninstall 來解除安裝apk.
  2. 智慧安裝是利用android系統提供的無障礙服務AccessibilityService,來模擬使用者點選,從而自動安裝.

3 pm命令介紹

(1) pm install

  pm install 命令的用法及引數解釋如下:

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.

(2) pm uninstall  pm uninstall 命令的用法及引數解釋如下:

pm uninstall [-k] PACKAGE

Options:
  -k: keep the data and cache directories around.

上面英語很簡單,不解釋了.

4 靜默安裝

為了方便演示,我把愛奇藝的安裝包重新命名為test.apk後放在了sdcard上。你可以自己去愛奇藝官網去下載,也可以自己找一個apk放到sdcard上,但是要知道apk的包名,後面解除安裝的時候要用到。  先上程式碼:

//靜默安裝
    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許可權
            process = Runtime.getRuntime().exec("su");
            os = new DataOutputStream(process.getOutputStream());
            os.write(cmd.getBytes());
            os.writeBytes("\n");
            os.writeBytes("exit\n");
            os.flush();
            //執行命令
            process.waitFor();
            //獲取返回結果
            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();
            }
        }
        //顯示結果
        tvTest.setText("成功訊息:" + successMsg.toString() + "\n" + "錯誤訊息: " + errorMsg.toString());
    }

這段程式碼就是在程式中執行pm命令,和在adb下執行 pm install -r /mnt/sdcard/test.apk 效果是一樣的, 關鍵的程式碼是 Runtime.getRuntime().exec(“su”) ,這段程式碼會要求獲取root許可權,所以你的手機必須root,不想root的話,直接用模擬器也可以。

通過 Runtime.getRuntime().exec(“su”) 獲取到 process 物件後就可以寫入命令了,每寫入一條命令就要換行,寫入 ‘\n’ 即可,最後寫入exit後離開命令執行的環境.

5 靜默解除安裝

靜默解除安裝和靜默安裝是一樣的,只是命令不同,靜默解除安裝需要用到包名,同樣,靜默解除安裝也需要root許可權  看程式碼:

//愛奇藝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許可權
            process = Runtime.getRuntime().exec("su");
            os = new DataOutputStream(process.getOutputStream());
            os.write(cmd.getBytes());
            os.writeBytes("\n");
            os.writeBytes("exit\n");
            os.flush();
            //執行命令
            process.waitFor();
            //獲取返回結果
            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();
            }
        }
        //顯示結果
        tvTest.setText("成功訊息:" + successMsg.toString() + "\n" + "錯誤訊息: " + errorMsg.toString());
    }

和靜默安裝一樣的程式碼就不解釋了。還有,如果你不知道一個apk的包名,那麼請反編譯後去看AndroidManifest.xml檔案,如果這個檔案開啟全是亂碼,說明是被混淆過的,那麼直接安裝它,然後到/data/data下面去找它的包,當然,手機得root才能進/data/data目錄。

6 智慧安裝

智慧安裝就稍微麻煩點了,原理是用到了android提供的AccessibilityService服務,這個服務可以獲取螢幕上的節點,一個節點也就是一個view,我們寫的xml檔案中每個標籤就是一個節點,然後可以模擬使用者的操作,對這些節點進行點選、滑動等操作。我們就是利用這個原理,來自動點選安裝按鈕的,當然使用這個服務必須使用者手動開啟無障礙服務。下面我們來看具體的實現方法。

(1) 建立AccessibilityService配置檔案  在res目錄下建立xml目錄,然後在xml目錄下建立一個accessibility_service_config.xml檔案,內容如下  res/xml/accessibility_service_config.xml:

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
                       android:accessibilityEventTypes="typeAllMask"
                       android:accessibilityFeedbackType="feedbackGeneric"
                       android:accessibilityFlags="flagDefault"
                       android:canRetrieveWindowContent="true"
                       android:description="@string/desc"
                       android:packageNames="com.android.packageinstaller"
    />

accessibilityEventTypes:指定我們在監聽視窗中可以模擬哪些事件,typeAllMask表示所有的事件都能模擬.  accessibilityFeedbackType:指定無障礙服務的反饋方式.  canRetrieveWindowContent:指定是否允許我們的程式讀取視窗中的節點和內容,當然是true.  description: 當用戶手動配置服務時,會顯示給使用者看.  packageNames: 指定我們要監聽哪個應用程式下的視窗活動,這裡寫com.android.packageinstaller表示監聽Android系統的安裝介面。  其餘引數照寫即可。  res/strings.xml:

<resources>
    <string name="app_name">SlientInstallTest</string>
    <string name="desc">智慧安裝app功能演示</string>
</resources>

(2) 建立AccessibilityService服務

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() {

    }

    //遍歷節點,模擬點選安裝按鈕
    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的時候模擬滑動一下
            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;
    }
}

當進入apk安裝介面就會回撥onAccessibilityEvent()這個方法,我們只關心TYPE_WINDOW_CONTENT_CHANGED和TYPE_WINDOW_STATE_CHANGED兩個事件,為了防止重複處理事件,用一個map來過濾事件,後面遞迴遍歷節點,找到’安裝’ ‘完成’ ‘確定’ 的按鈕,就點選,由於安裝介面需要滾動一下才能出現安裝按鈕,所以遇到ScrollView的時候就滾動一下.

(3) 在AndroidManifest中配置服務

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <service
            android:name=".MyAccessibilityService"
            android:label="智慧安裝App"
            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>
    </application>

重點是後面的service標籤:  android:label:這個就是使用者看到的無障礙服務的名稱  android:permission: 需要用到BIND_ACCESSIBILITY_SERVICE這個許可權.  action: android.accessibilityservice.AccessibilityService 有了這個action,使用者才能在設定裡面看到我們的服務,否則使用者無法開啟我們寫的服務,也就不能進到我們寫的MyAccessibilityService裡面了.所以,注意不要寫錯了,如果你發現無障礙服務裡面沒有我們寫的服務,請檢查這裡.

(4) 呼叫智慧安裝程式碼  前面準備工作完畢後,現在要用了,呼叫智慧安裝的程式碼如下:

 //智慧安裝
    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);
    }

(5) 手動配置智慧安裝服務  程式碼執行之後,還要使用者選擇開啟智慧安裝服務,讓使用者自己去找是不明智的,因此,我們要主動跳到配置介面,程式碼如下:

//跳轉到開啟智慧安裝服務的介面
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);

配置如下圖:

這裡寫圖片描述

看到了嗎,上面顯示的就是Service裡面的label的值,如果你沒有上面的選項,請檢查AndroidManifest裡面Service的配置.  點選’智慧安裝App’,開啟服務,如下圖:

這裡寫圖片描述

其中的提示文字就是我們在res/xml/accessibility_service_config.xml檔案中配置的description屬性

7 只能我們寫的app可以自動安裝

這樣寫完程式碼可以執行,點選按鈕自動安裝sdcard上的test.apk.但是你會發現,所有apk都會自動安裝,這就不符合我們的要求了,我們要求只能通過我們寫的app來自動安裝,其他apk還是要使用者手動去點。怎麼解決這個問題呢?  思路是:在MainActivity中建立一個public static boolean flag,在MyAccessibilityService的onAccessibilityEvent()中加一個flag判斷,然後呼叫智慧安裝前flag設為true,建立apk安裝事件的廣播接收器,當apk安裝完成後,設定falg為false,這樣其他apk就不能自動安裝了,就解決了這個問題  下面上完整程式碼.

8 完整程式碼

app/MainActivity.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才能執行智慧安裝
    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);
        //註冊apk安裝監聽
        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;
            //設定無障礙服務
            case R.id.btn_set:
                //跳轉到開啟無障礙服務的介面
                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許可權
            process = Runtime.getRuntime().exec("su");
            os = new DataOutputStream(process.getOutputStream());
            os.write(cmd.getBytes());
            os.writeBytes("\n");
            os.writeBytes("exit\n");
            os.flush();
            //執行命令
            process.waitFor();
            //獲取返回結果
            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();
            }
        }
        //顯示結果
        tvTest.setText("成功訊息:" + successMsg.toString() + "\n" + "錯誤訊息: " + 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許可權
            process = Runtime.getRuntime().exec("su");
            os = new DataOutputStream(process.getOutputStream());
            os.write(cmd.getBytes());
            os.writeBytes("\n");
            os.writeBytes("exit\n");
            os.flush();
            //執行命令
            process.waitFor();
            //獲取返回結果
            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();
            }
        }
        //顯示結果
        tvTest.setText("成功訊息:" + successMsg.toString() + "\n" + "錯誤訊息: " + 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);
    }

    //監聽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);
                //安裝完畢,設定flag,從而使得其餘的apk不能自動安裝
                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);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198

介面上就三個按鈕  res/layout/activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="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">

    <TextView
        android:id="@+id/tv_test"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text=""
        />

    <Button
        android:id="@+id/btn_install"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="安裝"
        />

    <Button
        android:id="@+id/btn_uninstall"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/btn_install"
        android:text="解除安裝"
        />

    <Button
        android:id="@+id/btn_set"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/btn_uninstall"
        android:text="開啟智慧安裝功能"
        />

    <Button
        android:id="@+id/btn_smart_install"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/btn_set"
        android:text="智慧安裝"
        />
</RelativeLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

服務配置檔案  res/xml/accessibility_service_config.xml

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
                       android:accessibilityEventTypes="typeAllMask"
                       android:accessibilityFeedbackType="feedbackGeneric"
                       android:accessibilityFlags="flagDefault"
                       android:canRetrieveWindowContent="true"
                       android:description="@string/desc"
                       android:packageNames="com.android.packageinstaller"
    />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

智慧安裝服務  app/MyAccessibilityService.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() {

    }

    //遍歷節點,模擬點選安裝按鈕
    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的時候模擬滑動一下
            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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

最後是配置檔案AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.slientinstalltest">

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <service
            android:name=".MyAccessibilityService"
            android:label="智慧安裝App"
            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>
    </application>

</manifest>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

注意:請把自己要安裝的apk放到sdcard上,並且修改程式碼中的apk路徑和包名

9 執行效果

這裡寫圖片描述

10 程式碼下載

演示程式碼已經上傳,下載地址:

11 總結

Android智慧安裝的原理就是利用了類似鉤子的服務,這個服務還可以用於微信搶紅包的開發,怎麼樣,是不是比ios好玩兒的多呢.