☰
Android聊天机器人课程设计:ListView+OkHttp实现聊天闭环
2026/10/28 12:39:37 网站建设 项目流程

简介:这是一份面向高校移动应用开发课程学习者与课程设计备赛学生的Android聊天机器人课设报告,围绕图灵机器人平台的API接入,系统梳理了从环境搭建到功能落地的完整实践路径,适合需要完成课程设计、巩固Android核心技术的初学者与中级开发者参考。压缩包内为1个doc文档,约1.46MB,按实验目的、实验内容、程序设计与说明的顺序组织,结构接近正式课设模板,可直接参照撰写。内容覆盖图灵机器人注册与API Key申请、activity_main与chatting_left_item等布局的线性与相对布局设计、ChatBean消息实体与ChatAdapter数据适配器编写、OkHttp网络请求与JSON解析,以及initView、showData、sendData、getDataFromServer、updateView五个方法的职责划分和onKeyDown退出处理,并配有各界面运行截图与类的说明,便于对照理解消息状态区分、左右气泡样式等细节。目前已有2708人学习,可作为移动应用开发课设的完整参考范例。

1. 需求立住:为什么把Android聊天机器人做成课程设计报告

做移动应用开发课程设计报告时,Android聊天机器人常被当成“调个接口就完事”的题目,真到验收却容易翻车:消息不滚动、左右气泡错位、断网直接崩。这个题要解决的不是炫技,而是把UI布局、数据存储、四大组件、网络编程和JSON解析串成一个能演示、能讲清的闭环。适合正在找安卓app课程设计选题的学生,也适合需要一份完整课程设计报告做参考的从业者。它不追求复杂推荐算法,重点在消息模型、适配器复用、异步请求和异常兜底。资源达人分享计划里常见的作业习题往往只给题目,这份报告则把界面、类说明和运行截图一起补齐,方便对照实现。

2. 方案选型:从对话闭环到OkHttp与开放平台接口

2.1 需求拆解:一次对话要经过哪五个动作

把“聊天”拆开看,它不是一个页面,而是一条链路:进入页面显示欢迎语、用户输入并点击发送、把用户消息追加到列表、向开放平台请求回复、解析回复并刷新列表。五个动作里任何一个断掉,演示都会显得像半成品。课程设计报告里最容易写虚的地方,是把“聊天功能”写成一句功能描述,却没有把消息状态、请求参数、返回字段和界面刷新顺序讲清楚。我的建议是先画一张文字版链路图,再落到类和方法上,这样报告里的设计说明和代码能对得上。

动作对应实现点验收信号
进入页面读取欢迎信息并插入列表首次打开能看到机器人欢迎语
用户发送输入框非空校验、状态置为发送右侧出现用户气泡
请求回复OkHttp异步请求开放平台接口不阻塞主线程,按钮可恢复
解析回复取JSON文本字段、异常兜底正常返回时显示机器人文本
刷新列表通知适配器、滚动到底部新消息可见且不串位

这张表可以直接放进课程设计报告的“需求分析”或“实验内容”部分。它比大段文字更容易让评阅人看到你知道每一步落在哪。参数上,用户消息状态通常设为1,机器人消息状态设为2,状态不是给用户看的,是给适配器判断加载左边还是右边布局用的。输入框要做去空格处理,空字符串不发送,连续点击发送要有节流,否则列表里会出现重复消息。

2.2 技术选型:为什么是ListView加Adapter,而不是一上来堆复杂框架

课程设计报告要体现“学过什么、怎么用起来”。ListView加BaseAdapter的组合虽然不新,但它把数据源、条目布局和复用机制暴露得很清楚,适合讲明白“消息列表是怎么显示的”。如果换成过于封装的框架,报告里容易只剩调用代码,设计说明写不深。聊天页面消息量不大,ListView足够;左右气泡用两个item布局区分,适配器里按状态加载不同布局,逻辑直观,答辩时也好讲。网络层用OkHttp,原文给出的版本是3.12.0,课程环境锁定这个版本即可,重点讲清楚异步请求、回调线程和失败处理。

选型点采用方案理由常见误用
消息列表ListView+自定义Adapter复用机制清晰,便于讲状态判断在getView里反复创建布局
网络请求OkHttp异步请求不阻塞主线程,回调结构清楚在主线程直接发请求
数据承载ChatBean字段少、职责单一把JSON对象直接塞进列表
布局复用两个item布局+style左右气泡样式统一两边布局各写一套字号边距
欢迎信息strings.xml便于统一修改硬编码在Activity里

提示:报告里写选型理由时,不要只写“因为简单”。要写清楚它对应了课程中的哪个知识点,以及它让哪个验收点变得可检查。

构造请求参数时,我一般会把输入文本、用户标识和机器人标识分开,避免把Key写死在Adapter里。下面这段代码只表达参数组织方式,接口地址和Key按实际课程环境替换,报告里也不要贴真实Key。

// 组织对话请求参数:输入文本、用户标识、机器人标识分开放,便于排查 public Map<String, String> buildChatParams(String input, String userId, String robotKey) { Map<String, String> params = new HashMap<>(); params.put("info", input == null ? "" : input.trim()); // 用户输入去空格 params.put("userid", userId); // 区分会话用户 params.put("key", robotKey); // 开放平台身份标识 return params; }

这段代码的重点不是Map本身,而是参数边界:输入为空时给空串,不让null进入请求;用户标识用于平台侧区分会话;Key只作为身份标识,不参与界面展示。报告里的参数说明要写到“每个字段从哪来、为空时怎么处理、出错时看哪里”,不要只贴一段请求代码就结束。

2.3 接口字段与消息模型:先把数据说清楚再写界面

开放平台返回的JSON通常包含文本字段和状态字段,字段名以实际返回为准。课程设计报告里不要编造一堆用不上的字段,挑真正参与界面显示的两三个讲透:回复文本、消息状态、时间或标识。消息模型ChatBean至少要有内容、状态、时间三个属性,状态用1和2区分发送与接收。这样适配器不需要猜“这条消息是谁发的”,Activity也不需要临时拼字符串。数据模型稳定后,布局和适配器才有依据。

注意:报告里的JSON示例要去掉真实Key、真实用户标识和真实请求地址,保留字段结构即可。

3. 总体设计:三层结构、四类布局与消息模型

3.1 总体架构:Activity、Adapter、Bean、布局各管一段

这套课程设计报告对应的应用不大,但职责边界要清楚。RobotActivity管生命周期、控件初始化、发送动作和网络回调;ChatAdapter管“一条消息怎么变成一行界面”;ChatBean管消息字段;布局文件管静态结构;strings.xml管欢迎语;drawable和style管气泡背景与文字样式。五块内容分开后,报告里的“类的说明”就不会写成流水账,答辩时也能按“界面层、数据层、网络层”讲。通常的做法是让Activity只做协调,不在getView里写网络请求,也不在Adapter里读Key。

文字版架构可以这样描述:最上层是聊天页面,包含标题、消息列表、输入区和发送按钮;中间层是适配器,根据ChatBean的状态选择左气泡或右气泡;数据层是消息集合,进入页面时插入欢迎消息,用户发送时追加状态1,平台回复时追加状态2;网络层负责构造请求、异步回调、JSON取文本;资源层提供字符串、颜色、背景选择器和文字样式。这个描述可以直接作为报告里的“总体设计”段落,不需要画图也能讲清层次。

模块职责不该做的事
RobotActivity初始化控件、处理点击、发起请求、刷新列表在点击事件里直接解析大段JSON
ChatAdapter按状态加载左右布局、绑定文本和头像持有Activity引用做网络请求
ChatBean保存消息内容、状态、时间混入界面颜色和布局参数
item布局定义单条消息的头像与气泡结构写业务判断
strings.xml存放欢迎语和固定提示存放Key和用户输入

3.2 主界面布局:LinearLayout套RelativeLayout的取舍

activity_main采用线性布局做外层,上面是标题TextView,下面是消息列表ListView,再下面是输入区。输入区里放EditText和发送按钮,按钮在右侧。这个结构适合课程设计:纵向排列清楚,评阅人一眼能看出页面由哪几块组成。外层LinearLayout的orientation设为vertical,ListView的layout_height设为0dp并给layout_weight=1,让它占满剩余空间;输入区给固定高度或wrap_content,避免键盘弹出时把列表压没。很多报告只写“用了线性布局”,却没写权重,导致复现时列表高度不对。

<!-- activity_main.xml:纵向三段式,ListView用权重占满中间区域 --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/tv_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="聊天窗口" /> <ListView android:id="@+id/lv_chat" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:divider="@null" android:stackFromBottom="true" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:id="@+id/et_input" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_toLeftOf="@+id/btn_send" android:hint="输入聊天内容" /> <Button android:id="@+id/btn_send" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:text="发送" /> </RelativeLayout> </LinearLayout>

这段布局的关键参数有三个:ListView高度用0dp加weight,保证它吃掉中间剩余空间;divider设为空,避免默认分割线破坏气泡观感;stackFromBottom让消息从底部开始堆叠,接近聊天工具的习惯。输入框用layout_toLeftOf把右侧让给按钮,按钮alignParentRight固定在右边。报告里说明布局时,要写“为什么这样设”,不要只贴XML。

3.3 左右气泡与样式复用:两个item布局和一个style

聊天界面需要两个条目布局:chatting_left_item显示机器人头像和左侧气泡,chatting_right_item显示用户头像和右侧气泡。两个布局结构相似,都是RelativeLayout里放ImageView和TextView,区别在头像位置、气泡背景选择器和文字对齐。TextView的style统一引用chat_content_style,背景分别引用chat_left_selector和chat_right_selector。这样字号、内边距、最大宽度只维护一份,左右两边不会出现“一个气泡胖一个气泡瘦”的玄学问题。通常的做法是把气泡的最大宽度设为屏幕宽度的一定比例,避免长文本把头像挤出屏幕。

<!-- chatting_left_item.xml:机器人消息,头像在左,气泡在右 --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp"> <ImageView android:id="@+id/iv_avatar" android:layout_width="40dp" android:layout_height="40dp" android:src="@drawable/robot_head" /> <TextView android:id="@+id/tv_content" style="@style/chat_content_style" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@+id/iv_avatar" android:background="@drawable/chat_left_selector" android:text="机器人消息" /> </RelativeLayout>

这段item布局只负责一条消息的静态结构。头像用固定尺寸,保证列表滚动时不会忽大忽小;TextView用style统一文字样式,背景用selector区分按压或普通状态;layout_toRightOf把气泡放到头像右侧。右侧item布局把ImageView换成alignParentRight,TextView放到头像左侧,背景换成chat_right_selector即可。报告里要说明两个布局的差异点,不要复制两段XML却不解释。

3.4 ChatBean字段设计:状态值1和2是适配器的分岔口

ChatBean是这套课程设计报告里最容易被写薄的部分。它不需要复杂继承,但字段要够用:消息内容content、消息状态state、消息时间time。state为1表示用户发送,为2表示机器人接收。构造方法可以带参,也可以空构造加get/set。报告里要写清楚“为什么用int不用boolean”,因为后续如果要扩展“系统提示”类型,int更容易加值。时间字段不一定要显示,但保存下来便于排序和排查。消息内容不要直接存JSON,解析后的纯文本更适合界面绑定。

// ChatBean:一条消息的最小字段集合,状态1=发送,2=接收 public class ChatBean { private String content; // 消息文本 private int state; // 1用户发送,2机器人接收 private long time; // 消息时间,便于排序排查 public ChatBean(String content, int state) { this.content = content; this.state = state; this.time = System.currentTimeMillis(); } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public int getState() { return state; } public void setState(int state) { this.state = state; } public long getTime() { return time; } public void setTime(long time) { this.time = time; } }

这段代码的参数说明要落到报告里:content是解析后的纯文本;state只取1或2,适配器据此选择布局;time在构造时写入当前时间,避免每条消息手动传。get/set方法看起来普通,但它们让适配器绑定数据和后续扩展都更稳。报告里的“类的说明”可以按“字段含义、状态取值、构造时机”三段写,比只列方法名清楚。

4. 核心实现:RobotActivity五个方法与ChatAdapter适配

4.1 RobotActivity五个方法:initView、showData、sendData、getDataFromServer、updateView

RobotActivity是聊天页面的协调者。initView负责找控件、初始化列表和适配器;showData负责把欢迎信息插入数据源并刷新;sendData负责取输入、校验、追加用户消息、清空输入框、发起请求;getDataFromServer负责异步请求和JSON解析;updateView负责把新消息通知适配器并滚动到底部。五个方法分开后,点击事件只调用sendData,不在点击里塞网络代码。报告里的方法说明要写“谁调用、做什么、失败时怎么返回”,不要只写方法名。

方法输入输出/副作用失败处理
initView无绑定控件、设置适配器控件为空时检查id
showData欢迎语列表新增状态2消息欢迎语为空则跳过
sendData输入框文本新增状态1消息、发请求空文本不发送
getDataFromServer用户文本回调机器人文本网络失败插入提示
updateView新消息通知适配器、滚动到底避免重复刷新

initView里要注意ListView的适配器只设置一次,数据集合在Activity里维护,适配器持有同一个集合引用。showData在onCreate里调用一次,插入欢迎语后刷新。sendData先trim,空串直接return;然后创建状态1的ChatBean加入集合,清空输入框,调用updateView,再调用getDataFromServer。getDataFromServer用OkHttp异步请求,回调里判断响应是否成功,解析文本,创建状态2的ChatBean,再runOnUiThread里更新界面。updateView里调用adapter.notifyDataSetChanged(),然后lvChat.setSelection(adapter.getCount()-1)滚到底部。

提示:报告里写方法说明时,把“线程切换”单独写一句。网络回调不在主线程,直接碰控件是常见崩溃点。

4.2 sendData与getDataFromServer:OkHttp异步请求和JSON解析

sendData的核心是“先落用户消息,再发请求”。用户点击发送后,界面要立刻出现右侧气泡,不能等网络回来才显示,否则演示时会有卡顿感。请求参数按上一章的方式组织,输入文本去空格,用户标识可以用一个固定字符串或本地生成值,课程设计里不必做登录系统。请求发出后按钮可以暂时禁用,回调后恢复,防止连续点击造成重复请求。报告里要写清楚按钮状态恢复的位置,最好放在回调的finally式逻辑里。

// sendData:先追加用户消息,再异步请求,避免界面等待网络 private void sendData() { String input = etInput.getText().toString().trim(); if (input.length() == 0) return; // 空输入不发送 ChatBean userMsg = new ChatBean(input, 1); chatList.add(userMsg); etInput.setText(""); updateView(); getDataFromServer(input); } // getDataFromServer:OkHttp异步请求,回调里解析文本并切回主线程 private void getDataFromServer(String input) { Map<String, String> params = buildChatParams(input, "course_user", robotKey); FormBody body = new FormBody.Builder() .add("info", params.get("info")) .add("userid", params.get("userid")) .add("key", params.get("key")) .build(); Request request = new Request.Builder() .url(chatEndpoint) // 开放平台对话接口地址,报告中用占位符 .post(body) .build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { runOnUiThread(() -> addRobotMessage("网络开小差了,稍后再试")); } @Override public void onResponse(Call call, Response response) throws IOException { String text = "没有听清"; try (ResponseBody responseBody = response.body()) { if (responseBody != null) { JSONObject json = new JSONObject(responseBody.string()); text = json.optString("text", "没有听清"); // 字段名按实际返回调整 } } catch (Exception e) { text = "回复解析失败"; } String finalText = text; runOnUiThread(() -> addRobotMessage(finalText)); } }); }

这段代码的关键点有四个:FormBody按表单字段提交,字段名和平台要求一致;enqueue是异步请求,不阻塞主线程;onFailure和解析异常都给出可读提示,不让界面停在“发送中”;runOnUiThread把更新界面切回主线程。参数说明里要写清chatEndpoint和robotKey来自课程环境配置,报告正文不要贴真实值。addRobotMessage是抽取出来的小方法,负责创建状态2消息、加入集合、调用updateView,这样回调里不会重复三行代码。

4.3 ChatAdapter的getView:按状态加载左右布局

ChatAdapter继承BaseAdapter,构造时传入Context和消息集合。getView里先复用convertView,再按position取出ChatBean,判断state。state为1加载chatting_right_item,state为2加载chatting_left_item。加载后用viewHolder或findViewById绑定内容。课程设计里可以用简单ViewHolder模式,避免滚动时重复查找控件。报告里要写清楚“为什么加载不同布局”,以及“状态判断放在适配器而不是Activity”的理由:一条消息怎么显示,是适配器的职责。

// getView:按消息状态选择左右布局,再绑定文本 @Override public View getView(int position, View convertView, ViewGroup parent) { ChatBean bean = data.get(position); ViewHolder holder; if (convertView == null) { int layoutId = bean.getState() == 1 ? R.layout.chatting_right_item : R.layout.chatting_left_item; convertView = LayoutInflater.from(context).inflate(layoutId, parent, false); holder = new ViewHolder(); holder.tvContent = convertView.findViewById(R.id.tv_content); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.tvContent.setText(bean.getContent()); return convertView; } static class ViewHolder { TextView tvContent; }

这段代码里,convertView为空才inflate,复用时不重新创建布局;layoutId由state决定,state为1走右侧用户布局,否则走左侧机器人布局;ViewHolder只保存TextView,因为头像固定,不需要每次绑定。这里有一个容易忽略的点:如果左右布局的控件id不一致,复用会出错。所以两个item布局里TextView的id要统一为tv_content,这样ViewHolder才能通用。报告里把这一点写进“适配器说明”,评阅人会看出你考虑过复用。

4.4 欢迎信息与返回键退出:小功能也要写进设计说明

欢迎信息放在strings.xml里,进入页面时取出来插入列表,状态设为2,显示在左侧。欢迎语可以准备几条随机取一条,但课程设计报告里不必把随机逻辑写复杂,能说明“首次进入有机器人欢迎语”即可。onKeyDown重写后退键,弹出退出确认或直接finish。直接finish在演示时容易误触退出,通常的做法是加一个确认对话框,或者连续按两次退出。报告里要写清楚为什么处理返回键:避免聊天页面按返回直接回到桌面,演示节奏被打断。

// onKeyDown:拦截返回键,避免演示时误退出 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { finish(); // 课程设计可在此换成确认对话框 return true; } return super.onKeyDown(keyCode, event); }

这段代码参数简单,但报告里要说明:keyCode判断返回键,finish结束当前Activity,return true表示事件已消费。如果课程要求保留系统返回行为,可以改成弹对话框。欢迎信息、返回键、输入框清空这些细节看似小,验收时却直接影响演示流畅度,报告里不要省略。

5. 常见问题与排查:消息错位、空回复和崩溃的五个坑

5.1 消息左右错位:状态值写反或布局id不统一

现象是用户发的话跑到左边,机器人回复跑到右边,或者第一条消息正常、滚动后错位。原因通常有两个:ChatBean的state在发送和接收时写反;两个item布局里TextView的id不一致,适配器复用时绑到了错误控件。排查时先看发送分支创建的是不是state=1,再看接收分支是不是state=2,最后检查两个item布局的TextView id是否都叫tv_content。解决方式是把状态值集中成常量,适配器里只认1和2,布局id统一,并在报告里写一张状态对照表。血泪经验是不要用“看起来像用户”来判断,状态是唯一依据。

5.2 主线程发网络请求:界面卡住或直接抛异常

现象是点击发送后界面卡几秒,或者日志里出现主线程网络异常,严重时应用无响应。原因是在点击事件里直接调用同步请求,或者把OkHttp的execute放在主线程。解决方式是用enqueue异步请求,回调里不直接碰控件,用runOnUiThread切回主线程。报告里要写清楚“请求在子线程、界面更新在主线程”这条边界。排查时看日志堆栈里有没有NetworkOnMainThread相关字样,再看回调里更新列表的代码是否包在runOnUiThread里。按钮状态也要在回调后恢复,避免用户以为没点上。

5.3 JSON字段缺失或空回复:解析直接崩

现象是平台返回正常,但应用崩溃或显示空白。原因常见为字段名写错、返回体为空、response.body()为null、解析时没有try-catch。解决方式是先判断响应是否成功,再判断body是否为空,用optString给默认值,解析异常时插入“回复解析失败”而不是让应用退出。报告里可以放一段简化JSON结构,说明只取文本字段。排查时把返回字符串打印到日志,注意不要把Key和用户标识打出来。课程设计里不需要处理所有异常,但空回复和解析失败必须兜住,否则答辩现场很容易翻车。

5.4 ListView复用导致头像串位:convertView没区分布局类型

现象是快速滚动后,机器人消息旁边出现用户头像,或者气泡背景串到另一条消息。原因是convertView复用时不看消息状态,直接把用户布局复用到机器人消息上;或者getView里只判断convertView为空,没判断布局类型是否匹配。解决方式有两种:一是两个item布局控件id统一,复用后只换文本;二是重写getItemViewType和getViewTypeCount,让ListView按类型复用。课程设计推荐第一种,简单可控。报告里要写“为什么id统一”,这是适配器能通用的前提。

5.5 键盘遮挡与返回键误触:演示节奏被打断

现象是输入框被软键盘挡住,或者按返回键直接退出应用。原因是布局没有处理键盘弹出模式,返回键直接finish。解决方式是在清单文件里给Activity设置合适的windowSoftInputMode,让页面整体上推或列表压缩;返回键加确认对话框或双击退出。报告里要写清楚这些设置放在哪里,不要只在代码里改。排查时在真机或模拟器上点输入框看列表是否还能滚到底,按返回键看是否给出提示。小问题不处理,答辩时就会变成“功能都有但不好用”。

6. 验收与答辩:从可演示到可复现的检查表

验收前我会先冻结一张检查表,按“能打开、能发送、能回复、能异常兜底、能讲清”五段走。打开应用看欢迎语是否出现,列表是否从底部开始;输入空串点发送,确认不追加消息;输入正常文本,确认右侧气泡立刻出现、输入框清空、按钮恢复;断网再发,确认出现可读提示而不是崩溃;连续快速点击发送,确认不重复请求;旋转屏幕或切后台再回来,确认消息不丢、不崩。答辩时先演示正常对话,再演示断网和空输入,最后打开ChatBean、ChatAdapter、RobotActivity三个类,按“数据怎么存、消息怎么显示、请求怎么发”讲一遍,比从头念代码有效。

检查项通过标准常见失分点
欢迎语首次进入可见写死在布局里、无法修改
发送消息右侧气泡即时出现等网络回来才显示
机器人回复左侧气泡显示文本字段取错、空回复崩溃
异常处理断网有提示直接闪退或卡死
代码讲解能说清状态1和2只背方法名

从那以后我每次带课程设计,都会让学生先冻结验收表再写代码,演示前强制走一遍断网、空输入、连续发送三个场景,省得现场靠运气。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询