前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >为了保护小姐姐的眼睛,我用自动化做了一款语音机器人

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

作者头像
AirPython
发布2020-05-26 11:12:22
7210
发布2020-05-26 11:12:22
举报
文章被收录于专栏:Python 自动化Python 自动化

1. 场景

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

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

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

2. 实现步骤

第 1 步,新建 Android 项目

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

代码语言:javascript
复制
//新建一个服务
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 为增强

代码语言:javascript
复制
//新建一个服务
@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 不为空的内容

代码语言:javascript
复制
 /***
  * 获取所有的文本内容
  * @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));
            }
        }
    }
}

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

代码语言:javascript
复制
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 步,添加悬浮框

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

代码语言:javascript
复制
# 悬浮框依赖
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, -2, 200).show();
    }

    if (!EasyFloat.isShow(this, "readmsg"))
    {
        EasyFloat.showAppFloat("readmsg");
    }
}

第 5 步,过滤页面

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

代码语言:javascript
复制
# 事件总线依赖
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 对象

代码语言:javascript
复制
//初始化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, null, null);
    } else
    {
        tts.speak(content, TextToSpeech.QUEUE_ADD, null);
    }
}

第 7 步,播放内容

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

代码语言:javascript
复制
//播放或者停止播放
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 步操作,当打开任意一篇微信公众号文章,悬浮框会自动显示,带上耳机,点击播放按钮,文章内容就能自动读出来了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 AirPython 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2. 实现步骤
  • 3. 最后
相关产品与服务
事件总线
腾讯云事件总线(EventBridge)是一款安全,稳定,高效的云上事件连接器,作为流数据和事件的自动收集、处理、分发管道,通过可视化的配置,实现事件源(例如:Kafka,审计,数据库等)和目标对象(例如:CLS,SCF等)的快速连接,当前 EventBridge 已接入 100+ 云上服务,助力分布式事件驱动架构的快速构建。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档