Android基于AccessibilityService制作的钉钉自动签到程序代码

前两天公司开始宣布要使用阿里钉钉来签到啦!!!~~这就意味着,我必须老老实实每天按时签到上班下班了,这真是一个悲伤的消息,可是!!!!那么机智(lan)的我,怎么可能就这么屈服!!!阿里钉钉签到,说到底不就是手机软件签到吗?我就是干移动开发的,做一个小应用每天自动签到不就行了:)

说干就干,首先分析一下,阿里钉钉的签到流程: 打开阿里钉钉- 广告页停留2S左右- 进入主页- 点击“工作”tab- 点击“签到”模块- 进入签到页面(可能会再次出现广告和对话框)- 点击签到

我们操作手机的过程就是这样,要实现这些点击,很自然想起了前段时间做的微信抢红包小应用,利用AccessibilityService服务帮助我们实现这些自动化操作。

以上是分析过程,接下来是我对这个小功能实现的具体方案思路:

将测试手机放公司并且安装这个应用,通过我远程的电话拨打或者短信发送到测试手机(只要能产生广播或者信息的就行),测试手机接受到广播信息,唤醒钉钉,进入钉钉页面,AccessibilityService开始工作,进行一系列点击签到操作,结束操作后退出钉钉,签到完成。

通过以上过程的分析我们大概要用到的知识有以下几块:

\1. 唤醒非自己的其他第三方应用

\2. 广播

\3. AccessibilityService服务

以下是对这三部分代码实现:

唤醒第三方应用

代码语言:javascript

package net.fenzz.dingplug;
import java.util.List;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
public class Utils {
 public static void openCLD(String packageName,Context context) { 
  PackageManager packageManager = context.getPackageManager(); 
  PackageInfo pi = null; 
   try { 
    pi = packageManager.getPackageInfo("com.alibaba.android.rimet", 0); 
   } catch (NameNotFoundException e) { 
   } 
   Intent resolveIntent = new Intent(Intent.ACTION_MAIN, null); 
   resolveIntent.addCategory(Intent.CATEGORY_LAUNCHER); 
   resolveIntent.setPackage(pi.packageName); 
   List<ResolveInfo  apps = packageManager.queryIntentActivities(resolveIntent, 0); 
   ResolveInfo ri = apps.iterator().next(); 
   if (ri != null ) { 
    String className = ri.activityInfo.name; 
    Intent intent = new Intent(Intent.ACTION_MAIN); 
    intent.addCategory(Intent.CATEGORY_LAUNCHER); 
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    ComponentName cn = new ComponentName(packageName, className); 
    intent.setComponent(cn); 
    context.startActivity(intent); 
   } 
 } 
}

接受电话广播并且唤醒钉钉:

mainifest先注册监听器

代码语言:javascript

 <!-- 注册监听手机状态 --  
  <receiver android:name=".PhoneReceiver"  
   <intent-filter android:priority="1000"   
    <action android:name="android.intent.action.PHONE_STATE" /  
   </intent-filter  
  </receiver  

相关权限

代码语言:javascript

<!-- 读取手机状态的权限 --  
 <uses-permission android:name="android.permission.READ_PHONE_STATE" /  
  <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/ 

代码

代码语言:javascript

package net.fenzz.dingplug;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;
import android.app.Service;
import android.util.Log;
public class PhoneReceiver extends BroadcastReceiver {
 private static final String TAG = "message";
 private static boolean mIncomingFlag = false;
 private static String mIncomingNumber = null;
 @Override
 public void onReceive(Context context, Intent intent) {
  // 如果是拨打电话
  if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
   mIncomingFlag = false;
   String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
   Log.i(TAG, "call OUT:" + phoneNumber);
  } else {
   // 如果是来电
   TelephonyManager tManager = (TelephonyManager) context
     .getSystemService(Service.TELEPHONY_SERVICE);
   switch (tManager.getCallState()) {
   case TelephonyManager.CALL_STATE_RINGING:
    mIncomingNumber = intent.getStringExtra("incoming_number");
    Log.i(TAG, "RINGING :" + mIncomingNumber);
    if(mIncomingNumber!=null&&mIncomingNumber.equals(你的手机号)){
     Utils.openCLD("com.alibaba.android.rimet", context);
     DingService.instance.setServiceEnable();
    }
    break;
   case TelephonyManager.CALL_STATE_OFFHOOK:
    if (mIncomingFlag) {
     Log.i(TAG, "incoming ACCEPT :" + mIncomingNumber);
    }
    break;
   case TelephonyManager.CALL_STATE_IDLE:
    if (mIncomingFlag) {
     Log.i(TAG, "incoming IDLE");
    }
    break;
   }
  }
 }
}

AccessibilityService服务实现:

相关权限及注册:

代码语言:javascript

<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" / 
<service
   android:name=".DingService"
   android:enabled="true"
   android:exported="true"
   android:label="@string/app_name"
   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/red_service_config" / 
 </service 

需要在res文件夹下新建一个xml文件夹里面放入一个这样的xml配置文件:

代码语言:javascript

<?xml version="1.0" encoding="utf-8"? 
<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/accessibility_description"
 android:notificationTimeout="100"
 android:packageNames="com.alibaba.android.rimet" 
 / 

代码:

代码语言:javascript

package net.fenzz.dingplug;
import java.util.ArrayList;
import java.util.List;
import android.accessibilityservice.AccessibilityService;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
public class DingService extends AccessibilityService {
private String TAG = getClass().getSimpleName();
private boolean isFinish = false;
public static DingService instance;
private int index = 1;
/**
* 获取到短信通知
* 0.唤醒屏幕
* 1.打开钉钉
* 2.确保当前页是主页界面
* 3.找到“工作”tab并且点击
* 4.确保到达签到页面
* 5.找到签到按钮,并且点击
* 6.判断签到是否成功
*  1.成功,退出程序
*  2.失败,返回到主页,重新从1开始签到
*/
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
// TODO Auto-generated method stub
//  final int eventType = event.getEventType();
ArrayList<String  texts = new ArrayList<String ();
Log.i(TAG, "事件---- " + event.getEventType());
if(isFinish){
return; 
}
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if(nodeInfo == null) {
Log.w(TAG, "rootWindow为空");
return ;
}
//  nodeInfo.
//  System.out.println("nodeInfo"+nodeInfo);
System.out.println("index:"+index);
switch (index) {
case 1: //进入主页
OpenHome(event.getEventType(),nodeInfo);
break;
case 2: //进入签到页
OpenQianDao(event.getEventType(),nodeInfo);
break;
case 3:
doQianDao(event.getEventType(),nodeInfo);
break;
default:
break;
}
}
private ArrayList<String  getTextList(AccessibilityNodeInfo node,ArrayList<String  textList){
if(node == null) {
Log.w(TAG, "rootWindow为空");
return null;
}
if(textList==null){
textList = new ArrayList<String ();
}
String text = node.getText().toString();
if(text!=null&&text.equals("")){
textList.add(text);
}
//  node.get
return null;
}
private void OpenHome(int type,AccessibilityNodeInfo nodeInfo) {
if(type == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED){
//判断当前是否是钉钉主页
List<AccessibilityNodeInfo  homeList = nodeInfo.findAccessibilityNodeInfosByText("工作");
if(!homeList.isEmpty()){
//点击
boolean isHome = click( "工作");
System.out.println("---- "+isHome);
index = 2;
System.out.println("点击进入主页签到");
}
}
}
private void OpenQianDao(int type,AccessibilityNodeInfo nodeInfo) {
if(type == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED){
//判断当前是否是主页的签到页
List<AccessibilityNodeInfo  qianList = nodeInfo.findAccessibilityNodeInfosByText("工作");
if(!qianList.isEmpty()){
boolean ret = click( "签到");
index = 3;
System.out.println("点击进入签到页面详情");
}
//   index = ret?3:1; 
}
}
private void doQianDao(int type,AccessibilityNodeInfo nodeInfo) {
if(type == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED){
//判断当前页是否是签到页
List<AccessibilityNodeInfo  case1 = nodeInfo.findAccessibilityNodeInfosByText("开启我的签到之旅");
if(!case1.isEmpty()){
click("开启我的签到之旅");
System.out.println("点击签到之旅");
}
List<AccessibilityNodeInfo  case2 = nodeInfo.findAccessibilityNodeInfosByText("我知道了");
if(!case2.isEmpty()){
click("我知道了");
System.out.println("点击我知道对话框");
}
List<AccessibilityNodeInfo  case3 = nodeInfo.findAccessibilityNodeInfosByText("签到");
if(!case3.isEmpty()){
Toast.makeText(getApplicationContext(), "发现目标啦!!~~", 1).show();
System.out.println("发现目标啦!");
click("签到");
isFinish = true;
}
}
//  if(type == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED){
//   List<AccessibilityNodeInfo  case3 = nodeInfo.findAccessibilityNodeInfosByText("签到");
//   if(!case3.isEmpty()){
//    Toast.makeText(getApplicationContext(), "发现目标啦!!~~", 1).show();
//   }
//  }
}
//通过文字点击
private boolean click(String viewText){
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if(nodeInfo == null) {
Log.w(TAG, "点击失败,rootWindow为空");
return false;
}
List<AccessibilityNodeInfo  list = nodeInfo.findAccessibilityNodeInfosByText(viewText);
if(list.isEmpty()){
//没有该文字的控件
Log.w(TAG, "点击失败,"+viewText+"控件列表为空");
return false;
}else{
//有该控件
//找到可点击的父控件
AccessibilityNodeInfo view = list.get(0);
return onclick(view); //遍历点击
}
}
private boolean onclick(AccessibilityNodeInfo view){
if(view.isClickable()){
view.performAction(AccessibilityNodeInfo.ACTION_CLICK);
Log.w(TAG, "点击成功");
return true;
}else{
AccessibilityNodeInfo parent = view.getParent();
if(parent==null){
return false;
}
onclick(parent);
}
return false;
}
//点击返回按钮事件
private void back(){
performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
}
@Override
public void onInterrupt() {
// TODO Auto-generated method stub
}
@Override
protected void onServiceConnected() {
// TODO Auto-generated method stub
super.onServiceConnected();
Log.i(TAG, "service connected!");
Toast.makeText(getApplicationContext(), "连接成功!", 1).show();
instance = this;
}
public void setServiceEnable(){
isFinish = false;
Toast.makeText(getApplicationContext(), "服务可用开启!", 1).show();
index = 1;
}
}

以上基本是所有代码,这个小程序中可以不用Activity组件,也可以加一个小的Activity,用来作为系统的总开关,当然也可以自动检测时间,来判断是否开启服务,这样就不用Activity了,在这个小例子中,我使用了一个小activity,就放了一个button。

项目源码

以上就是本文的全部内容,希望对大家的学习有所帮助。

更多Android进阶指南 可以扫码 解锁 《Android十大板块文档》

1.Android车载应用开发系统学习指南(附项目实战)

2.Android Framework学习指南,助力成为系统级开发高手

3.2024最新Android中高级面试题汇总+解析,告别零offer

4.企业级Android音视频开发学习路线+项目实战(附源码)

5.Android Jetpack从入门到精通,构建高质量UI界面

6.Flutter技术解析与实战,跨平台首要之选

7.Kotlin从入门到实战,全方面提升架构基础

8.高级Android插件化与组件化(含实战教程和源码)

9.Android 性能优化实战+360°全方面性能调优

10.Android零基础入门到精通,高手进阶之路

敲代码不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔

  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值