2025年3月31日 星期一 乙巳(蛇)年 正月初一 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > Python

为了保护小姐姐的眼睛,我用自动化做了一款语音机器人

时间:10-12来源:作者:点击数:31
CDSY,CDSY.XYZ

1. 场景

最近一位小姐姐在微信上向我抱怨,说自己每天坐地铁上下班,路上会阅读一些好的文章来提升自己。

但上了一天的班,实在太累了;如果戴上耳机的同时,文章能自动阅读起来,就好了!

本篇文章将带大家用自动化技术,来实现这一功能。

2. 实现步骤

第1步,新建 Android 项目

使用 Android Studio 新建一个项目,并创建一个无障碍服务,设置只处理微信应用内的页面事件

  • //新建一个服务
  • public class MsgService extends AccessibilityService
  • {
  •   @Override
  •   public void onAccessibilityEvent(AccessibilityEvent event)
  •   {
  •         
  •   }
  • }
  • //通过packageNames指定只处理微信App页面事件
  • <?xml version="1.0" encoding="utf-8"?>
  • <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
  •     android:accessibilityEventTypes="typeWindowStateChanged"
  •     android:accessibilityFeedbackType="feedbackGeneric"
  •     android:accessibilityFlags="flagDefault"
  •     android:canRetrieveWindowContent="true"
  •     android:description="@string/desc"
  •     android:notificationTimeout="100"
  •     android:packageNames="com.tencent.mm" />

第2步,安装文字转语言引擎

由于系统内置的 Pico TTS 不支持中文,为了更好地将文字转为语音,这里先下载安装Google 文字转语音这款App,然后将首选引擎切换到 Google文字转语言引擎

图片

第3步,获取公众号文章内容

使用 Android SDK 自带的uiautomatorviewer打开某一篇公众号文章的页面元素树

图片

通过分析,发现一篇文章的正文内容都包含在控件中text属性中,因此,我们只需要遍历出所有的控件,找出所有 text 属性不为空的内容。

需要注意的是,由于微信基于腾讯 X5 内核,内容包裹在WebView 内部,直接获取控件是获取不到的,因此,需要在服务初始化的时候配置 flags 为增强

  • //新建一个服务
  • @Override
  • protected void onServiceConnected()
  • {
  •     super.onServiceConnected();
  •     AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo();
  •     serviceInfo.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
  •     serviceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
  •     serviceInfo.packageNames = new String[]{"com.tencent.mm"};
  •     serviceInfo.notificationTimeout = 100;
  •     //保证能够获取到WebView内部的控件元素
  •     serviceInfo.flags = serviceInfo.flags | AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY;
  •     setServiceInfo(serviceInfo);
  •     Toast.makeText(MsgService.this, "连接服务成功",
  •                 Toast.LENGTH_SHORT).show();
  • }

接着,先找到 WebView 控件,然后遍历子元素,找出所有子元素 text 不为空的内容

  •  /***
  •   * 获取所有的文本内容
  •   * @param webNode
  •   * @return
  •   */
  • private void getAllContents(AccessibilityNodeInfo webNode)
  • {
  •     for (int i = 0; i < webNode.getChildCount(); i++)
  •     {
  •         AccessibilityNodeInfo tempNode = webNode.getChild(i);
  •         String id = tempNode.getViewIdResourceName();
  •         //过滤
  •         if (TextUtils.equals("meta_content", id))
  •         {
  •             continue;
  •         }
  •         String tempContent = tempNode.getText().toString().trim();
  •         //加入内容
  •         if (!TextUtils.isEmpty(tempContent))
  •         {
  •             contents.add(tempContent);
  •         }
  •         //循环遍历
  •         //判断是否有子节点
  •         if (tempNode.getChildCount() > 0)
  •         {
  •             for (int j = 0; j < tempNode.getChildCount(); j++)
  •             {
  •                 getAllContents(tempNode.getChild(j));
  •             }
  •         }
  •     }
  • }

最后,将文章内容分段存储到配置文件中

  • StringBuilder sb = new StringBuilder();
  • for (int i = 0; i < contents.size(); i++)
  • {
  •     sb.append(contents.get(i)).append(";;;");
  •     Log.d("xag", contents.get(i));
  • }
  • Log.d("xag""*******************获取完成*********************");
  • //存储
  • SpUtil.clear(BaseApplication.getInstance());
  • SpUtil.put("contents", sb.toString());

第4步,添加悬浮框

图片

为了更加方便地管理语音播放功能,新建一个系统悬浮窗,并设置按钮的点击事件,即:点击关闭按钮可以关闭悬浮框;点击复选框,可以切换到播放、暂停状态

  • # 悬浮框依赖
  • implementation 'com.github.princekin-f:EasyFloat:1.3.2'
  • //显示悬浮框
  • private void initFloatDialog()
  • {
  •     View currentFLoat = EasyFloat.getAppFloatView("readmsg");
  •     if (null == currentFLoat)
  •     {
  •         //初始化悬浮框View,并新增回调事件
  •         EasyFloat.with(this).setLayout(R.layout.float_test, new OnInvokeView()
  •         {
  •             @Override
  •             public void invoke(View view)
  •             {
  •                 ImageView close_iv = view.findViewById(R.id.ivClose);
  •                 final CheckBox float_cb = view.findViewById(R.id.float_cb);
  •                 close_iv.setOnClickListener(new View.OnClickListener()
  •                 {
  •                     @Override
  •                     public void onClick(View v)
  •                     {
  •                         EasyFloat.dismissAppFloat("readmsg");
  •                     }
  •                 });
  •                 //播放、停止切换功能    
  •                 float_cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
  •                 {
  •                     @Override
  •                     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
  •                     {
  •                         
  •                     }
  •                 });
  •             }
  •         }).setShowPattern(ShowPattern.ALL_TIME)
  •         .setTag("readmsg")
  •         .setAnimator(new DefaultAnimator())
  •         .setGravity(Gravity.END | Gravity.CENTER_VERTICAL, -2200).show();
  •     }
  •     if (!EasyFloat.isShow(this"readmsg"))
  •     {
  •         EasyFloat.showAppFloat("readmsg");
  •     }
  • }

第5步,过滤页面

为了提升用户体验,可以对页面进行过滤,保证只有在文章页面的时候,才显示系统悬浮框

  • # 事件总线依赖
  • implementation 'org.simple:androideventbus:1.0.5.1'
  • //如果是微信公众号文章页面
  • if (TextUtils.equals(currentClassName, CLASS_NAME_PAGE_ARTICLE))
  • {
  •     //等待页面加载
  •     try
  •     {
  •         Thread.sleep(5000);
  •     } catch (InterruptedException e)
  •     {
  •         e.printStackTrace();
  •     }
  •     //发送显示悬浮框的事件
  •     EventBus.getDefault().post(new ShowFloatBean(true));
  • }
  • //订阅事件,显示或隐藏悬浮框
  • @Subscriber
  • private void changeFloatStatus(ShowFloatBean showFloatBean)
  • {
  •     Log.d("xag""接受到事件,展示或者隐藏:" + showFloatBean.isShow());
  •     boolean showFloat = showFloatBean.isShow();
  •     if (showFloat)
  •     {
  •         initFloatDialog();
  •     } else
  •     {
  •         EasyFloat.dismissAppFloat("readmsg");
  •     }
  • }

第6步,实例化 TTS 对象

在 Application 中为TTS指定语言,并实例化语音播放 TTS 对象

  • //初始化TTS
  • private void initTTS()
  • {
  •     //初始化tts监听对象
  •     tts = new TextToSpeech(this, onInitListener);
  •     //语音音调调节
  •     tts.setPitch(1.0f);
  •     
  •     //语音音速
  •     tts.setSpeechRate(0.8f);
  • }
  • /***
  •  * 播放方法的封装
  •  */
  • public void speakContent(String content)
  • {
  •     if (null == tts)
  •     {
  •         initTTS();
  •     }
  •     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
  •     {
  •         tts.speak(content, TextToSpeech.QUEUE_ADD, nullnull);
  •     } else
  •     {
  •         tts.speak(content, TextToSpeech.QUEUE_ADD, null);
  •     }
  • }

第7步,播放内容

点击播放按钮,就可以将当前页面的内容分段读出来

  • //播放或者停止播放
  • if (isChecked)
  • {
  •     String content = SpUtil.get("contents""");
  •     String[] contents = content.split(";;;");
  •     
  •     //注意太长没法直接播放
  •     for (String item : contents)
  •     {
  •         BaseApplication.getInstance().speakContent(item);
  •     }
  • else
  • {
  •     BaseApplication.getInstance().stopSpeak();
  • }

需要注意的是,如果文本太长,没法播放出来,这里是分段的内容从存储文件中取出来,然后分段读出来

3. 最后

经过上面 7步操作,当打开任意一篇微信公众号文章,悬浮框会自动显示,带上耳机,点击播放按钮,文章内容就能自动读出来了。

已经将全部源码上传:https://github.com/xingag/app_spider/tree/master/%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7%E6%96%87%E7%AB%A0%E8%AF%AD%E9%9F%B3%E6%9C%BA%E5%99%A8%E4%BA%BA。

CDSY,CDSY.XYZ
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐