You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Room数据库备份恢复按钮无响应问题求助

Room数据库备份恢复功能无响应问题排查与解决

问题描述

我正在Android Studio中开发一个基于Room的数据库备份恢复程序,通过RecyclerView展示用户列表,核心需求是实现数据库备份到外部存储、从外部存储恢复的功能,但点击工具栏的备份/恢复按钮后完全没有反应,没有Toast提示,也没有文件生成或恢复动作。

现有代码

主Activity代码

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.example.innbucksbackuprestoreroomdb.database.AppDatabase;
import com.example.innbucksbackuprestoreroomdb.database.User;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.List;

public class BackupRestore extends AppCompatActivity {
    private UserListAdapter userListAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.backup_restore);

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        initRecyclerView();

        loadUserList();
    }

    private void initRecyclerView(){
        RecyclerView recyclerView = findViewById(R.id.recyclerview);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
        recyclerView.addItemDecoration(dividerItemDecoration);

        userListAdapter = new UserListAdapter(this);
        recyclerView.setAdapter(userListAdapter);
    }

    private void loadUserList(){
        AppDatabase database = AppDatabase.getDbInstance(this.getApplicationContext());
        List<User> userList = database.userDao().getallusers();
        userListAdapter.setUserList(userList);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if (resultCode == 100){
            loadUserList();
        }

        super.onActivityResult(requestCode, resultCode, data);
    }

    public static void importDB(Context context) {
        try {
            File sd = Environment.getExternalStorageDirectory();
            // by closing the database some other database files ending with -shm and -wal are deleted so that there is one database file and it has all content
            AppDatabase.getDbInstance(context).close();

            if (sd.canRead()) {
                File currentDB = context.getDatabasePath(AppDatabase.DATABASE_NAME);
                File backupDB = new File(sd, AppDatabase.DATABASE_NAME);

                transfer(context, backupDB, currentDB);
                Toast.makeText(context, "Database Restored successfully", Toast.LENGTH_SHORT).show();

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public static void exportDB(Context context) {
        try {
            File sd = Environment.getExternalStorageDirectory();
            // by closing the database some other database files ending with -shm and -wal are deleted so that there is one database file and it has all content
            AppDatabase.getDbInstance(context).close();

            if (sd.canWrite()) {
                File currentDB = context.getDatabasePath(AppDatabase.DATABASE_NAME);
                File backupDB = new File(sd, AppDatabase.DATABASE_NAME);
                transfer(context, currentDB, backupDB);

                Toast.makeText(context, "Backup is successful to SD card", Toast.LENGTH_SHORT).show();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void transfer(Context context, File sourceFile, File destinationFile) throws IOException {
        if (sourceFile.exists()) {
            FileChannel src = new FileInputStream(sourceFile).getChannel();
            FileChannel dst = new FileOutputStream(destinationFile).getChannel();
            dst.transferFrom(src, 0, src.size());
            src.close();
            dst.close();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.back_up){
            BackupRestore.exportDB(getBaseContext());
        }
        else if(id == R.id.restore_btn){
            BackupRestore.importDB(getBaseContext());
        }
        return super.onOptionsItemSelected(item);
    }
}

用户列表适配器代码

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.innbucksbackuprestoreroomdb.database.User;
import java.util.List;

public class UserListAdapter extends RecyclerView.Adapter<UserListAdapter.MyViewHolder> {
    private Context context;
    private List<User> userList;

    public UserListAdapter(Context context){
        this.context = context;
    }

    public void setUserList(List<User>userList){
        this.userList = userList;
        notifyDataSetChanged();
    }

    @NonNull
    @Override
    public UserListAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.recycler_row, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull UserListAdapter.MyViewHolder holder, int position) {
        holder.id.setText(String.valueOf(this.userList.get(position).getId()));
        holder.Name.setText(this.userList.get(position).getName());
    }

    @Override
    public int getItemCount() {
        return this.userList == null ? 0 : this.userList.size();
    }

    public static class MyViewHolder extends RecyclerView.ViewHolder{
        TextView id;
        TextView Name;
        public MyViewHolder(View view){
            super(view);
            id = view.findViewById(R.id.tvId);
            Name = view.findViewById(R.id.tvName);
        }
    }
}

问题排查与修复方案

1. 菜单事件未正确消费

onOptionsItemSelected方法中,处理完按钮点击后需返回true标记事件已处理,否则系统会继续传递事件,导致逻辑未执行。修改如下:

@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
    int id = item.getItemId();

    if (id == R.id.back_up){
        BackupRestore.exportDB(getBaseContext());
        return true;
    }
    else if(id == R.id.restore_btn){
        BackupRestore.importDB(getBaseContext());
        return true;
    }
    return super.onOptionsItemSelected(item);
}

2. 缺少外部存储读写权限

Android 6.0(API 23)及以上需动态申请读写外部存储权限,仅在Manifest中声明无效。

步骤1:在Manifest中添加权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- Android 13+ 需单独申请媒体权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

步骤2:添加动态权限申请逻辑

在Activity中添加权限检查方法:

private static final int STORAGE_PERMISSION_CODE = 101;

private void requestStoragePermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
                || checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_PERMISSION_CODE);
        }
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == STORAGE_PERMISSION_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 权限申请成功
        } else {
            Toast.makeText(this, "需要存储权限才能执行备份/恢复操作", Toast.LENGTH_SHORT).show();
        }
    }
}

在onOptionsItemSelected中先检查权限:

if (id == R.id.back_up){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && 
        (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)) {
        requestStoragePermission();
    } else {
        BackupRestore.exportDB(getBaseContext());
    }
    return true;
}
else if(id == R.id.restore_btn){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && 
        (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)) {
        requestStoragePermission();
    } else {
        BackupRestore.importDB(getBaseContext());
        loadUserList(); // 恢复后重新加载列表
    }
    return true;
}

3. Room数据库实例关闭与重新初始化问题

直接调用AppDatabase.getDbInstance(context).close()后,后续获取实例可能拿到已关闭的对象,需确保单例方法能重新创建实例:

// AppDatabase类中的单例方法示例
public static AppDatabase getDbInstance(Context context) {
    if (instance == null || !instance.isOpen()) {
        instance = Room.databaseBuilder(context.getApplicationContext(),
                        AppDatabase.class, DATABASE_NAME)
                .allowMainThreadQueries() // 注意:实际开发建议用异步操作
                .build();
    }
    return instance;
}

4. 异常处理优化

当前仅打印异常堆栈,用户无法感知错误,修改importDB和exportDB添加错误提示:

public static void importDB(Context context) {
    try {
        // ...原有逻辑
    } catch (Exception e) {
        e.printStackTrace();
        Toast.makeText(context, "恢复失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
    }
}

public static void exportDB(Context context) {
    try {
        // ...原有逻辑
    } catch (Exception e) {
        e.printStackTrace();
        Toast.makeText(context, "备份失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
    }
}

5. 适配Android Q+存储策略

Environment.getExternalStorageDirectory()在Android Q(API 29)及以上已废弃,建议使用应用专属外部存储路径:

// 替换exportDB中的sd路径
File sd = context.getExternalFilesDir(null); // 应用专属外部存储目录,Android Q+无需动态权限

验证修改

完成上述修改后重新运行程序:

  1. 首次点击备份/恢复按钮会弹出权限申请对话框,授权后即可执行操作
  2. 备份成功后可在Android/data/你的包名/files目录下找到数据库文件
  3. 恢复后RecyclerView会自动刷新显示最新数据

内容的提问来源于stack exchange,提问作者Anesu Mazvimavi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 07:00:56