如何完美实现微信自动发朋友圈&自动添加好友&等等

wechat

背景

要实现微信自动化,大致有这么几种办法

  1. 基于Web微信API的框架 如 # wechaty 这里以这个举例是因为他有个付费版,可以自动发朋友圈,可以分享卡片,可以做群管理,很是方便快捷 ,但是太贵了,200块大洋一个月,你用了之后预计就甩不掉了。
  2. 基于xposed 插件开发 ,通过广播方式和sdk 交互,只要发送广播就可以自动发送朋友圈,但是实现难度相对较高,需要逆向知识,还有被封号的风险,不可取。
  3. 基于AccessibilityService实现,有一个自动抢红包的功能就是通过他实现的,原本该功能是对那些由于视力、听力或其它身体原因导致不能方便使用Android智能手机的用户,Android提供了Accessibility功能和服务帮助这些用户更加简单地操作设备,包括文字转语音(不支持中文)、触觉反馈、手势操作、轨迹球和手柄操作等,到Android 4.1版本以后,系统辅助服务增加了与窗口元素的双向交互,此时可以通过辅助功能服务操作窗口元素,比如点击按钮、输入文本信息等功能,越来越方便。

这些方法各有利弊,综合看来,第一种实现总会有一天微信说要关闭web服务,那岂不是很惨,第二种风险在于被封号,只有第三种目前看来最合适,想要自动发送朋友圈,AccessibilityService是最完美的实现。知其然知其所以然,我们要想用好AccessibilityService,就要明白其原理,这样能更好的理解每一步操作的含义,少走弯路,避免考虑不周导致成功率不足。

自动化视频效果展示:

在做的过程中,也遇到很多问题,例如经常拿不到
AccessibilityNodeInfo实例,如果拿不到就无法操作当前界面的元素,等于是无法再执行下去了,这里有几个关键点需要注意的,只要注意这几个就可以完美拿到。卖个关子,下面会提到,请往下看。

  • 自动发送朋友圈视频
  • 自动添加好友视频
  • 自动分享小程序给好友(doing)
  • 自动拉好友进群(doing)
  • 自动踢人(doing)

AccessibilityService原理

先大致了解下原理,对你的使用更是事半功倍。
类图源于 here

AccessibilityService类图

分析下这个类图。

  • AccessibilityService:最主要的onBind()、onAccessibilityEvent(event: AccessibilityEvent)、onInterrupt()三个函数,后面两个需要子类实现,onBind已经实现了,看下这个函数源码 直接return了一个静态内部类IAccessibilityServiceClientWrapper
public final IBinder onBind(Intent intent) { return 
new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
        }
}
  • IAccessibilityServiceClientWrapper : 用于和system_server通信的匿名Binder服务,
public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
            implements HandlerCaller.Callback {

该类又继承了IAccessibilityServiceClient.Stub,并实现了HandlerCaller.Callback接口。看到这分析出,这是一个跨进程通信Service,在IAccessibilityServiceClientWrapper构造函数中看到Callbacks 回调接口

   public interface Callbacks {
        void onAccessibilityEvent(AccessibilityEvent event);
        void onInterrupt();
        void onServiceConnected();
        void init(int connectionId, IBinder windowToken);
        boolean onGesture(int gestureId);
        boolean onKeyEvent(KeyEvent event);
        void onMagnificationChanged(@NonNull Region region,
                float scale, float centerX, float centerY);
        void onSoftKeyboardShowModeChanged(int showMode);
        void onPerformGestureResult(int sequence, boolean completedSuccessfully);
        void onFingerprintCapturingGesturesChanged(boolean active);
        void onFingerprintGesture(int gesture);
        void onAccessibilityButtonClicked();
        void onAccessibilityButtonAvailabilityChanged(boolean available);
    }

看到这里再回头看看onBind函数的具体实现如图


看到了吧,AccessibilityService的AccessibilityEvent事件
来源于IAccessibilityServiceClientWrapper,我们再看看IAccessibilityServiceClientWrapper是如何收到这个Event,再往下跟踪代码


IAccessibilityServiceClientWrapper中executeMessage(Message message)函数调用mCallback.onAccessibilityEvent(event)传递给AccessibilityService,executeMessage函数是HandlerCaller.Callback接口的实现,那谁发送的这个Message呢,
IAccessibilityServiceClientWrapper中同样的onAccessibilityEvent()函数如图


而这个函数又是谁调的呢,这里就到了进程间通信的逻辑,看一下外部逻辑,上面是倒推逻辑,下面正推一下。

AccessibilityService跟一个监控一样,界面的所有的事件都可以收到,那它的源头肯定在View上,肯定在View的事件处理上,跟着这个逻辑去找一下



在performClick函数中发现有一个AccessibilityEvent事件传递,再往里面跟踪发现了这个有用的信息


这里面可以清晰的看到,你收到的AccessibilityEvent事件所有的字段赋值逻辑就在这里。那它是如何发出去交给AccessibilityService呢,肯定是通过AIDL,进一步查找源码


发现在sendAccessibilityEventUncheckedInternal函数中,调用了
getParent().requestSendAccessibilityEvent(this, event),接着看看这个getParent()干了什么,找了一圈找到具体实现在哪,最终在ViewRootImpl中找到这个方法实现

   @Override
    public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
        if (mView == null || mStopped || mPausedForTransition) {
            return false;
        }

        // Immediately flush pending content changed event (if any) to preserve event order
        if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
                && mSendWindowContentChangedAccessibilityEvent != null
                && mSendWindowContentChangedAccessibilityEvent.mSource != null) {
            mSendWindowContentChangedAccessibilityEvent.removeCallbacksAndRun();
        }

        // Intercept accessibility focus events fired by virtual nodes to keep
        // track of accessibility focus position in such nodes.
        final int eventType = event.getEventType();
        switch (eventType) {
            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
                final long sourceNodeId = event.getSourceNodeId();
                final int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
                        sourceNodeId);
                View source = mView.findViewByAccessibilityId(accessibilityViewId);
                if (source != null) {
                    AccessibilityNodeProvider provider = source.getAccessibilityNodeProvider();
                    if (provider != null) {
                        final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
                                sourceNodeId);
                        final AccessibilityNodeInfo node;
                        node = provider.createAccessibilityNodeInfo(virtualNodeId);
                        setAccessibilityFocus(source, node);
                    }
                }
            } break;
            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
                final long sourceNodeId = event.getSourceNodeId();
                final int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
                        sourceNodeId);
                View source = mView.findViewByAccessibilityId(accessibilityViewId);
                if (source != null) {
                    AccessibilityNodeProvider provider = source.getAccessibilityNodeProvider();
                    if (provider != null) {
                        setAccessibilityFocus(null, null);
                    }
                }
            } break;


            case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
                handleWindowContentChangedEvent(event);
            } break;
        }
        mAccessibilityManager.sendAccessibilityEvent(event);
        return true;
    }

可以看到是mAccessibilityManager.sendAccessibilityEvent 发出了事件,再看下这个函数的实现逻辑

 public void sendAccessibilityEvent(AccessibilityEvent event) {
        final IAccessibilityManager service;
        final int userId;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
            if (!mIsEnabled) {
                Looper myLooper = Looper.myLooper();
                if (myLooper == Looper.getMainLooper()) {
                    throw new IllegalStateException(
                            "Accessibility off. Did you forget to check that?");
                } else {
                    // If we're not running on the thread with the main looper, it's possible for
                    // the state of accessibility to change between checking isEnabled and
                    // calling this method. So just log the error rather than throwing the
                    // exception.
                    Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
                    return;
                }
            }
            if ((event.getEventType() & mRelevantEventTypes) == 0) {
                if (DEBUG) {
                    Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event
                            + " that is not among "
                            + AccessibilityEvent.eventTypeToString(mRelevantEventTypes));
                }
                return;
            }
            userId = mUserId;
        }
        try {
            event.setEventTime(SystemClock.uptimeMillis());
            // it is possible that this manager is in the same process as the service but
            // client using it is called through Binder from another process. Example: MMS
            // app adds a SMS notification and the NotificationManagerService calls this method
            long identityToken = Binder.clearCallingIdentity();
            service.sendAccessibilityEvent(event, userId);
            Binder.restoreCallingIdentity(identityToken);
            if (DEBUG) {
                Log.i(LOG_TAG, event + " sent");
            }
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error during sending " + event + " ", re);
        } finally {
            event.recycle();
        }
    }

IAccessibilityManager 是个aidl接口,最终通过他发送给了服务


看到这是不是明白了其中的原理。在AccessibilityService你还可以拿到Activity的一些信息,同样的道理,你在源码中肯定能找到那个实现,你可以试着自己去搜一下。

AccessibilityService使用心得

上面卖的关子,现在可以圆满了,在使用中遇到过很多种情况拿不到RootInActiveWindow 也就是AccessibilityNodeInfo(表示窗口内容的节点),当窗口能拿到这个节点时,你才能通过他去findView,所以你知道它的重要性了,但为什么很多时候拿不到呢

  • 第一个case,如图

如果在TYPE_WINDOWS_CHANGED中就会拿不到

  • 第二个case,如图

当你不在当前页面(com.tencent.mm.ui.LauncherUI是微信的主页)时同样也有可能获取不到,有可能是在其他页面。

  • 第三个case,没图,这种情况就很奇怪,在上面两个都避免了之后,还有拿不到的情况,怎么办了,偶然间我切回桌面,又回来,发现又有了,具体什么原理,目前没找到答案,好吧,总算有个解决办法,当拿不到这个界面的节点时,我切到任务管理状态,再点物理返回键,达到切换的效果,这时候还真的又拿到了,也许这是目前最有价值的一个case,希望你也能用它解决问题。

总结

读到这是不是想看如何实现呢,这里直接提供项目源码,并没有写如何使用的教程,推荐给你们几个写的特别详细的教程,希望对你有帮助,使用起来相对很简单的,看完这些文章基本够用
Android自动化模拟操作开源库源码解析
你真的理解AccessibilityService吗
AccessibilityService分析与防御
A complete guide to Accessibility Service Part 1 — Android
A Complete Guide to Accessibility Service Part 2 — Android

本项目源码完善中,功能上会加入

  • 自动分享小程序给好友
  • 自动拉好友进群
  • 自动踢人
    代码会提交到这里 I校长
    欢迎Star

你可能感兴趣的:(如何完美实现微信自动发朋友圈&自动添加好友&等等)