一、项目介绍
本项目使用android来开发一个智能聊天机器人,该智能聊天机器人主要是供用户娱乐,他可以供用户娱乐休闲,他可以与用户讲故事、说笑话、说笑话、跟用户聊天,非常有趣。

涉及到知识点:

网络编程【okhttp】
json数据解析
Handler处理
在实现智能机器人聊天功能,在实现这个功能的过程中申请了一个图灵机器人的key,根据该key并通过异步访问网络获取机器人回复的信息,接着调用Handler将获取的信息发送到主线程,并通过JSON解析将获取的聊天数据解析成字符串显示到界面上。

二、项目结果
在这里插入图片描述

三、项目开发
1、需求开发
聊天机器人的主要功能就是和用户进行智能对话,如此智能的效果,涉及对用户语义理解,以及海量信息的精准搜索,我们没有办法做到,选择调用的是第三方公司提供的开发API。

图灵机器人:http://www.tuling123.com/member/robot/index.jhtm

模型:

2、开发环境介绍
开发工具:JDK8
API版本:Android API27
3、功能实现【聊天功能】
从需求出发进行分析,显然要实现聊天功能,我们需要一个界面:

界面上需要显示机器人及用户的头像,还需要一个编辑框和发送按钮以及显示聊天信息的界面。聊天界面使用了ListView控件。

用户输入聊天信息,信息需要保存且显示在聊天信息中,同时向图灵机器人发送聊天信息,图灵机器人做出响应,得到响应的聊天信息,保存并显示在聊天信息中。

3.1、申请机器人身份标识
课本255

3.2、搭建聊天界面布局
课本256-257

3.3、搭建聊天条目布局
1.创建聊天界面Item

2.放置姐买你控件

3.修改styles.xml

3.4封装聊天信息实体类
由于机器人与用户聊天的每天信息都会有消息的状态,消息的内容等属性,因此需要创建一个CHatBean类来存放消息的这些属性。

3.5、编写聊天列表适配器
由于聊天界面用了ListView控件显示聊天信息,因此需要一个数据适配器ChatAdapter对ListView控件进行数据适配。

1、创建ChatAdapter类

2、创建ViewHolder类

3.6、实现智能机器人通信
聊天界面主要用于展示机器人与用户的头像和聊天内容,当第一次进入智能机器人聊天应用时,首先程序会从string.xml文件中获取机器人需要发出的欢迎信息并显示再界面上,用户接受到欢迎信息后,会与机器人进行一些互动,发送一些信息,程序会将这些信息封装到一个ChatBean对象中并显示到界面上,同时会根据用户发送的聊天内容来从图灵机器人服务器上获取机器人的回复信息,并将获取的机器人回复信息通过Json解析显示到界面上。

在项目的RobotActivity中实现聊天界面的逻辑代码,具体步骤如下:

1,添加okhttp库

2,设置机器人欢迎信息

注意问题:

由于需要访问网络,需要对Android Studio模拟器联网进行设置
1.开启root权限
2.更改dns

由于图灵机器人v2版本不能使用get方式,所以需要按照图灵机器人接口说明中所写的,使用post方法
官方文档:https://www.kancloud.cn/turing/www-tuling123-com/718227

请求信息格式:

{
 “reqType”:0,
 “perception”: {
 “inputText”: {
 “text”: “讲个笑话”
 }
 },
 “userInfo”: {
 “apiKey”: “4fce0bc257c04a9f88f0a55ee179794d”,
 “userId”: “fool”
 }
 }


响应信息格式:

{
 “emotion”: {
 “robotEmotion”: {
 “a”: 0,
 “d”: 0,
 “emotionId”: 0,
 “p”: 0
 },
 “userEmotion”: {
 “a”: 0,
 “d”: 0,
 “emotionId”: 10300,
 “p”: 0
 }
 },
 “intent”: {
 “actionName”: “”,
 “code”: 10004,
 “intentName”: “”
 },
 “results”: [
 {
 “groupType”: 1,
 “resultType”: “text”,
 “values”: {
 “text”: “每次都是人家讲,欺负人家”
}
 }
 ]
 }


解析json

为了解析JSON数据,Android SDK为开发者提供了org.json包,该包存放了解析JSON数据的类,其中最重要的两个类是JSONObject用于解析对象结构的JSON数据,JSONArray用于类解析对象结构的JSON对象

四、大致流程
在这里插入图片描述

五、代码
1、布局文件
activity_robot.xml

<?xml version="1.0" encoding="utf-8"?>







chat_left_item.xml <?xml version="1.0" encoding="utf-8"?> 


 chatting_right_item.xml <?xml version="1.0" encoding="utf-8"?>


2、ChatBean
 package com.lsz.robot;/**
• 用于存放机器人和用户聊天的每条消息的状态,内容等属性。
 */
 public class ChatBean {
 public static final int SEND = 1; // 发送消息
 public static final int RECEIVE = 2; // 接收到消息,即机器人发送的消息
 private int state; // 消息的状态(接受|发送)
 private String message; // 消息的内容
public int getState() {
 return state;
 }
public void setState(int state) {
 this.state = state;
 }
public String getMessage() {
 return message;
 }
public void setMessage(String message) {
 this.message = message;
  • 3、ChatAdapter
•  package com.lsz.robot;
import android.content.Context;
 import android.content.Intent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 import android.widget.TextView;import java.util.List;
public class ChatAdapter extends BaseAdapter {
//聊天数据列表
private List<ChatBean> chatBeanList;

/*
   一个用于加载布局的系统服务,就是实例化与Layout XML文件对应的View对象,
   不能直接使用, 需要通过getLayoutInflater( )方法或getSystemService( )
   方法来获得与当前Context绑定的 LayoutInflater实例!
 */
private LayoutInflater layoutInflater;

//构造器
public ChatAdapter(List<ChatBean> chatBeanList, Context context) {
    this.chatBeanList = chatBeanList;
    layoutInflater = LayoutInflater.from(context);
}

/**
 *
 * @return 聊天数据列表的总数
 */
@Override
public int getCount() {
    return chatBeanList.size();
}

/**
 *
 * @param
 * @return 返回对应item上的对象
 */
@Override
public Object getItem(int position) {
    return chatBeanList.get(position);
}


/**
 * @param position
 * @return  item对象的id
  */
@Override
public long getItemId(int position) {
    return position;
}

/**
 * 返回对应的视图
 * @param position
 * @param contentView
 * @param viewGroup
 * @return
 */
@Override
public View getView(int position, View contentView, ViewGroup viewGroup) {

    //用来获取 Item 界面上的控件
    Holder holder = new Holder();

    // 判断当前的信息是发送的信息还是接受的信息,不同信息加载不同的view
    if (chatBeanList.get(position).getState() == ChatBean.RECEIVE){
        // 加载左边布局,也就是机器人对应的布局信息
        /*
        public View inflate (int resource, ViewGroup root, boolean attachToRoot) 该方法的三个参数依次为:
        ①要加载的布局对应的资源id
        ②为该布局的外部再嵌套一层父布局,如果不需要的话,写null就可以了!
        ③是否为加载的布局文件的最外层套一层root布局,不设置该参数的话,
        如果root不为null的话,则默认为true 如果root为null的话,
        attachToRoot就没有作用了! root不为null,attachToRoot为true的话,
        会在加载的布局文件最外层嵌套一层root布局; 为false的话,则root失去作用!
         简单理解就是:是否为加载的布局添加一个root的外层容器~!
         */
        contentView = layoutInflater.inflate(R.layout.chatting_left_item,null);
    }else {
        // 加载右边的布局
        contentView = layoutInflater.inflate(R.layout.chatting_right_item,null);
    }
    holder.tv_chat_content = (TextView) contentView.findViewById(R.id.tv_chat_content);
    // 将机器人与用户的聊天数据显示在界面上
    holder.tv_chat_content.setText(chatBeanList.get(position).getMessage());
    return contentView;
}
    • // private Intent intent = new Intent();
     //
     /**
     * 用来获取 Item 界面上的控件
     */
     class Holder{
     public TextView tv_chat_content; // 聊天内容
     }
     }
    
    
    
    
     
     4、RobotActivity
     package com.lsz.robot;import android.os.Bundle;
     import android.os.Handler;
     import android.os.Message;
     import android.text.TextUtils;
     import android.view.KeyEvent;
     import android.view.View;
     import android.widget.Button;
     import android.widget.EditText;
     import android.widget.ListView;
     import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;
    import org.json.JSONException;
     import org.json.JSONObject;import java.io.IOException;
     import java.util.ArrayList;
     import java.util.List;import okhttp3.Call;
     import okhttp3.Callback;
     import okhttp3.MediaType;
     import okhttp3.OkHttpClient;
     import okhttp3.Request;
     import okhttp3.RequestBody;
     import okhttp3.Response;

    /**

    • 界面交互
      */
      public class RobotActivity extends AppCompatActivity {
      private ListView listView;
      //聊天列表适配器对象
      private ChatAdapter adapter;
      // 存放所有聊天数据的集合
      private List chatBeanList;
      //编辑文本框对象
      private EditText et_send_msg;
      //发送键对象
      private Button btn_send;
      //图灵机器人 接口地址 http://openapi.tuling123.com/openapi/api/v2 47.93.153.235
      private static final String WEB_SITE = “http://openapi.tuling123.com/openapi/api/v2”;
      private static final String KEY = “4fce0bc257c04a9f88f0a55ee1797”;
      // 发送的消息
      private String sendMsg;
      // 存储欢迎信息的数组
      private String[] welcome;
      //捕获事件的对象
      private MHandler mHandler;
      // 获取数据
      private static final int MSG_OK = 1;
      /**
    • onCreate()函数是在activity初始化的时候调用的,
    • 通常情况下,我们需要在onCreate()中调用setContentView(int)函数填充屏幕的UI,
    • 一般通过findViewById(int)返回xml中定义的视图或组件的ID
    • @param savedInstanceState
      */
      @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_robot);
      chatBeanList = new ArrayList();
      mHandler = new MHandler();
      // 获取内置的欢迎信息[ 获取strings.xml文件中的欢迎信息,并将这些信息存放在数组welcome中 ]
      welcome = getResources().getStringArray(R.array.welcome);
      // 初始化界面控件
      initView();
      }

    /**

    • 初始化控件
      */
      public void initView() {
      //ListView允许用户通过上下滑动来将屏幕外的数据滚动到屏幕内,
      // 同时屏幕内原有的数据滚动出屏幕,从而显示更多的数据内容。
      listView = (ListView) findViewById(R.id.list);
      //编辑框
      et_send_msg = (EditText) findViewById(R.id.et_send_msg);
      //发送按钮
      btn_send = (Button) findViewById(R.id.btn_send);
      //在聊天视图框中,显示所有的聊天信息
      adapter = new ChatAdapter(chatBeanList, this);
      listView.setAdapter(adapter);
      //为发送按键添加一个点击监听器
      btn_send.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View view) {
      sendData(); // 点击发送按钮,发送信息
      }
      });
      //键盘监听:键盘按下enter键时发送消息
      et_send_msg.setOnKeyListener(new View.OnKeyListener() {
      @Override
      public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
      if (keyCode == KeyEvent.KEYCODE_ENTER && keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
      sendData();
      }
      return false;
      }
      });
      // 随机获取一个数
      int position = (int) (Math.random() * welcome.length - 1);
      // 用随机数获取机器人的欢迎消息
      showData(welcome[position]);
      }

    /**

    • 发送信息
      */
      private void sendData() {
      // 获取输入的字符串
      sendMsg = et_send_msg.getText().toString();
      if (TextUtils.isEmpty(sendMsg)) { // 判断是否为空
    //即时提示信息
     Toast.makeText(this, "您还没有输入任何信息哦", Toast.LENGTH_LONG).show();
     return;

    }
    et_send_msg.setText("");
    // 替换空格和换行
    sendMsg = sendMsg.replaceAll(" “, “”).replaceAll(”\n", “”).trim();
    ChatBean chatBean = new ChatBean();
    chatBean.setMessage(sendMsg);

    // SEND表示自己发送的消息 ===>1
    chatBean.setState(chatBean.SEND);
    chatBeanList.add(chatBean);

    // 更新 ListView 列表
    adapter.notifyDataSetChanged();

    // 从服务器获取机器人发送的消息
    getDataFromServer();
    System.out.println(“sendDate:”);
    }

    /**

    • 从服务端接收数据
      */
      public void getDataFromServer() {
      // 用来拼装 json 数据(json数据按照接口文档来书写)
      String startJson = “{“reqType”:0,“perception”:{“inputText”:{“text”:”";
      String endJson = “”}},“userInfo”:{“apiKey”:" + KEY + “,“userId”:”\n" +
      “682469”}}";
      // 封装请求体
      String json = startJson + sendMsg +
      endJson;
      //System.out.println(json);
      RequestBody body = RequestBody.create(
      MediaType.parse(“application/json;charset=utf-8”), json);
      //构建请求,传入url和请求参数(json形式)
      Request request = new Request.Builder()
      .url(WEB_SITE)
      .post(body)
      .build();
      OkHttpClient okHttpClient = new OkHttpClient();
      Call call = okHttpClient.newCall(request);
      // 开启异步线程访问网络
      call.enqueue(new Callback() {
    /**
      * 获取响应失败
      * @param call
      * @param e
      */
     @Override
     public void onFailure(Call call, IOException e) {

    // System.out.println(“回调失败”);
    e.printStackTrace();
    }

    /**
             * 获取响应成功
             * @param call
             * @param response
             * @throws IOException
             */
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String res = response.body().string();
                Message message = new Message();
                message.what = MSG_OK;
                message.obj = res;
                mHandler.sendMessage(message);

    // System.out.println(“Call回传”+res);
    }
    });
    }

    /**
     * 事件捕获
     */
    class MHandler extends Handler {
    
        @Override
        public void dispatchMessage(Message msg) {
            super.dispatchMessage(msg);
            switch (msg.what) {
    
                //成功获取数据后,将消息对象转化成字符串进行解析
                case MSG_OK:
                    if (msg.obj != null) {
                        String vlResult = (String) msg.obj;
                        parseData(vlResult);
                    }
                    break;
            }
        }
    }
    
    /**
     * 解析服务器返回的数据
     * @param JsonData
     */
    private void parseData(String JsonData) { // Json解析

    // try {
    // JSONObject obj = new JSONObject(JsonData);
    // String content = obj.getString(“text”); // 获取的机器人信息
    // int code = obj.getInt(“code”); //服务器状态码
    // updateView(code, content); // 更新界面
    // } catch (JSONException e) {
    // e.printStackTrace();
    // showData(“主人,你的网络不好哦”);
    // }

    //解析成功更新界面
        try {
            JSONObject obj = new JSONObject(JsonData);
            System.out.println("obj:" + obj);
            int code = (int) new JSONObject(obj.get("intent").toString()).get("code");
            String content = obj.getJSONArray("results").getString(0);
            String s =new JSONObject( new JSONObject(content).get("values").toString()).getString("text");

    // System.out.println(“content:” + s);

    // 更新界面
            updateView(code, s);
        } catch (JSONException e) {
            e.printStackTrace();
            showData("主人,你的网络不好哦");
        }
    }
    
    private void showData(String message) {
        ChatBean chatBean = new ChatBean();
        chatBean.setMessage(message);
    
        // RECEIVE 表示机器人发送的信息
        chatBean.setState(ChatBean.RECEIVE);
        // 将机器人发送的信息添加奥chatBeanList集合中
        chatBeanList.add(chatBean);
    
        //更新聊天视图
        adapter.notifyDataSetChanged();
    }
    
    private void updateView(int code, String content) {
        // code 有很多种类,可以参考官网
        switch (code) {
            case 4004:
                showData("主人,今天我累了,我要休息了,明天再来找我玩吧");
                break;
    
            case 40005:
                showData("主人,你说的是外星语吗?");
                break;
            case 40006:
                showData("主人,我今天要去约会哦,暂时不上班啦");
                break;
            case 40007:
                showData("主人,明天在和你耍啦,我生病了,呜呜......");
                break;
            default:
                showData(content);  // 默认显示传入的数据
                break;
        }
    }
    
    protected long exitTime; // 记录第一次点击的时间
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK
                && event.getAction() == KeyEvent.ACTION_DOWN) {
            if ((System.currentTimeMillis() - exitTime) > 2000) {
                Toast.makeText(RobotActivity.this, "再按一次退出智能聊天程序!",
                        Toast.LENGTH_SHORT).show();
                exitTime = System.currentTimeMillis();
            } else {
                RobotActivity.this.finish();
                System.exit(0);
            }
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    }