Android实现静默安装和智能安装

静默安装,就是指在程序安装时,用户并不会感知到安装的过程,自己就安装完成了。一些系统自带应用市场会具有静默安装的功能,比如小米的应用市场。在一些非系统自带的应用市场,要想完成静默安装,就必须具有root权限。可见权限的重要性,在系统的支持下,你可以做到很多别人做不到的事情。当然,像360手机卫士,应用宝,豌豆荚之类的非系统支持的应用市场,大多使用了智能安装,仍然会弹出系统安装弹窗,但是会迅速自动点击安装按钮。对大多数用户来说,还是未root用户比较多,因为这种方式也是很常见的。

一.静默安装

静默安卓有两个前提条件,一个是手机必须具有root权限,一个是系统是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 命令,执行。-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权限,否则是没有办法成功执行pm命令的。执行完成后通过读取安装结果,判断是否安装成功。我们也可以先判断一下当前是否具有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来实现对安装界面的自动点击。什么是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的残疾人的,运行在后台,当系统发生一些AccessibilityEvent时会产生回调。通过这些回调,我们可以通过代码做一些事情。这里我们要做的就是,当系统安装弹窗出现时,通过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监听哪个包名下的事件。这里的com.android.packageinstaller就是指安卓的安装界面。
  • description,当在辅助功能界面点击你自己的app后,会显示这些文字。你可以描述你开启辅助功能的目的,为自己狡辩一下。
  • accessibilityEventTypes,The event types this serivce would like to receive as specified in AccessibilityEvent.我们可以在监听窗口中模拟哪些事件。typeAllMask代表所有。
  • accessibilityFlags,Additional flags as specified in AccessibilityServiceInfo.一些附加参数,默认即可。
  • accessibilityFeedbackType,无障碍服务的反馈方式,比如针对残疾人可以语音反馈,这里并不需要。
  • canRetrieveWindowContent,Attribute whether the accessibility service wants to be able to retrieve the active window content. 我们是否可以检索窗口中的内容,当然应该是可以的。

然后我们需要继承AccessibilityService,重写相关方法实现智能安装的具体逻辑。如下所示:

/**
 * 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);
                }
            }
        }
    }
}

/**
 * 递归处理节点信息
 * 节点名称为Button,内容为 安装,确定,完成的,模拟点击
 * 节点名称是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_CHANGEDAccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,分别代表窗体状态的变化和窗体内容的变化,符合条件的节点再对节点内容进行分析:

  • 当节点名称是Button时,且节点内容是安装 || 确定 || 完成时,模拟点击事件AccessibilityNodeInfo.ACTION_CLICK
  • 当节点名称是ScrollView时,这时候是在显示权限列表,一些系统会要求显示完全部权限,这时候模拟上滑。

既然是一个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);

前提是用户确实开启了辅助功能,这时候才会自动智能安装。

不管是哪种方式,缺陷都是很大的。静默安装需要足够的权限,智能安装需要用户去开启辅助功能,很多用户应该都不会去操作的。或许也正想看看你申请了哪些该死的权限。面对安卓杂乱的生态环境,希望在某些小小的方面可以达成一致性,就像最近Google提出要强制统一通知中心。如果安卓能有一个统一的通知机制,当然是指在国内。就会少了多少服务相互唤醒,就不会有那么多厂商去标榜自己的通知到达率。

源码下载

Android静默安装实现方案,仿360手机助手秒装和智能安装功能 ——————郭霖
https://developer.android.com/guide/topics/ui/accessibility/services.html

有任何疑问,欢迎加群讨论:261386924

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容