Android 高仿豌豆莢 一鍵安裝app 功能 實現

希爾瓦娜斯女神發表於2015-10-16

以往我們那些應用市場 幫我們安裝app的時候  我們都得點確定,當然你如果 root 以後 不用點確定 也能自動安裝了,後來豌豆莢 推出了一個功能 非root的手機也能不點確定 直接幫你安裝好。(如果不理解我這段話意思的同學 趕緊試用豌豆莢就知道了)

實際上 這個功能還是蠻重要的,比如我們的app 如果需要強制升級 什麼的,使用者下載好 你啟動installer 然後還要使用者點確定才能安裝,你看這就是使用者體驗不好嗎 對吧,學會這個可以幫我們做很多事。

當然了 首先要感謝豌豆莢團隊 在csdn做的採訪,這是這篇文章的基礎 http://www.csdn.net/article/1970-01-01/2824737 他透露了這個功能點的point。

好廢話不多說 我們直接上程式碼吧,因為這個功能所涉及到的api 比較小眾,我就不過多介紹了,有需要的同學可以參考 官方文件的這個training http://developer.android.com/intl/zh-cn/training/accessibility/service.html

我著重提一下,千萬不要用這種方式去實現 流氓軟體的流氓功能,作為android 開發,一個好的生態圈是要我們自己去維護的,不要學 百度 那種流氓apk!

首先 我們來定義一個特殊的服務:

 1 package com.example.administrator.powertest;
 2 
 3 import android.accessibilityservice.AccessibilityService;
 4 import android.view.accessibility.AccessibilityEvent;
 5 import android.view.accessibility.AccessibilityNodeInfo;
 6 
 7 import java.util.List;
 8 
 9 /**
10  * 這個服務是不需要你在activity裡去開啟的,屬於系統級別輔助服務 需要在設定裡去手動開啟 和我們平常app裡
11  * 經常使用的service 是有很大不同的 非常特殊
12  * 你可以在 \sdk\samples\android-23\legacy\ApiDemos 這樣的目錄下 找到這個工程 這個工程下面有一個accessibility
13  * 包 裡面有關於這個服務的demo 當然他們那個demo 非常複雜,但是資訊量很大,有興趣深入研究的同學可以多看demo
14  * 我這裡只實現最基本的功能 且沒有做冗餘和異常處理,只包含基礎功能,不能作為實際業務上線!
15  */
16 public class MyAccessibilityService extends AccessibilityService {
17     public MyAccessibilityService() {
18     }
19 
20     /**
21      * AccessibilityService 這個服務可以關聯很多屬性,這些屬性 一般可以通過程式碼在這個方法裡進行設定,
22      * 我這裡偷懶 把這些設定屬性的流程用xml 寫好 放在manifest裡,如果你們要使用的時候需要區分版本號
23      * 做相容,在老的版本里是無法通過xml進行引用的 只能在這個方法裡手寫那些屬性 一定要注意.
24      * 同時你的業務如果很複雜比如需要初始化廣播啊之類的工作 都可以在這個方法裡寫。
25      */
26     @Override
27     protected void onServiceConnected() {
28         super.onServiceConnected();
29     }
30 
31     /**
32      * 當你這個服務正常開啟的時候,就可以監聽事件了,當然監聽什麼事件,監聽到什麼程度 都是由給這個服務的屬性來決定的,
33      * 我的那些屬性寫在xml裡了。
34      */
35     @Override
36     public void onAccessibilityEvent(AccessibilityEvent event) {
37         /**
38          * 事件是分很多種的,我這裡是最簡單的那種,只演示核心功能,如果要做成業務上線 這裡推薦一個方法可以快速理解這裡的type屬性。
39          * 把這個type的int 值取出來 並轉成16進位制,然後去AccessibilityEvent 原始碼裡find。順便看註釋 ,這樣是迅速理解type型別的方法
40          */
41         final int eventType = event.getEventType();
42         switch (eventType) {
43             case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
44                 //這個地方沒什麼好說的 你就理解成 找到當前介面 包含有安裝 這個關鍵詞的 所有節點就可以了。返回這些節點的list
45                 //注意這裡的find 其實是contains的意思,比如你介面上有2個節點,一個節點內容是安裝1 一個節點內容是安裝2,那這2個節點是都會返回過來的
46                 //除了有根據Text找節點的方法 還有根據Id找節點的方法。考慮到眾多手機rom都不一樣,這裡需要大家多測試一下,有的rom packageInstall
47                 //定製的比較深入,可能和官方rom裡差的很遠 這裡就要做冗餘處理,可以告訴大家一個小技巧 你就把這些rom的 安裝器開啟 然後
48                 //通過ddms裡 看view結構的按鈕 直接進去看就行了,可以直接看到那個介面屬於哪個包名,也可以看到你要捕獲的那個按鈕的id是什麼 很方便!
49                 List<AccessibilityNodeInfo> list = event.getSource().findAccessibilityNodeInfosByText("安裝");
50                 if (null!=list){
51                     for (AccessibilityNodeInfo info : list) {
52                         if (info.getText().toString().equals("安裝"))
53                         {
54                             //找到你的節點以後 就直接點選他就行了
55                             info.performAction(AccessibilityNodeInfo.ACTION_CLICK);
56                         }
57                     }
58                 }
59                 break;
60             default:
61                 break;
62         }
63     }
64     @Override
65     public void onInterrupt() {
66 
67     }
68 }

服務定義好了 就要在配置檔案裡配置一下,看manifest的主要程式碼:

 1 <!-- label 這個就是在設定介面顯示的label 應該比較好理解了-->
 2         <service
 3             android:name=".MyAccessibilityService"
 4             android:exported="true"
 5             android:label="女神的自動裝"
 6             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
 7             <intent-filter>
 8                 <action android:name="android.accessibilityservice.AccessibilityService" />
 9             </intent-filter>
10             <meta-data
11                 android:name="android.accessibilityservice"
12                 android:resource="@xml/taskbackconfig" />
13         </service>

然後我們在res路徑下 新建一個xml 資料夾 並在下面 新建一個xml檔案取名為taskbackconfig.xml

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <!--
 3   Copyright (C) 2011 The Android Open Source Project
 4 
 5   Licensed under the Apache License, Version 2.0 (the "License");
 6   you may not use this file except in compliance with the License.
 7   You may obtain a copy of the License at
 8 
 9        http://www.apache.org/licenses/LICENSE-2.0
10 
11   Unless required by applicable law or agreed to in writing, software
12   distributed under the License is distributed on an "AS IS" BASIS,
13   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   See the License for the specific language governing permissions and
15   limitations under the License.
16  -->
17 <!--
18 這個就是給我們的AccessibilityService 設定屬性的,當然你也可以在程式碼裡 的 connnect函式裡 手動設定。可以向下相容。
19 accessibilityFeedbackType 這個屬性如果不設定的話 我們那個onAccessibilityEvent 這個回撥函式 根本回撥不了 所以這裡要注意
20 packageNames 這個屬性 就是捕獲什麼app的行為的,比如我這裡寫的包名是packageinstaller 那就肯定只能捕獲安裝器的 事件了
21 有的rom 安裝器可能不是這個包名 那你就要進行特殊設定了,此外這個屬性你如果什麼都不寫 就意味著 你可以捕獲所有手機的動作
22 如果你要做流氓軟體的話 可以packageNames 裡面什麼都不寫。。。甚至可以操作支付寶 給你打錢。。。如果你知道使用者密碼的話。
23 當然你如果真這麼做了 相信捕獲一次使用者輸入密碼的行為 也是很容易的。。細思極恐 我就不往下深入了。。。
24 
25 description 這個就是對你那個申請服務的時候說明了,可以寫的煽情一點 讓使用者開啟這個服務的可能性更高一點。。。
26 -->
27 <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
28     android:accessibilityEventTypes="typeAllMask"
29     android:notificationTimeout="100"
30     android:packageNames="com.android.packageinstaller"
31     android:accessibilityFeedbackType="feedbackSpoken"
32     android:canRetrieveWindowContent="true"
33     android:description="@string/hint" />

到此時就差不多了,我們再把activity的程式碼放上來:

  1 package com.example.administrator.powertest;
  2 
  3 import android.content.BroadcastReceiver;
  4 import android.content.Context;
  5 import android.content.Intent;
  6 import android.content.IntentFilter;
  7 import android.net.Uri;
  8 import android.os.Bundle;
  9 import android.provider.Settings;
 10 import android.support.design.widget.FloatingActionButton;
 11 import android.support.design.widget.Snackbar;
 12 import android.support.v4.content.LocalBroadcastManager;
 13 import android.support.v7.app.AppCompatActivity;
 14 import android.support.v7.widget.Toolbar;
 15 import android.util.Log;
 16 import android.view.View;
 17 import android.view.Menu;
 18 import android.view.MenuItem;
 19 import android.widget.TextView;
 20 
 21 import java.io.File;
 22 
 23 public class MainActivity extends AppCompatActivity {
 24 
 25     private TextView tv,installTv;
 26     /**
 27      * 你得引導使用者去設定介面嗎,你不能讓使用者自己去找吧。
 28      */
 29     private static final Intent sSettingsIntent =
 30             new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
 31     //這裡就假設想要安裝的apk 是扇貝網 並且在sd卡根目錄下面
 32     private static final String FILE_PATH="/mnt/sdcard/shanbeidanci6.0.000.apk";
 33 
 34 
 35     @Override
 36     protected void onCreate(Bundle savedInstanceState) {
 37         super.onCreate(savedInstanceState);
 38         setContentView(R.layout.activity_main);
 39         Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
 40         setSupportActionBar(toolbar);
 41 
 42         FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
 43         fab.setOnClickListener(new View.OnClickListener() {
 44             @Override
 45             public void onClick(View view) {
 46                 Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
 47                         .setAction("Action", null).show();
 48             }
 49         });
 50 
 51         tv = (TextView) findViewById(R.id.tv);
 52         tv.setOnClickListener(new View.OnClickListener() {
 53 
 54             @Override
 55             public void onClick(View v) {
 56                 startActivity(sSettingsIntent);
 57             }
 58         });
 59         installTv=(TextView)this.findViewById(R.id.tv2);
 60         installTv.setOnClickListener(new View.OnClickListener(){
 61 
 62             @Override
 63             public void onClick(View v) {
 64                 //呼叫安裝器去安裝我們的apk 一鍵安裝開始啦,如果使用者把那個服務開啟了的話。
 65                 Intent intent = new Intent(Intent.ACTION_VIEW);
 66                 intent.setDataAndType(Uri.fromFile(new File(FILE_PATH)), "application/vnd.android.package-archive");
 67                 startActivity(intent);
 68             }
 69         });
 70     }
 71 
 72     @Override
 73     public boolean onCreateOptionsMenu(Menu menu) {
 74         // Inflate the menu; this adds items to the action bar if it is present.
 75         getMenuInflater().inflate(R.menu.menu_main, menu);
 76         return true;
 77     }
 78 
 79     private class ResponseReceiver extends BroadcastReceiver {
 80 
 81         public void onReceive(Context context, Intent intent) {
 82 
 83             tv.setText(intent.getStringExtra("msg"));
 84         }
 85     }
 86 
 87     @Override
 88     public boolean onOptionsItemSelected(MenuItem item) {
 89         // Handle action bar item clicks here. The action bar will
 90         // automatically handle clicks on the Home/Up button, so long
 91         // as you specify a parent activity in AndroidManifest.xml.
 92         int id = item.getItemId();
 93 
 94         //noinspection SimplifiableIfStatement
 95         if (id == R.id.action_settings) {
 96             return true;
 97         }
 98 
 99         return super.onOptionsItemSelected(item);
100     }
101 }

到此所有程式碼就結束了,如果你想做的好一點 ,請自己做冗餘異常處理,我這裡主要演示功能就不做的那麼細緻了,最後看下跑起來的效果吧:

 

相關文章