目录
一、测试环境说明
二、项目简介
基于Android的公交线路查询系统旨在为乘客提供一种快速、便捷、准确的公交车路线查询方式。
通过本系统,乘客可以随时随地利用移动设备查询所需的公交车路线信息,从而提高市民的出行效率,促进城市公共交通事业的可持续发展,并进一步完善公交系统的智能化服务。
本系统采用Android平台作为开发基础,结合Java语言和SQLite数据库技术,实现了轻量级、高效的数据存储与查询功能。
系统的主要功能包括用户注册登录、公交线路查询、站点查询等并且项目带有详细的中文注释。
三、项目演示
网络资源模板--基于Android studio 公交线路APP
四、部设计详情(部分)
登录页面
package com.my.myapplication.ui;
import com.my.myapplication.R;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.my.myapplication.bean.UserBean;
import com.my.myapplication.helper.MyHelper;
import com.my.myapplication.utils.SpUtils;
//登录页
public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
//是否记住账号 SharedPreferences存储用的key
private final String key_is_remember = "key_is_remember";
// 存储账号 SharedPreferences用的key
private final String key_account = "ke_account";
//存储密码 SharedPreferences用的key
private final String key_password = "key_password";
//账号
private EditText mEtName;
private EditText mEtPassword;
//密码
private Button mBtLogin;
//记住密码
private CheckBox mCkRemember;
//注册按钮
private TextView mTvRegister;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置全屏
requestWindowFeature(Window.FEATURE_NO_TITLE);
if (Build.VERSION.SDK_INT >= 21) {
Window window = getWindow();
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
window.setStatusBarColor(Color.TRANSPARENT);
//
}
setContentView(R.layout.activity_login);
initView();
}
private void initView() {
//查找控件
mEtName = findViewById(R.id.et_name);
mEtPassword = findViewById(R.id.et_password);
mBtLogin = findViewById(R.id.bt_login);
mCkRemember = findViewById(R.id.ck_remember);
//获取是否记账密码的状态
boolean is_remember = SpUtils.getInstance(this).getBoolean(key_is_remember, false);
//如果记住密码 则从SharedPreferences存储中获取 账号密码 并赋值给对应的控件
if (is_remember) {
mCkRemember.setChecked(true);
mEtName.setText(SpUtils.getInstance(this).getString(key_account));
mEtPassword.setText(SpUtils.getInstance(this).getString(key_password));
}
//绑定 登录注册按钮点击事件
mBtLogin.setOnClickListener(this);
mTvRegister = findViewById(R.id.tv_register);
mTvRegister.setOnClickListener(this);
}
//登录注册按钮点击事件处理
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.bt_login:
login();
break;
case R.id.tv_register:
// 点注册按钮跳注册页面
startActivity(new Intent(this, RegisterActivity.class));
break;
}
}
//登录逻辑处理
private void login() {
//获取账号密码
String userName = mEtName.getText().toString().trim();
String passWord = mEtPassword.getText().toString().trim();
//判断账号密码是否为空 如果是则提示用户 并返回 不在继续执行后续代码
if (TextUtils.isEmpty(userName)) {
Toast.makeText(this, "账号必须填写", Toast.LENGTH_SHORT).show();
return;
}
if (TextUtils.isEmpty(passWord)) {
Toast.makeText(this, "密码必须填写", Toast.LENGTH_SHORT).show();
return;
}
//判断记住密码按钮是否是被勾选 如果是 则把记住密码的状态 账号 密码 写入SharedPreferences中
if (mCkRemember.isChecked()) {
SpUtils.getInstance(this).putBoolean(key_is_remember, true);
SpUtils.getInstance(this).putString(key_account, userName);
SpUtils.getInstance(this).putString(key_password, passWord);
} else {
//没有勾选记住密码 清空SharedPreferences保存的 记住账号密码状态 用户名 和密码
SpUtils.getInstance(this).putBoolean(key_is_remember, false);
SpUtils.getInstance(this).putString(key_account, "");
SpUtils.getInstance(this).putString(key_password, "");
}
//开个线程进行登录 因为耗时操作不能再ui线程中操作 否则会引起ui界面卡顿
new Thread(new Runnable() {
@SuppressLint("WrongConstant")
@Override
public void run() {
try {
// 这里是调用耗时操作方法 调用数据库方法 判断账号密码是否正确
UserBean user = MyHelper.getInstance(LoginActivity.this).matchAccount(userName, passWord);
//返回不为空 则说明查到用户 账号密码正则
if (user != null) {
//再ui线程中更新ui 因为安卓不能子子线程中更新ui
runOnUiThread(new Runnable() {
@Override
public void run() {
//登录成功 跳主页面 并销毁登录页面
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finish();
}
});
} else {
runOnUiThread(new Runnable() {
@Override
public void run() {
//登录失败 提示用户
Toast.makeText(LoginActivity.this, "登录失败", Toast.LENGTH_SHORT).show();
}
});
}
} catch (Exception e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this, "未知错误", Toast.LENGTH_SHORT).show();
}
});
}
}
}).start();
}
}
1. 界面与功能概述
主要实现用户登录功能。界面包含账号输入框、密码输入框、登录按钮、记住密码复选框和注册跳转链接。
页面采用沉浸式状态栏设计,整体风格简洁。核心功能包括:验证用户输入、记住密码、登录跳转和注册跳转。
2. 核心逻辑处理
- 输入验证:检查账号密码是否为空,若为空则弹出Toast提示用户。
- 记住密码:通过SharedPreferences持久化存储用户账号密码,勾选后下次自动填充。
- 登录验证:通过子线程异步查询数据库(MyHelper类),匹配账号密码正确性,成功则跳转主页面,失败提示错误。
- 线程安全:耗时的数据库操作在子线程执行,通过`runOnUiThread`安全更新UI。
3. 数据存储机制
使用SpUtils工具类管理SP存储,保存三种数据:
- 是否记住密码的布尔值(`key_is_remember`)
- 账号字符串(`key_account`)
- 密码字符串(`key_password`)
用户取消勾选时会清空存储的密码信息。
4. 交互与跳转设计
- 登录成功直接进入MainActivity并销毁当前页面。
- 注册按钮跳转至RegisterActivity。
- 错误处理涵盖空输入、账号密码不匹配及未知异常,均通过Toast友好提示。整体流程符合典型登录场景需求,兼顾用户体验与数据安全性。
注册页面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp"
android:layout_marginTop="50dp"
android:layout_marginRight="30dp"
android:orientation="vertical">
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="15dp"
android:background="@null"
android:hint="请输入账号"
android:paddingLeft="10dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:textSize="19sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#cccccc" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp"
android:layout_marginTop="10dp"
android:layout_marginRight="30dp"
android:layout_marginBottom="10dp"
android:orientation="vertical">
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="15dp"
android:background="@null"
android:hint="请输入密码"
android:inputType="textPassword"
android:paddingLeft="10dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:textSize="19sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#cccccc" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:orientation="vertical">
<EditText
android:id="@+id/et_password_again"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="15dp"
android:background="@null"
android:hint="请再次输入密码"
android:inputType="textPassword"
android:paddingLeft="10dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:textSize="19sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#cccccc" />
</LinearLayout>
<Button
android:id="@+id/bt_register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="30dp"
android:layout_marginTop="20dp"
android:layout_marginRight="30dp"
android:background="@drawable/bt_selector"
android:gravity="center"
android:paddingLeft="60dp"
android:paddingTop="15dp"
android:paddingRight="60dp"
android:paddingBottom="15dp"
android:text="注册"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
1. 界面与功能概述
主要实现用户注册功能。界面包含账号输入框、密码输入框、确认密码输入框和注册按钮。
顶部ActionBar显示标题和返回箭头,整体设计简洁。
核心功能包括:输入验证、账号查重、密码一致性检查、数据写入数据库及注册结果反馈。
2. 核心逻辑处理
- 输入验证:检查账号、密码及确认密码是否为空,若为空则弹出Toast提示并终止流程。
- 密码一致性:比对两次输入的密码,不一致时立即提示用户。
- 账号查重:通过子线程查询数据库(MyHelper类),若账号已存在则提示"注册失败"。
- 数据写入:创建UserBean对象并调用数据库插入方法,根据返回值判断注册成功与否,成功则自动关闭页面。
3. 交互与导航设计
- 返回逻辑:ActionBar的返回箭头点击事件直接销毁当前Activity,符合用户预期。
- 异步处理:数据库操作在子线程执行,通过`runOnUiThread`安全更新UI,避免主线程卡顿。
- 结果反馈:所有操作结果(成功/失败/错误)均通过Toast即时提示,用户体验友好。
4. 安全与健壮性
- 密码一致性检查在客户端完成,减少无效服务器请求。
- 异常捕获全面,包括数据库操作异常和未知错误,均会提示用户。
- 线程管理规范,耗时操作与UI更新严格分离,避免内存泄漏风险。整体流程覆盖典型注册场景,兼顾功能完整性和操作安全性。
首页
package com.my.myapplication.ui;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.my.myapplication.adapter.MyAdapter;
import com.my.myapplication.bean.BusBean;
import com.my.myapplication.helper.MyHelper;
import java.util.ArrayList;
import java.util.List;import com.my.myapplication.R;
//主界面
public class MainActivity extends AppCompatActivity {
//recyclerview 适配器
private MyAdapter mMyAdapter;
//recyclerview 数据
private List<BusBean> mBeanList = new ArrayList<>();
//浮动按钮 添加按钮
private FloatingActionButton mFl1;
//RecyclerView
private RecyclerView mRecyclerview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 设置标题
getSupportActionBar().setTitle(getResources().getString(R.string.app_name));
//查找Recyclerview控件
mRecyclerview = findViewById(R.id.recyclerview);
//绑定Recyclerview适配器
mMyAdapter = new MyAdapter(MainActivity.this, mBeanList);
//设置Recyclerview 布局管理器
mRecyclerview.setLayoutManager(new LinearLayoutManager(MainActivity.this));
// mRecyclerView.setLayoutManager(new GridLayoutManager(this,3));
//设置Recyclerview适配器
mRecyclerview.setAdapter(mMyAdapter);
mFl1 = findViewById(R.id.fl_1);
//添加按钮点击处理事件
mFl1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//跳转添加编辑界面 添加编辑公用一个界面 用intent携带的type数据区分
Intent intent = new Intent(MainActivity.this, AddAndEditActivity.class);
intent.putExtra("type", "add");
startActivity(intent);
}
});
}
//界面重新可见时候调用 添加数据返回界面 从新刷新界面数据
@Override
public void onResume() {
super.onResume();
//开线程
new Thread(new Runnable() {
@SuppressLint("WrongConstant")
@Override
public void run() {
try {
// 这里是调用耗时操作方法 获取该分类数据
mBeanList = MyHelper.getInstance(MainActivity.this).getAllData();
runOnUiThread(new Runnable() {
@Override
public void run() {
//更新recyclerview 显示数据
mMyAdapter.upDate(mBeanList);
}
});
} catch (Exception e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "未知错误", Toast.LENGTH_SHORT).show();
}
});
}
}
}).start();
}
//创建菜单
@Override
public boolean onCreateOptionsMenu(Menu menu) {
//将菜单资源加载到当前的菜单资源
getMenuInflater().inflate(R.menu.main1, menu);
return true;
}
//菜单栏 点击事件
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// 搜索按钮
if (item.getItemId() == R.id.id1) {
Intent intent = new Intent(MainActivity.this, SearchActivity.class);
startActivity(intent);
}
return true;
}
}
1. 界面与功能概述
采用RecyclerView展示数据列表(BusBean对象),顶部ActionBar显示应用名称和搜索菜单。
核心功能包括:数据列表展示、浮动按钮跳转新增页面、菜单栏搜索跳转、数据自动刷新。
界面设计遵循Material Design规范,包含FAB(浮动操作按钮)和RecyclerView的线性布局。
2. 数据加载与展示机制
- 异步数据加载:在onResume生命周期中启动子线程,通过MyHelper从数据库获取全部数据(getAllData()),避免主线程卡顿。
- 列表动态更新:使用自定义Adapter(MyAdapter)绑定数据,通过upDate()方法实现数据变化时的UI刷新。
- 线程安全控制:数据库操作在子线程完成,结果通过runOnUiThread回调更新RecyclerView,确保线程安全。
3. 交互与导航设计
- FAB功能:点击浮动按钮跳转AddAndEditActivity,并通过Intent传递"type=add"标识新增操作。
- 菜单功能:右上角菜单包含搜索项(R.id.id1),点击跳转SearchActivity实现搜索功能。
- 自动刷新:页面重新可见时(onResume)自动重载数据,保证数据时效性。
4. 异常处理与扩展性
- 对数据库操作进行try-catch捕获,异常时Toast提示"未知错误"。
- RecyclerView支持切换布局管理器(注释中保留GridLayoutManager示例),便于后期扩展多样式布局。
- 菜单资源(R.menu.main1)独立配置,方便后续功能迭代。整体架构清晰,兼顾功能完整性和可维护性。
新增页面
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="*"
android:textColor="#ff0000" />
<TextView
android:id="@+id/tv_qibujia"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="起步价"
/>
android:inputType="number"
android:background="@drawable/llshap2"
android:gravity="top|left"
android:hint="起步价(元)"
android:padding="10dp"
android:paddingTop="12dp"
android:paddingBottom="12dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_zuigaojia"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="*"
android:textColor="#ff0000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="最高价"
/>
<EditText
android:id="@+id/et_zuigaojia"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:inputType="number"
android:background="@drawable/llshap2"
android:gravity="top|left"
android:hint="最高价(元)"
android:padding="10dp"
android:paddingTop="12dp"
android:paddingBottom="12dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="*"
android:textColor="#ff0000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="运营时间:"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
>
android:id="@+id/tv_xuanzemoban"
android:layout_width="wrap_content"
android:gravity="center"
android:layout_height="wrap_content"
android:text="选择末班时间"
android:background="@drawable/bt_selector"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:textColor="@color/white"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="*"
android:textColor="#ff0000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发车间隔"
/>
<EditText
android:id="@+id/et_jiange"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/llshap2"
android:gravity="top|left"
android:hint="如10-15分钟"
android:padding="10dp"
android:paddingTop="12dp"
android:paddingBottom="12dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="*"
android:textColor="#ff0000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="所属城市:" />
<EditText
android:id="@+id/et_chengshi"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/llshap2"
android:gravity="top|left"
android:hint="如广东省深圳市"
android:padding="10dp"
android:paddingTop="12dp"
android:paddingBottom="12dp" />
</LinearLayout>
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
android:text="运营状态:"/>
<RadioButton
android:id="@+id/rb_zhengchang"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="正常"/>
<RadioButton
android:id="@+id/rb_tingyun"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="停运"/>
<RadioButton
android:id="@+id/rb_shigong"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="道路施工临时更改线路"/>
</RadioGroup>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
>
<TextView
android:id="@+id/tv_addtext"
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center"
android:layout_height="wrap_content"
android:text="添加站点"
android:background="@drawable/bt_selector"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:textColor="@color/white"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="*"
android:textColor="#ff0000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="站点内容(最少2站)" />
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_weight="1"
android:descendantFocusability="blocksDescendants"
>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
</LinearLayout>
<Button
android:id="@+id/bt_write"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginBottom="10dp"
android:background="@drawable/bt_selector"
android:gravity="center"
android:paddingLeft="60dp"
android:paddingTop="15dp"
android:paddingRight="60dp"
android:paddingBottom="15dp"
android:text="保存"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
</ScrollView>
1. 核心功能定位
该Activity主要实现公交线路信息的新增功能,属于数据录入模块的核心界面。
通过表单收集线路名称、票价模式、运营时间等关键信息,并支持动态添加公交站点。
采用"统一票价"和"分段收费"两种模式,根据用户选择动态调整表单字段,提供时间选择器等便捷输入方式。
2. 交互设计特点
- 动态表单:通过单选按钮切换票价模式,实时显示/隐藏"最高票价"字段,并智能调整标签文字("起步价"或"全程票价")。
- 时间选择:集成系统原生时间选择器,对首末班车时间进行格式化处理(自动补零),提升数据规范性。
- 站点管理:采用自定义弹窗收集站点信息,通过RecyclerView实现站点列表的动态增删,强制校验至少需要2个站点。
3. 数据校验机制
提交时执行严格的多维度校验:
- 必填字段非空检查(线路名称、票价等)
- 票价模式差异化校验(分段收费需检查最高票价)
- 业务规则校验(站点数量不得少于2个)
所有校验失败均通过Toast即时反馈,防止无效数据提交。
4. 技术实现亮点
- 采用异步线程处理数据库插入操作,主线程仅负责UI更新,保证界面流畅性。
- 使用事务型操作:先插入主表数据获取ID,再批量插入关联的站点数据,确保数据完整性。
- 通过自定义Dialog实现站点输入,结合Adapter实时更新列表,形成完整的数据采集闭环。整体设计兼顾用户体验与数据可靠性。
五、项目源码
👇👇👇👇👇快捷方式👇👇👇👇👇