文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

android AccessibilityService无障碍功能开发,实现自动化测试

2023-09-13 10:27

关注

android AccessibilityService无障碍功能开发,实现自动化测试,这里使用抖音为例子,仅供技术研究学习使用。

使用方法

安装好APP后,需要打开无障碍功能,打开后,在次打开抖音APP,随便找一个直播间,上下滑动切换直接后,实现模拟点击屏幕,可以自动完成关注。

代码如下

package com.nyw.testclick;import androidx.annotation.RequiresApi;import androidx.appcompat.app.AppCompatActivity;import android.accessibilityservice.AccessibilityService;import android.accessibilityservice.AccessibilityServiceInfo;import android.accessibilityservice.GestureDescription;import android.content.Context;import android.content.Intent;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.content.pm.ServiceInfo;import android.graphics.Path;import android.graphics.Point;import android.graphics.Rect;import android.net.Uri;import android.os.Build;import android.os.Bundle;import android.provider.Settings;import android.text.TextUtils;import android.util.Log;import android.view.accessibility.AccessibilityManager;import android.view.accessibility.AccessibilityNodeInfo;import android.widget.Toast;import java.util.List;public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        boolean enabled = isAccessibilityServiceEnabled(this, MyAccessibilityService.class);        //判断是否安装抖音        boolean exist1 = checkAppInstalled(MainActivity.this, "com.ss.android.ugc.aweme");        //抖音极速版        //boolean exist1 = checkAppInstalled(getContext(), "com.ss.android.article.video");        //抖音火山版        //boolean exist1 = checkAppInstalled(getContext(), "com.ss.android.ugc.live");        if (exist1) {            Intent intent = new Intent();            //抖音 打开个人中心  104248958804 需要去获取抖音的UserId//            intent.setData(Uri.parse("snssdk1128://user/profile/104248958804"));            // 打开首页            intent.setData(Uri.parse("snssdk1128://feed?refer=web&gd_label=1"));            //抖音极速版            //intent.setData(Uri.parse("snssdk1112://user/profile/104248958804"));            //抖音火山版            //intent.setData(Uri.parse("snssdk1112://profile?id=104248958804"));            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);            startActivity(intent);        } else {            Toast.makeText(MainActivity.this, "请先安装此应用", Toast.LENGTH_SHORT).show();        }        if (enabled==false){            final String actionOpen=Settings.ACTION_ACCESSIBILITY_SETTINGS;//系统辅助功能服务            Intent intent=new Intent(actionOpen);            startActivity(intent);        }    }        public static boolean isAccessibilityServiceEnabled(Context context, Class service) {        AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);        List enabledServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);        for (AccessibilityServiceInfo enabledService : enabledServices) {            ServiceInfo enabledServiceInfo = enabledService.getResolveInfo().serviceInfo;            if (enabledServiceInfo.packageName.equals(context.getPackageName()) && enabledServiceInfo.name.equals(service.getName()))                return true;        }        return false;    }        private boolean checkAppInstalled(Context context, String pName) {        if (pName == null || pName.isEmpty()) {            return false;        }        final PackageManager packageManager = context.getPackageManager();        List info = packageManager.getInstalledPackages(0);        if (info == null || info.isEmpty()) {            return false;        }        for (int i = 0; i < info.size(); i++) {            if (pName.equals(info.get(i).packageName)) {                return true;            }        }        return false;    }}

 自定义一个服务MyAccessibilityService,继承AccessibilityService,实现2个方法,重写一个方法,代码如下

package com.nyw.testclick;import android.accessibilityservice.AccessibilityService;import android.accessibilityservice.AccessibilityServiceInfo;import android.accessibilityservice.GestureDescription;import android.content.Context;import android.graphics.Path;import android.graphics.Point;import android.graphics.Rect;import android.os.Build;import android.provider.Settings;import android.text.TextUtils;import android.util.Log;import android.view.KeyEvent;import android.view.accessibility.AccessibilityEvent;import android.view.accessibility.AccessibilityManager;import android.view.accessibility.AccessibilityNodeInfo;import androidx.annotation.RequiresApi;import java.util.List;public class MyAccessibilityService extends AccessibilityService {    @Override    public void onAccessibilityEvent(AccessibilityEvent event) {        //这个函数可以接收系统发送来的AccessibilityEvent,接收来的AccessibilityEvent是经过过滤的,过滤是在配置工作时设置的        //event.getSource()  这是当前event的节点信息       // AccessibilityService.getRootInActiveWindow();  获取到当前活跃中本服务的可检索到窗口的根节点       // AccessibilityNodeInfo.recycle()//为避免创建重复的实例通过recycle方法回收掉nodeInfo        //event.TYPE_NOTIFICATION_STATE_CHANGED  基本窗口view的变化都可以使用这个type来监听        //event.TYPE_WINDOW_STATE_CHANGED  打开popupwindow,菜单,对话框时候会触发        //event.TYPE_WINDOW_CONTENT_CHANGED  更加精确的代表了基于当前event.source中的子view的内容变化        //event.TYPE_WINDOWS_CHANGED  窗口的变化        //获取到当前活跃中本服务的可检索到窗口的根节点 两种方式获取的childNode个数不一致//        AccessibilityNodeInfo mNodeInfo1 = getRootInActiveWindow();//获取NodeInfo//        AccessibilityNodeInfo mNodeInfo2= event.getSource();//获取NodeInfo        //查找我们需要做操作的view//        List listNodes1 =mNodeInfo1. findAccessibilityNodeInfosByViewId("id");//操作的view,查找我们需要操作的对象方法之一//        List listNodes2=mNodeInfo2.findAccessibilityNodeInfosByText("id");//操作的view,查找我们需要操作的对象方法之一       // findFocus(0);//查找拥有特定焦点类型的控件       // getRootInActiveWindow();//如果配置能够获取窗口内容,则会返回当前活动窗口的根结点//        getServiceInfo();//获取当前服务的配置信息//        performGlobalAction(0);//执行全局操作,比如返回,回到主页,打开最近等操作//        event.getClassName();//获取事件源对应类的类型,比如点击事件是有某个Button产生的,那么此时获取的就是Button的完整类名//        event. getText();//获取事件源的文本信息,比如事件是有TextView发出的,此时获取的就是TextView的text属性.如果该事件源是树结构,那么此时获取的是这个树上所有具有text属性的值的集合//        event.isEnabled();//事件源(对应的界面控件)是否处在可用状态//        event.getItemCount();//如果事件源是树结构,将返回该树根节点下子节点的数量        try {            int eventType = event.getEventType();//事件类型            String packageName = event.getPackageName().toString();            String className = event.getClassName().toString();            List list5 = null;            List list6 = null;            List list7 = null;            AccessibilityNodeInfo rootNodeInfo = getRootInActiveWindow();            if ("android.widget.ImageView".equals(event.getClassName().toString())) {                perforGlobalClick("com.ss.android.ugc.aweme:id/gsb");                Log.i("sdfkslfsfks", "用户名:" + getTextById("com.ss.android.ugc.aweme:id/m39"));                Log.i("sdfkslfsfks", "文本信息:" + getTextById(" id:com.ss.android.ugc.aweme:id/ag7"));            }            switch (eventType) {                case AccessibilityEvent.TYPE_VIEW_CLICKED:                    // 界面点击                    Log.i("sdfkslfsfks", "页面点击了");                    Log.d("sdfkslfsfks", event.getClassName() + "                    " + event.getSource().getViewIdResourceName());//                logViewHierarchy(getRootInActiveWindow(), 0);//                perforGlobalClick("com.ss.android.ugc.aweme:id/efr");                    break;                case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:                    // 界面文字改动                    Log.i("sdfkslfsfks", "界面文字改动");                    perforGlobalClick("com.ss.android.ugc.aweme:id/gsb");                    break;                case AccessibilityEvent.TYPE_VIEW_SCROLLED:                    //滚动视图的事件。此事件类型通常不直接发送                    Log.e("sdfkslfsfks", "onServiceConnected:" + "实现辅助功能");                    Log.d("TAG", "packageName = " + packageName + ", className = " + className);                    //滑动就自动点赞及关注                    perforGlobalClick("com.ss.android.ugc.aweme:id/gsb");                    if (className.equals("com.lynx.tasm.behavior.KeyboardMonitor")) {                        Log.e("sdfkslfsfks", "执行了搜索按钮");                        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {list5 = rootNodeInfo.findAccessibilityNodeInfosByViewId("com.ss.android.ugc.aweme:id/rzy");                        }                        if (null != list5) {for (AccessibilityNodeInfo info : list5) {    clickByNode(this, info);}                        }                    }                    if (className.equals("androidx.recyclerview.widget.RecyclerView")) {                        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {list6 = rootNodeInfo.findAccessibilityNodeInfosByText("用户");                        }                        if (null != list6) {for (AccessibilityNodeInfo info : list6) {    Log.e("sdfkslfsfks", info.toString());    clickByNode(this, info.getParent());}                        }                        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {list7 = rootNodeInfo.findAccessibilityNodeInfosByText("关注");                        }                        if (null != list7) {for (AccessibilityNodeInfo info : list7) {    Log.e("sdfkslfsfks", info.toString());}                        }                    }                    //获取根节点                    AccessibilityNodeInfo rootNode = getRootInActiveWindow();                    //匹配Text获取节点                    List list1 = rootNode.findAccessibilityNodeInfosByText("match_text");                    //匹配id获取节点                    List list2 = rootNode.findAccessibilityNodeInfosByViewId("match_id");                    //获取子节点                    AccessibilityNodeInfo infoNode = rootNode.getChild(0);                    break;            }        }catch (Exception exception){}    }    @Override    public void onInterrupt() {        //在系统将要关闭这个AccessibilityService会被调用。在这个方法中进行一些释放资源的工作。       // disableSelf();//禁用服务。调用此方法后,服务将被禁用,设置将显示它已关闭。    }    @Override    protected void onServiceConnected() {        super.onServiceConnected();        //重载的方法        //系统成功绑定该服务时被触发调用这个方法,在这个方法里你可以做一下初始化工作,例如设备的声音震动管理,也可以调用setServiceInfo()进行配置工作        //配置,可在这里配制,也可以在values中添加配制文件进行配制,2种方法二选一//        AccessibilityServiceInfo accessibilityServiceInfo=new AccessibilityServiceInfo();//        accessibilityServiceInfo.eventTypes=AccessibilityEvent.TYPES_ALL_MASK;//        accessibilityServiceInfo.feedbackType=AccessibilityServiceInfo.FEEDBACK_GENERIC;//        accessibilityServiceInfo.flags = AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;//获取View的Id//        accessibilityServiceInfo.notificationTimeout=100;//        accessibilityServiceInfo.packageNames=new String[]{"com.nyw.testclick","com.ss.android.ugc.aweme"};//自动点击的包名//        setServiceInfo(accessibilityServiceInfo);//设置当前服务的配置信息        }//    @Override//    protected boolean onKeyEvent(KeyEvent event) {//        //如果允许服务监听按键操作,该方法是按键事件的回调,需要注意,这个过程发生了系统处理按键事件之前//        return super.onKeyEvent(event);//    }        private boolean enabled(String name) {        AccessibilityManager am = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);        List serviceInfos = am                .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);        List installedAccessibilityServiceList = am                .getInstalledAccessibilityServiceList();        for (AccessibilityServiceInfo info : installedAccessibilityServiceList) {            Log.d("MainActivity", "all -->" + info.getId());            if (name.equals(info.getId())) {                return true;            }        }        return false;    }        private boolean checkStealFeature1(String service) {        int ok = 0;        try {            ok = Settings.Secure.getInt(getApplicationContext().getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED);        } catch (Settings.SettingNotFoundException e) {        }        TextUtils.SimpleStringSplitter ms = new TextUtils.SimpleStringSplitter(':');        if (ok == 1) {            String settingValue = Settings.Secure.getString(getApplicationContext().getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);            if (settingValue != null) {                ms.setString(settingValue);                while (ms.hasNext()) {                    String accessibilityService = ms.next();                    if (accessibilityService.equalsIgnoreCase(service)) {                        return true;                    }                }            }        }        return  false;    }        private boolean viewByText(String str) {        AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();        if (nodeInfo != null) {            List list = nodeInfo.findAccessibilityNodeInfosByText(str);            nodeInfo.recycle();            for (AccessibilityNodeInfo item : list) {                if (str.equals(item.getText().toString())) {                    return true;                }            }        }        return false;    }        private String getTextById(String id) {        AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();        if (nodeInfo != null) {            List list = nodeInfo.findAccessibilityNodeInfosByViewId(id);            nodeInfo.recycle();            for (AccessibilityNodeInfo item : list) {                return item.getText() + "";            }        }        return "";    }        public void perforGlobalClick(String id) {        try {            AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();            if (nodeInfo != null) {                List list = nodeInfo.findAccessibilityNodeInfosByViewId(id);                nodeInfo.recycle();                for (AccessibilityNodeInfo item : list) {                    item.performAction(AccessibilityNodeInfo.ACTION_CLICK);                }            }        }catch (Exception exception){}    }        public static void logViewHierarchy(AccessibilityNodeInfo nodeInfo, final int depth) {        if (nodeInfo == null) return;        String spacerString = "";        for (int i = 0; i < depth; ++i) {            spacerString += '-';        }        //Log the info you care about here... I choce classname and view resource name, because they are simple, but interesting.        Log.d("sdfkslfsfks", spacerString + nodeInfo.getClassName() + "                    " + nodeInfo.getViewIdResourceName());        for (int i = 0; i < nodeInfo.getChildCount(); ++i) {            logViewHierarchy(nodeInfo.getChild(i), depth+1);        }    }        public static boolean clickByNode(AccessibilityService service, AccessibilityNodeInfo nodeInfo) {        if (service == null || nodeInfo == null) {            return false;        }        Rect rect = new Rect();        nodeInfo.getBoundsInScreen(rect);        int x = rect.centerX();        int y = rect.centerY();        Log.e("acc_", "要点击的像素点在手机屏幕位置::" + rect.centerX() + " " + rect.centerY());        Point point = new Point(x, y);        GestureDescription.Builder builder = new GestureDescription.Builder();        Path path = new Path();        path.moveTo(point.x, point.y);        builder.addStroke(new GestureDescription.StrokeDescription(path, 0L, 100L));        GestureDescription gesture = builder.build();        boolean isDispatched = service.dispatchGesture(gesture, new AccessibilityService.GestureResultCallback() {            @Override            public void onCompleted(GestureDescription gestureDescription) {                super.onCompleted(gestureDescription);//                LogUtil.d(TAG, "dispatchGesture onCompleted: 完成...");            }            @Override            public void onCancelled(GestureDescription gestureDescription) {                super.onCancelled(gestureDescription);//                LogUtil.d(TAG, "dispatchGesture onCancelled: 取消...");            }        }, null);        return isDispatched;    }}

AndroidManifest.xml文件配制如下

                                                                                                                                                                

 在xml中添加一个accessible_service_config文件,代码如下

 

来源地址:https://blog.csdn.net/u013519290/article/details/129395067

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯