当前位置: 首页 > news >正文

【Android】答题系统Web服务器APP应用开发流程详解

之前开发出来的答题系统服务器放在PC台式机或者笔记本电脑上存在明显不足,无法24小时持续运行,存在高能耗及设备寿命短等问题,持续高负荷运行不仅耗电量大,还会加速硬件老化,反应迟钝,如何解决呢。

为了更节能和延长设备寿命,笔者思考着更优解决方案

  • 从电子设备的发展趋势显示,微型化设备在功耗和便携性方面具有显著优势;
  • 体积更小的设备通常能耗更低,且便于携带和管理;
  • 安卓系统手机或闲置旧手机可作为理想替代品;
  • 安卓系统这些设备体积小、功耗低,适合长时间运行服务器应用;
  • 闲置手机重新利用起来既环保又经济,能够有效解决原有方案的不足;

文章目录

  • 新建项目
  • 新建页面
  • 实现功能
    • 1. HTTP服务
      • 开始服务
      • 安装模块
      • 后台运行
    • 2. 访问服务
      • 打开浏览器
      • 二维码访问
    • 3. 更新题库
      • 下载题库
      • 题库来源
      • 下载服务器
      • 下载失败问题
  • 运行效果
    • 项目源码

对新手读者来说,手里也有闲置的手机话,对软件开发很感兴趣,但是不知道怎么给其开发安卓应用,这里就展开讲一下,方便新手学习入门。

新手或许有点感觉陌生,接下来说的Android系统也叫安卓系统

开发答题服务器APP,这对设备性能要求较低,普通智能手机即可胜任服务器角色,通过家庭网络局域网实现随时都可访问答题系统。

这里以安卓系统的手机APP开发为例,笔者使用开发工具是Android Studio 版本2024,

若新手想要安装AndroidStudio开发工具,可参考以下文章:

  • 【Android】安装2025版AndroidStudio开发工具开发老安卓旧版App

开发安卓系统的手机APP需要有以下基础

  • 用Java语言开发过任意程序,
  • 有学习过用旧的Eclipse ADT工具开发过安卓app,

新手有此基础,学习使用Android Studio开发工具会顺利上手的

新建项目

打开Android Studio开发工具,选择新建项目New Project时,

选择其中No Activity,这一项,开发的App项目支持在老旧的安卓系统版本上运行,

点击Next下一步,到新建项目的信息填写里,如下图,
图片描述

  • Language - 开发语言,选择最早支持的Java;
  • Minimum SDK - 最低系统版本,选择API 19, 也就是Android 4.4;
  • Build configuration language - 构建工具的语言, 开发工具最早使用的Groovy DSL(build.gradle);

笔者手里有好几个闲置的老旧安卓系统手机,最低安卓系统版本是4.4.4,

为了兼顾到最低的安卓系统版本,所以这里就选择最小Android 4.4,

点击最后Finish完成,

然后观察开发工具的处理进度条,通常在右下角位置,等待它处理完成,

准备就绪,可以开始了,

新建页面

在选择Android的项目结构下,展开java分支,

选中包名(Package name),按鼠标右键选择,新建第一个页面,如下图
图片描述

新建空白的页面按照步骤:NewActivityEmpty Views Activity

出现的窗口,如下图,
图片描述
点击OK确定新建,接下来又是等待进度条完成,

页面自动创建好了,Android项目结构下,会多出来以下两个文件待编辑,分别说明如下

  • MainActivity.java - 页面的代码逻辑文件,这里写代码;
  • activity_main.xml - 页面的布局文件,这里设计页面;

那个页面的布局文件要先做好,系统会自动创建好页面上的所有布局控件供开发者调用,这样能提高开发效率,让开发者专注于实现功能逻辑代码;

修改布局文件activity_main.xml

页面布局做好后,运行效果如下图:
图片描述

知道怎样按上图布局控件吧,布局很简单的,这可是新手必备的基础操作,这里就不贴布局内容了

然后,修改页面逻辑文件MainActivity.java

写页面的逻辑代码,处理初始化,代码如下

public class MainActivity extends AppCompatActivity {//...@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//...tvAddress = findViewById(R.id.textViewServerAddress);btnStart = findViewById(R.id.buttonStartServer);btnStop = findViewById(R.id.buttonStopServer);btnOpen = findViewById(R.id.buttonOpenBrowser);btnStart.setOnClickListener(s->startServer());btnStop.setOnClickListener(s->stopServer());btnOpen.setOnClickListener(s->openBrowser());//...}//...
}

这看起来也很简单吧,页面控件已经初始化好了,这里就是把页面的布局控件对象都获取出来,方便后面调用

还有其它初始化逻辑,不是重要的,例如以前输入了什么内容会保存起来,这里等下次打开App需要读取出来设置到控件中,还有处理授权,防止CPU休眠等等. 有很多需要自己实现,这都是小问题,所以,继续讲那些没必要吧。

实现功能

用户操作的那些主要功能,需要一个个实现,

1. HTTP服务

当点击开始服务按钮时,需要去开启一个服务器,

开始服务

调用的方法是startServer(),实现代码如下

private void startServer(){if (!isServiceBound) {// 绑定服务Intent intent = new Intent(this, WebServerService.class);bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);}// 启动服务(会创建前台通知)Intent serviceIntent = new Intent(this, WebServerService.class);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {startForegroundService(serviceIntent);} else {startService(serviceIntent);}showToast( "服务器开启");}
}

从代码中看出,它是绑定一个服务类WebServerService.class来启用一个HTTP服务,
这服务器是在后台运行的,为了好控制,需要通过页面逻辑来绑定它

需要写一个类文件WebServerService.java,这里面写逻辑,实现服务,代码如下

public class WebServerService extends Service {//...@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {// 启动Web服务器startWebServer();// ...return START_STICKY; // 系统会重启服务}//...
}

从上面代码看出,该类继承了服务类Service,服务类的一些方法可选择哪些实现,

上面其中方法onStartCommand就是前面调用startService(serviceIntent)时就会触发的,

调用方法startWebServer(),代码如下

private void startWebServer() {if (webServer == null) {try {webServer = new AndroidWebServer(SERVER_PORT,this);webServer.start();// 启动成功...} catch (Exception e) {// 启动失败...}}}

从代码中看出,AndroidWebServer类是要自己实现的服务逻辑类文件,传入的第一个参数是端口号,如数字类型8080,

这上面执行了方法start(),它启动了HTTP(web)服务器,

如何实现HTTP服务功能呢,细节过于复杂,不过没关系,利用现有的插件就行(用别人写好的轮子)

这里就借助开发的构建工具,去安装一个模块插件 implementation libs.nanohttpd来使用,里面集成了服务器的功能,如何使用呢;

要写一个服务逻辑文件AndroidWebServer.java,在里面实现如何使用它,

public class AndroidWebServer extends NanoHTTPD {private final Context context;public AndroidWebServer(int port, Context context) {super(port);this.context = context;}@Overridepublic Response serve(IHTTPSession session) {return super.serve(session);}
}

从上面代码看出,继承一个类NanoHTTPD ,实现它的方法serve(session),去处理请求页面路由和返回内容就可

NanoHTTPD关键词是模块插件里的,如果没有安装好这个模块,代码里就会飘红,上面提示报错引用了不存在的类,

安装模块

这里用一个能实现HTTP服务器的模块插件,

如何安装模块插件NanoHTTPD

打开项目下的libs.versions.toml文件,在对应项下面添加它的版本号和模块信息,

[versions]
//...
nanohttpd = "2.3.1"[libraries]
//..
nanohttpd = { module = "org.nanohttpd:nanohttpd", version.ref = "nanohttpd" }

还要在app模块文件build.gradle中添加,内容如下

//...
dependencies {//...implementation libs.nanohttpd
}

添加后,编辑器上方会出现一个提示,可点击Sync Now,执行Gradle同步,开始联网安装,

等模块安装成功后,再看看一个类文件AndroidWebServer.java,会发现里面继承的模块NanoHTTPD类关键词不会飘红了,

接着实现服务器的功能,代码如下,在方法里serve(session)实现

public class AndroidWebServer extends NanoHTTPD {//...@Overridepublic Response serve(IHTTPSession session) {//return super.serve(session);//这里实现对答题题库资源的读取和处理网页请求返回...}
}

处理请求返回的所有资源包括答题文件都放在Android项目里的assets文件夹里面的,

在里面还有个文件夹wwwroot,是存放H5页面的,由以下uniapp项目发布H5生成

  • 答题-答卷系统-小程序-uniapp-项目源码

这就是服务器实现大概过程,上面调用它的方法start()就会开始服务,停止服务就调用它的方法stop(),

启用一个服务器功能,是需要每天24小时运行的,

如果不在后台保持运行,等安卓设备屏幕自动一关,或进入待机模式,那服务器就会停止,访问时突然停止响应,需要你来重新打开,这样很闹心;

后台运行

要想保持后台运行,

需要在上面的一个服务类中继续完善,添加对应权限,这样锁屏后会继续运行,能保持在24小时长期运行,

使用后台服务,就需要往配置文件AndroidManifest.xml里添加allowBackup=true

允许后台运行,内容如下

<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools">...<uses-permission android:name="android.permission.WAKE_LOCK" /><uses-permission android:name="android.permission.FOREGROUND_SERVICE" />...<application...android:allowBackup="true"... /></application>
</manifest>

仅仅上面的保持应用在后台长期运行的条件是不够的,还需要你打开手机系统设置里面的省电策略,还有安全管理下的锁屏清理程序,只要不kill掉本应用就基本没问题了。

2. 访问服务

要想看访问服务器的答题页面查看效果,就需要打开浏览器,

打开浏览器

当点击其中的打开浏览器按钮,就是调用方法openBrowser()

实现代码如下

private void openBrowser(){String url = webServerService.getServerUrl();if (url==null || url.isEmpty()) {showToast( "无法获取网络IP地址");} else {// 打开浏览器访问Web服务器LinkUtils.openUrl(this, url);}}

从上面代码中看出,获取到服务器地址,就调用了LinkUtils类中方法openUrl()打开浏览器,

这个LinkUtils类也需要自己实现,实现不来的话,就看看项目源码吧,

不过,太旧的安卓手机自带的浏览器可能无法正常加载服务器的H5页面,是因为浏览器内核是Chrome的版本太旧,已不能支持执行现代的js脚本

经过笔者测试,旧安卓手机系统版本在Android 4.4 , Android 5.0,Android 6.0上的自带浏览器是无法正常加载H5页面

要能正常访问,可试试安装用新的浏览器打开,

也可以用你自己常用的手机浏览器扫码访问(若还访问不了,笔者可以肯定你仍在用旧手机的浏览器!=.=)

二维码访问

要让另外的手机访问答题服务器的答题页面,需要手动输入IP地址,

输入IP感觉太麻烦吧,就弄个二维码显示,这样用手机扫码就可直接访问了,

当服务器的手机每次连接WIFI热点,其IP可能会变动,弄个二维码就没事;

要使用它,需要安装谷歌的zxing模块插件,

就在app模块文件build.gradle中添加,内容如下

//...
dependencies {//...implementation 'com.google.zxing:core:3.5.2'implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
}

添加后,编辑器上方会出现一个提示,点击Sync Now,执行Gradle同步,开始联网安装,

安装好了,写一个类QRCodeUtils.java文件,

这里面实现如何调用这个模块,代码如下

public class QRCodeUtils {public static Bitmap generateQRCode(String content, int width, int height){try {Hashtable<EncodeHintType, Object> hints = new Hashtable<>();hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);hints.put(EncodeHintType.MARGIN, 1);BitMatrix bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);return createBitmapFromBitMatrix(bitMatrix);} catch (WriterException e) {e.printStackTrace();return null;}}//...
}

然后,在服务器成功开启的事件中添加如下代码

Bitmap qrCode = QRCodeUtils.generateQRCode(serverUrl, 300, 300);
if (qrCode!=null){ImageView iv = findViewById(R.id.imageView);iv.setImageBitmap(qrCode);ll.setVisibility(View.VISIBLE);
}

从上面代码中看出,调用方法generateQRCode()就能生成图片对象,把图片设置到页面的控件ImageView中,

在页面的一排按钮下面,展示二维码图片,

这个二维码可以方便让另外的手机浏览器或微信扫码就能访问

是不是没意思,想象一下:

  • 在学校,模拟考试场,老师让学生用手机扫码进入考试;
  • 在家里,家长要考察孩子的学习成绩,让孩子用手机扫码进入答题;
  • 会告诉你很多…

3. 更新题库

服务器的题库是会经常改动的,

在往后的使用中,我们会从电脑上添加很多资料到题库资源中,

因此需要服务器会更新题库才行,

实现更新题库功能,可以让答题服务器的题库更快的更新,

下载题库

更新题库的过程就是,需要先实现下载压缩文件,然后解压它,

最新的题库是一个压缩包,是从电脑上的桌面程序做出来的,

如何实现呢,先实现一个请求服务器下载一个文件,

然后实现解压文件,放到App的缓存文件目录中,

把原来的题库文件都清空了,再放新的题库在里面,

让服务器从这个题库目录中读取文件,用来响应页面的请求返回内容,

按照这个步骤实现,要做的细节很多,这里就不讲,自己能实现吧,

剩下的一些代码还有很多,这里不详细讲了,完整代码可以看文章提供相关项目源码,

题库来源

有个电脑桌面程序就是用来做题库文件的,在之前的文章就讲过,可以看这篇文章

  • WPF-答题系统题库编辑工具桌面程序开发项目流程详解

桌面程序的菜单下有个题库选项,展开后有个生成压缩文件可点击,如下图
图片描述
可以将自己做好的题库打包,生成的压缩文件就是打包好的题库资源文件,

若需要桌面程序,可自己做一个来用,参考以下源码

  • WPF-桌面程序-答题系统-题库编辑器 项目源码

也可以用作者已建好的题库资源,就放在GitCode代码托管平台上

  • resource_mp_answer

题库就是一个ZIP压缩文件

下载服务器

把压缩文件放到电脑的下载服务器上,可以用电脑自带的IIS服务器来下载文件,需要自己开启,

怎么开启呢,从电脑上搜索IIS即可找到,如下图
图片描述

找不到IIS?电脑系统需要安装IIS,参考 Windows 11 IIS服务器安装与配置

打开后,选择启用默认的网站Default Web Site,如下图
图片描述

点击图中右侧的浏览,会打开一个文件夹,

然后把那个题库压缩包文件放在电脑启用的IIS服务器上的默认wwwroot文件夹里,如下图
图片描述
只要电脑在服务器的局域网内,就可访问到电脑的IP地址,地址格式像这样:

http://[IP地址]:80/resource_mp_answer.zip

不会查看自己的电脑IP地址?参考 查看电脑的局域网IP地址,

在安卓APP的答题服务器里粘贴输入这个地址,就能访问下载更新题库,

下载失败问题

都弄好了,运行看效果吧,可能会遇到以下问题,看如何解决,

当点击更新题库按钮,请求访问下载时,可能报错如下:

ClearText HTTP traffic to IP地址 not permitted

原因是联网安全导致的,原http请求需要改成https请求,

由于是在家庭网络局域网内访问,这里用不到https(这会降低设备性能,执行加解密过程,会消耗计算资源),

使用http请求,就需要往配置文件AndroidManifest.xml里添加usesCleartextTraffic=true

解除安全限制,内容如下

<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools">...<application...android:usesCleartextTraffic="true"... /></application>
</manifest>

如果不是上面的问题,那可能是你填入的下载地址有问题吧,再仔细看看,笔者当时没遇到还有别的问题呢。

解决后,重新编译运行看看

运行效果

这里笔者做出来后,运行在真机上,效果图如下
图片描述

点击开启服务按钮后,就是上面的效果截图,同时展示了二维码方便另外的手机扫码访问,该有的功能都有;

需要注意:

  • 不要运行在开发工具自带的模拟器上,因为IP地址不在一个局域网段上,电脑与模拟器的服务器无法互相访问,
  • 扫码访问需要那些手机设备和服务器同在一个局域网段哦,也就是一起连接一个WIFI路由器(WIFI热点),

写到这里,知道笔者做出来这个是为了什么吧,打造良好的学习工具可以帮助整理思路、节省时间,并提高理解深度。无论是打造什么学习工具软件,正确的选择都能让学习事半功倍。

闲置手机弃之可惜,留着自用,对开发者而言仍具实用价值。通过合理配置,闲置设备可转化为持续运行的答题服务器,为家庭成员提供便捷的学习与复习工具,实现随时访问的教育资源。

为了不让它运行到没电而自动关机,需要插上充电器,

长期插上充电器不会有事吧,担心忘记取下来,有担心一会没电,这是可以解决的:

  • 需要将充电器连接至智能(定时)插座,设定充电时间段,定时插座按预设时间自动开启充电,到达截止时间后自动断电,形成循环充电模式;
  • 定时断电机制防止手机长时间处于满电状态,减少电池损耗,循环充电模式维持电量在合理区间,避免过充或深度放电,延长电池寿命;
  • 自动开关功能无需手动操作,节省电力消耗,通过固定时间段的充放电循环,确保设备随时可用,同时减少用户频繁管理的麻烦;

项目源码

  • 相关项目源码 点此查看,
  • 更多的源码 点此查看,
  • 更多的资源 点此查看,

如果下载了项目源码,会发现一个问题,也是笔者后来发现的,需要改一下项目源码,

问题是在:

点击更新题库后,提示更新成功,实际上访问答题页面时发现没有更新,

调查出结果:

题库更新没问题,是修改更新前端H5项目源码时写错了,需要改下,

解决步骤:

在项目中找到这个文件index-BEnBRmpL.js,这个是编译后的源码文件,

其位置在项目文件夹的 app/src/main/assets/wwwroot/assets/目录下,

在开发工具上打开这个文件,按Ctrl+F快捷键,调出查找工具,输入查找文本内容如下

/api/version`,fail:t,success:t=>{console.log("ready  res",t),"string"==typeof t.data&&/^\d+(\.\d+)+?$/.test(t.data)?e({url:`${location.origin}/api`

编译后的源码文件如果看不懂,不用管它,那代码是压缩了,把多条语句写到一行上,把文件变小了

在第二个输入框输入替换内容,将其替换为

/assets/index-BEnBRmpL.js`,fail:t,success:t=>{"string"==typeof t.data?e({url:`${location.origin}/resource_mp_answer`

输入后,点击查找,找到后如下图:
图片描述
然后,点击 Replace 按钮,替换完成,

保存修改的文件后,重新编译运行试试看问题是否解决了。
图片描述


文章转载自:

http://RLNd4mW2.nqdkx.cn
http://ZoBml3Hx.nqdkx.cn
http://kjdB3hwc.nqdkx.cn
http://jGbGveuX.nqdkx.cn
http://gLuxdbx2.nqdkx.cn
http://WzeIDgKG.nqdkx.cn
http://5aELTT3I.nqdkx.cn
http://8lDkxeOA.nqdkx.cn
http://fJGucPkD.nqdkx.cn
http://2aeaTcwa.nqdkx.cn
http://trrlmcEs.nqdkx.cn
http://S2fDzZND.nqdkx.cn
http://s1vlPuea.nqdkx.cn
http://VFXcAkIT.nqdkx.cn
http://SPsx9Gzh.nqdkx.cn
http://3HDAgPZH.nqdkx.cn
http://JRAhm8bP.nqdkx.cn
http://YMDwllkD.nqdkx.cn
http://j83s3sY5.nqdkx.cn
http://IiMTetGh.nqdkx.cn
http://yOV5Ljpp.nqdkx.cn
http://XbYZBsW0.nqdkx.cn
http://gr26IRbj.nqdkx.cn
http://EWobe1BG.nqdkx.cn
http://GVRJC6MF.nqdkx.cn
http://qO0orkQo.nqdkx.cn
http://3CPPAguI.nqdkx.cn
http://RdIS39uf.nqdkx.cn
http://yYkLu8jJ.nqdkx.cn
http://3NVudsP3.nqdkx.cn
http://www.dtcms.com/a/383432.html

相关文章:

  • Web服务器VS应用服务器:核心差异解析
  • 分享一个vue2的tinymce配置
  • spring bean一共有几种作用域
  • Redie详细入门教程2
  • Maven入门_简介、安装与配置
  • Vue组件化开发介绍
  • ​new species of flying reptile1 discovered in Scotland​
  • Spring JDBC与KingbaseES深度集成:构建高性能国产数据库应用实战
  • 闪电科创 SCI专业辅导
  • 【数据结构与算法】图 Floyd算法
  • 代码随想录算法训练营第十一天--二叉树2 || 226.翻转二叉树 / 101.对称二叉树 / 104.二叉树的最大深度 / 111.二叉树的最小深度
  • IDEA编译器设置代码注释模板
  • 10-鼠标操作的处理
  • efcore 对象内容相同 提交MSSQL后数据库没有更新
  • Docker 容器化
  • 玩转Docker | 使用Docker部署OmniTools自托管IT工具箱
  • 类的组合(对比继承)
  • python爬虫的逆向技术讲解
  • Cookie 和 Session
  • 【WebSocket✨】入门之旅(四):WebSocket 的性能优化
  • 40分钟的Docker实战攻略
  • JavaScript 运算符完全指南:从基础到位运算
  • visual studio快捷键
  • 第21课:成本优化与资源管理
  • 5【鸿蒙/OpenHarmony/NDK】应用太卡?用 Node-API 异步任务解决:从卡顿根源到流畅方案
  • 利用OpenCV进行对答题卡上的答案进行识别的案例
  • 如何用 Rust 实现的基础屏幕录制程序?
  • 认知语义学隐喻理论对人工智能自然语言处理中深层语义分析的赋能与挑战
  • 常见索引失效场景及原因分析(含示例)
  • 嵌入式Linux常用命令