1. 项目概述与设计思路
这个单机版购物商城项目采用纯本地化设计思路,完全基于SQLite数据库实现所有业务逻辑。作为一个不依赖网络连接的安卓应用,它完整实现了电商核心功能链路:从用户注册登录、商品浏览、购物车管理到订单支付的全流程闭环。
项目最值得关注的设计特点是其"轻量级全本地化"架构。所有数据操作都通过原生SQLite API实现,没有引入任何ORM框架。这种设计虽然牺牲了部分开发便利性,但带来了两个显著优势:一是APK体积控制在5MB以内;二是所有数据库操作对开发者完全透明,非常适合作为SQLite实战教学案例。
核心数据模型设计遵循"够用就好"原则。用户表(user)直接包含余额字段(balance),这种反范式设计在单机环境下反而简化了充值、支付逻辑。购物车(cart)采用经典的"用户-商品"关联模型,而订单表(orders)则创新性地使用JSON字符串存储商品快照,这种取舍在移动端单机应用中是非常务实的解决方案。
2. 核心模块实现解析
2.1 用户系统实现
用户模块采用经典的CRUD结构,通过UserDBHelper类封装所有数据库操作。注册逻辑特别值得注意:
java复制public boolean register(String user, String pwd) {
SQLiteDatabase db = getWritableDatabase();
ContentValues values = new ContentValues();
values.put("username", user);
values.put("password", pwd);
values.put("balance", 0.0); // 初始化余额为0
long result = db.insert("user", null, values);
return result != -1;
}
几个关键设计细节:
- 密码明文存储:实际项目中必须加密,但教学示例简化了该流程
- 余额初始化为0.0:使用real类型而非integer,方便后续金额计算
- 返回值处理:insert()返回-1表示失败,否则返回新插入行的ID
登录验证采用直接查询比对方式:
java复制public boolean login(String user, String pwd) {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.query("user", new String[]{"password"},
"username=?", new String[]{user}, null, null, null);
if (cursor.moveToFirst()) {
String storedPwd = cursor.getString(0);
return storedPwd.equals(pwd);
}
return false;
}
注意:真实项目中必须使用密码加盐哈希存储,且查询应防止SQL注入
2.2 购物车与商品系统
购物车实现采用"用户-商品"多对多关系模型,核心表结构:
sql复制CREATE TABLE cart (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
goods_id INTEGER,
count INTEGER,
UNIQUE(user_id, goods_id) ON CONFLICT REPLACE
)
添加购物车的冲突处理策略很有特色:
java复制db.insertWithOnConflict("cart", null, values, SQLiteDatabase.CONFLICT_REPLACE);
这种设计实现了:
- 同一商品重复添加时自动更新数量
- 通过UNIQUE约束保证数据一致性
- 使用CONFLICT_REPLACE策略简化业务逻辑
商品展示采用RecyclerView标准实现,但视图绑定方式值得学习:
java复制@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Goods item = goodsList.get(position);
holder.name.setText(item.getName());
holder.price.setText(String.format("¥%.2f", item.getPrice()));
// 图片加载使用本地资源ID
holder.image.setImageResource(getImageResId(item.getImageName()));
holder.itemView.setOnClickListener(v -> {
Intent intent = new Intent(context, GoodsDetailActivity.class);
intent.putExtra("goods_id", item.getId());
context.startActivity(intent);
});
}
2.3 订单与支付系统
订单表设计采用"非规范化"策略,将商品信息以JSON形式持久化:
sql复制CREATE TABLE orders (
order_id TEXT PRIMARY KEY,
user_id INTEGER,
goods_info TEXT, -- JSON格式商品快照
total_price REAL,
create_time TEXT,
status INTEGER DEFAULT 0
)
订单生成逻辑包含关键业务验证:
java复制public boolean createOrder(List<CartItem> cartItems) {
double total = calculateTotal(cartItems);
if (currentUser.getBalance() < total) {
return false; // 余额不足
}
String orderId = generateOrderId();
String goodsJson = convertToJson(cartItems);
SQLiteDatabase db = getWritableDatabase();
ContentValues values = new ContentValues();
values.put("order_id", orderId);
values.put("user_id", currentUserId);
values.put("goods_info", goodsJson);
values.put("total_price", total);
values.put("create_time", getCurrentTime());
return db.insert("orders", null, values) != -1;
}
支付流程实际上是两步操作:扣款+创建订单,需要保证原子性:
java复制public boolean processPayment(List<CartItem> items) {
SQLiteDatabase db = getWritableDatabase();
try {
db.beginTransaction();
double total = calculateTotal(items);
if (!updateBalance(db, currentUserId, -total)) {
return false;
}
if (!createOrder(db, items)) {
db.endTransaction();
return false;
}
clearCart(db, currentUserId);
db.setTransactionSuccessful();
return true;
} finally {
db.endTransaction();
}
}
3. 高级功能实现细节
3.1 讨论区实现方案
讨论功能采用简约设计,核心表结构仅包含:
sql复制CREATE TABLE comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
content TEXT,
create_time TEXT
)
界面实现采用RecyclerView + EditText组合,刷新逻辑值得参考:
java复制private void refreshComments() {
List<Comment> comments = dbHelper.getComments(lastGoodsId);
adapter.setData(comments);
recyclerView.scrollToPosition(0);
// 智能滚动处理
if (comments.size() > 5) {
recyclerView.postDelayed(() ->
recyclerView.smoothScrollToPosition(0), 300);
}
}
时间显示优化技巧:
java复制public static String formatCommentTime(String dbTime) {
SimpleDateFormat dbFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date date = dbFormat.parse(dbTime);
if (System.currentTimeMillis() - date.getTime() < 86400000) {
return new SimpleDateFormat("HH:mm").format(date);
}
return new SimpleDateFormat("MM-dd").format(date);
} catch (Exception e) {
return dbTime;
}
}
3.2 数据库升级策略
当前项目的简单粗暴方案:
java复制@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS user");
onCreate(db);
}
生产环境推荐采用渐进式迁移:
java复制@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
for (int v = oldVersion; v < newVersion; v++) {
switch (v) {
case 1:
upgradeToV2(db);
break;
case 2:
upgradeToV3(db);
break;
}
}
}
private void upgradeToV2(SQLiteDatabase db) {
// 示例:添加用户头像字段
db.execSQL("ALTER TABLE user ADD COLUMN avatar TEXT");
}
4. 性能优化与改进建议
4.1 主线程数据库操作问题
原始代码在主线程直接操作数据库的风险:
- 复杂查询导致UI卡顿
- 插入大量数据时ANR风险
改进方案一:AsyncTask封装
java复制private class QueryTask extends AsyncTask<Void, Void, Cursor> {
protected Cursor doInBackground(Void... voids) {
return dbHelper.getReadableDatabase()
.query("goods", null, null, null, null, null, null);
}
protected void onPostExecute(Cursor cursor) {
adapter.swapCursor(cursor);
}
}
改进方案二:Room + LiveData
java复制@Dao
interface GoodsDao {
@Query("SELECT * FROM goods")
LiveData<List<Goods>> getAll();
}
// 在ViewModel中观察
goodsDao.getAll().observe(this, goods -> {
adapter.submitList(goods);
});
4.2 数据缓存策略优化
实现内存缓存层:
java复制private LruCache<String, Bitmap> memoryCache;
void initCache() {
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
memoryCache = new LruCache<String, Bitmap>(cacheSize) {
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount() / 1024;
}
};
}
4.3 支付流程增强
建议实现的改进点:
- 支付状态机设计
java复制enum PayState {
INIT,
PROCESSING,
SUCCESS,
FAILED
}
- 支付结果回调机制
java复制interface PaymentCallback {
void onSuccess(Order order);
void onFailed(int errorCode);
void onProcessing();
}
- 本地收据生成
java复制public void generateReceipt(Order order) {
String content = String.format(Locale.CHINA,
"订单号:%s\n时间:%s\n金额:¥%.2f\n商品:%s",
order.getOrderId(),
order.getCreateTime(),
order.getTotalPrice(),
formatGoodsList(order.getGoodsInfo()));
File receipt = new File(getReceiptsDir(), order.getOrderId() + ".txt");
try (FileWriter writer = new FileWriter(receipt)) {
writer.write(content);
}
}
5. 项目扩展方向
5.1 多语言支持方案
实现步骤:
- 创建values-zh/strings.xml
- 动态切换Locale
java复制public static void changeLanguage(Context context, String language) {
Resources res = context.getResources();
Configuration conf = res.getConfiguration();
conf.setLocale(new Locale(language));
res.updateConfiguration(conf, res.getDisplayMetrics());
}
5.2 数据导出功能
实现SQLite数据库导出:
java复制public void exportDatabase(Context context) {
File dbFile = context.getDatabasePath("shop.db");
File exportDir = new File(Environment.getExternalStorageDirectory(), "shop_backup");
if (!exportDir.exists()) exportDir.mkdirs();
File backup = new File(exportDir, "shop_" + System.currentTimeMillis() + ".db");
try (InputStream in = new FileInputStream(dbFile);
OutputStream out = new FileOutputStream(backup)) {
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
}
}
5.3 界面主题定制
实现夜间模式:
- 定义夜间主题res/values-night/styles.xml
- 动态切换
java复制AppCompatDelegate.setDefaultNightMode(
isNight ? MODE_NIGHT_YES : MODE_NIGHT_NO);
6. 项目实战经验总结
在实际开发这类单机购物应用时,有几个关键点需要特别注意:
- 数据一致性保障
- 使用事务处理关键操作(如支付流程)
- 合理设置数据库约束(UNIQUE、NOT NULL等)
- 考虑实现简单的WAL日志机制
- 性能优化技巧
- 为常用查询添加索引
sql复制CREATE INDEX idx_user_balance ON user(balance);
- 分页加载商品数据
java复制public List<Goods> getGoodsByPage(int page, int pageSize) {
String limit = pageSize + " OFFSET " + (page - 1) * pageSize;
return db.query("goods", null, null, null, null, null, null, limit);
}
- 异常处理经验
- 数据库操作必须try-catch
- 文件操作检查外部存储状态
java复制if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
// 处理SD卡不可用情况
}
- 合理处理并发修改冲突
这个项目虽然规模不大,但完整展示了单机安卓应用的数据持久化方案设计。对于初学者而言,最值得借鉴的是其"简单但完整"的设计哲学——每个技术选型都紧紧围绕单机环境的特点做取舍,没有过度设计。