Android聊天机器人
一、项目介绍本项目使用android来开发一个智能聊天机器人,该智能聊天机器人主要是供用户娱乐,他可以供用户娱乐休闲,他可以与用户讲故事、说笑话、说笑话、跟用户聊天,非常有趣。
涉及到知识点:
网络编程【okhttp】json数据解析Handler处理在实现智能机器人聊天功能,在实现这个功能的过程中申请了一个图灵机器人的key,根据该key并通过异步访问网络获取机器人回复的信息,接着调用Handler将获取的信息发送到主线程,并通过JSON解析将获取的聊天数据解析成字符串显示到界面上。
二、项目结果三、项目开发1、需求开发聊天机器人的主要功能就是和用户进行智能对话,如此智能的效果,涉及对用户语义理解,以及海量信息的精准搜索,我们没有办法做到,选择调用的是第三方公司提供的开发API。
图灵机器人:http://www.tuling123.com/member/robot/index.jhtm
模型:
2、开发环境介绍开发工具:JDK8API版本:AndroidAPI273、功能实现【聊天功能】从需求出发进行分析,显然要实现聊天功能,我们需要一个界面:
界面上需要显示机器人及用户的头像,还需要一个编辑框和发送按钮以及显示聊天信息的界面。聊天界面使用了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,设置机器人欢迎信息
注意问题:
由于需要访问网络,需要对AndroidStudio模拟器联网进行设置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数据,AndroidSDK为开发者提供了org.json包,该包存放了解析JSON数据的类,其中最重要的两个类是JSONObject用于解析对象结构的JSON数据,JSONArray用于类解析对象结构的JSON对象
四、大致流程五、代码1、布局文件activity_robot.xml
chat_left_item.xml
chatting_right_item.xml
2、ChatBeanpackagecom.lsz.robot;/***用于存放机器人和用户聊天的每条消息的状态,内容等属性。*/publicclassChatBean{publicstaticfinalintSEND=1;//发送消息publicstaticfinalintRECEIVE=2;//接收到消息,即机器人发送的消息privateintstate;//消息的状态(接受|发送)privateStringmessage;//消息的内容publicintgetState(){returnstate;}publicvoidsetState(intstate){this.state=state;}publicStringgetMessage(){returnmessage;}publicvoidsetMessage(Stringmessage){this.message=message;}}3、ChatAdapterpackagecom.lsz.robot;importandroid.content.Context;importandroid.content.Intent;importandroid.view.LayoutInflater;importandroid.view.View;importandroid.view.ViewGroup;importandroid.widget.BaseAdapter;importandroid.widget.TextView;importjava.util.List;publicclassChatAdapterextendsBaseAdapter{//聊天数据列表privateListchatBeanList;/*一个用于加载布局的系统服务,就是实例化与LayoutXML文件对应的View对象,不能直接使用,需要通过getLayoutInflater()方法或getSystemService()方法来获得与当前Context绑定的LayoutInflater实例!*/privateLayoutInflaterlayoutInflater;//构造器publicChatAdapter(ListchatBeanList,Contextcontext){this.chatBeanList=chatBeanList;layoutInflater=LayoutInflater.from(context);}/****@return聊天数据列表的总数*/@OverridepublicintgetCount(){returnchatBeanList.size();}/****@param*@return返回对应item上的对象*/@OverridepublicObjectgetItem(intposition){returnchatBeanList.get(position);}/***@paramposition*@returnitem对象的id*/@OverridepubliclonggetItemId(intposition){returnposition;}/***返回对应的视图*@paramposition*@paramcontentView*@paramviewGroup*@return*/@OverridepublicViewgetView(intposition,ViewcontentView,ViewGroupviewGroup){//用来获取Item界面上的控件Holderholder=newHolder();//判断当前的信息是发送的信息还是接受的信息,不同信息加载不同的viewif(chatBeanList.get(position).getState()==ChatBean.RECEIVE){//加载左边布局,也就是机器人对应的布局信息/*publicViewinflate(intresource,ViewGrouproot,booleanattachToRoot)该方法的三个参数依次为:①要加载的布局对应的资源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());returncontentView;}//privateIntentintent=newIntent();///***用来获取Item界面上的控件*/classHolder{publicTextViewtv_chat_content;//聊天内容}}4、RobotActivitypackagecom.lsz.robot;importandroid.os.Bundle;importandroid.os.Handler;importandroid.os.Message;importandroid.text.TextUtils;importandroid.view.KeyEvent;importandroid.view.View;importandroid.widget.Button;importandroid.widget.EditText;importandroid.widget.ListView;importandroid.widget.Toast;importandroidx.appcompat.app.AppCompatActivity;importorg.json.JSONException;importorg.json.JSONObject;importjava.io.IOException;importjava.util.ArrayList;importjava.util.List;importokhttp3.Call;importokhttp3.Callback;importokhttp3.MediaType;importokhttp3.OkHttpClient;importokhttp3.Request;importokhttp3.RequestBody;importokhttp3.Response;/***界面交互*/publicclassRobotActivityextendsAppCompatActivity{privateListViewlistView;//聊天列表适配器对象privateChatAdapteradapter;//存放所有聊天数据的集合privateListchatBeanList;//编辑文本框对象privateEditTextet_send_msg;//发送键对象privateButtonbtn_send;//图灵机器人接口地址http://openapi.tuling123.com/openapi/api/v247.93.153.235privatestaticfinalStringWEB_SITE="http://openapi.tuling123.com/openapi/api/v2";privatestaticfinalStringKEY="4fce0bc257c04a9f88f0a55ee1797";//发送的消息privateStringsendMsg;//存储欢迎信息的数组privateString[]welcome;//捕获事件的对象privateMHandlermHandler;//获取数据privatestaticfinalintMSG_OK=1;/***onCreate()函数是在activity初始化的时候调用的,*通常情况下,我们需要在onCreate()中调用setContentView(int)函数填充屏幕的UI,*一般通过findViewById(int)返回xml中定义的视图或组件的ID*@paramsavedInstanceState*/@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_robot);chatBeanList=newArrayList();mHandler=newMHandler();//获取内置的欢迎信息[获取strings.xml文件中的欢迎信息,并将这些信息存放在数组welcome中]welcome=getResources().getStringArray(R.array.welcome);//初始化界面控件initView();}/***初始化控件*/publicvoidinitView(){//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=newChatAdapter(chatBeanList,this);listView.setAdapter(adapter);//为发送按键添加一个点击监听器btn_send.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(Viewview){sendData();//点击发送按钮,发送信息}});//键盘监听:键盘按下enter键时发送消息et_send_msg.setOnKeyListener(newView.OnKeyListener(){@OverridepublicbooleanonKey(Viewview,intkeyCode,KeyEventkeyEvent){if(keyCode==KeyEvent.KEYCODE_ENTER&&keyEvent.getAction()==KeyEvent.ACTION_DOWN){sendData();}returnfalse;}});//随机获取一个数intposition=(int)(Math.random()*welcome.length-1);//用随机数获取机器人的欢迎消息showData(welcome[position]);}/***发送信息*/privatevoidsendData(){//获取输入的字符串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(" ","").trim();ChatBeanchatBean=newChatBean();chatBean.setMessage(sendMsg);//SEND表示自己发送的消息===>1chatBean.setState(chatBean.SEND);chatBeanList.add(chatBean);//更新ListView列表adapter.notifyDataSetChanged();//从服务器获取机器人发送的消息getDataFromServer();System.out.println("sendDate:");}/***从服务端接收数据*/publicvoidgetDataFromServer(){//用来拼装json数据(json数据按照接口文档来书写)StringstartJson="{"reqType":0,"perception":{"inputText":{"text":"";StringendJson=""}},"userInfo":{"apiKey":"+KEY+","userId":" "+"682469"}}";//封装请求体Stringjson=startJson+sendMsg+endJson;//System.out.println(json);RequestBodybody=RequestBody.create(MediaType.parse("application/json;charset=utf-8"),json);//构建请求,传入url和请求参数(json形式)Requestrequest=newRequest.Builder().url(WEB_SITE).post(body).build();OkHttpClientokHttpClient=newOkHttpClient();Callcall=okHttpClient.newCall(request);//开启异步线程访问网络call.enqueue(newCallback(){/***获取响应失败*@paramcall*@parame*/@OverridepublicvoidonFailure(Callcall,IOExceptione){//System.out.println("回调失败");e.printStackTrace();}/***获取响应成功*@paramcall*@paramresponse*@throwsIOException*/@OverridepublicvoidonResponse(Callcall,Responseresponse)throwsIOException{Stringres=response.body().string();Messagemessage=newMessage();message.what=MSG_OK;message.obj=res;mHandler.sendMessage(message);//System.out.println("Call回传"+res);}});}/***事件捕获*/classMHandlerextendsHandler{@OverridepublicvoiddispatchMessage(Messagemsg){super.dispatchMessage(msg);switch(msg.what){//成功获取数据后,将消息对象转化成字符串进行解析caseMSG_OK:if(msg.obj!=null){StringvlResult=(String)msg.obj;parseData(vlResult);}break;}}}/***解析服务器返回的数据*@paramJsonData*/privatevoidparseData(StringJsonData){//Json解析//try{//JSONObjectobj=newJSONObject(JsonData);//Stringcontent=obj.getString("text");//获取的机器人信息//intcode=obj.getInt("code");//服务器状态码//updateView(code,content);//更新界面//}catch(JSONExceptione){//e.printStackTrace();//showData("主人,你的网络不好哦");//}//解析成功更新界面try{JSONObjectobj=newJSONObject(JsonData);System.out.println("obj:"+obj);intcode=(int)newJSONObject(obj.get("intent").toString()).get("code");Stringcontent=obj.getJSONArray("results").getString(0);Strings=newJSONObject(newJSONObject(content).get("values").toString()).getString("text");//System.out.println("content:"+s);//更新界面updateView(code,s);}catch(JSONExceptione){e.printStackTrace();showData("主人,你的网络不好哦");}}privatevoidshowData(Stringmessage){ChatBeanchatBean=newChatBean();chatBean.setMessage(message);//RECEIVE表示机器人发送的信息chatBean.setState(ChatBean.RECEIVE);//将机器人发送的信息添加奥chatBeanList集合中chatBeanList.add(chatBean);//更新聊天视图adapter.notifyDataSetChanged();}privatevoidupdateView(intcode,Stringcontent){//code有很多种类,可以参考官网switch(code){case4004:showData("主人,今天我累了,我要休息了,明天再来找我玩吧");break;case40005:showData("主人,你说的是外星语吗?");break;case40006:showData("主人,我今天要去约会哦,暂时不上班啦");break;case40007:showData("主人,明天在和你耍啦,我生病了,呜呜......");break;default:showData(content);//默认显示传入的数据break;}}protectedlongexitTime;//记录第一次点击的时间@OverridepublicbooleanonKeyDown(intkeyCode,KeyEventevent){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);}returntrue;}returnsuper.onKeyDown(keyCode,event);}}