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+无需动态权限
验证修改
完成上述修改后重新运行程序:
- 首次点击备份/恢复按钮会弹出权限申请对话框,授权后即可执行操作
- 备份成功后可在
Android/data/你的包名/files目录下找到数据库文件 - 恢复后RecyclerView会自动刷新显示最新数据
内容的提问来源于stack exchange,提问作者Anesu Mazvimavi
相关产品推荐
相关产品推荐

