提问人:Amirhussein 提问时间:3/11/2023 最后编辑:bighugedevAmirhussein 更新时间:10/3/2023 访问量:373
无法在外部存储中的 Android 11+ 上创建数据库
can't create database on android 11+ in external storage
问:
我想在我的应用程序中创建一个数据库。我使用了这段代码,它在 Android 9 及更低版本上正常工作,但在 Android 10+ 上我崩溃了,不仅没有创建数据库,而且在外部存储中没有创建文件目录。
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.miplan">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage">
</uses-permission>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MiPlan">
<activity
android:name=".SplashActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:exported="false" />
<activity
android:name=".LogoActivity"
android:exported="true">
</activity>
</application>
</manifest>
这是 MyDatabaseHelper:
package com.example.miplan;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import androidx.annotation.Nullable;
public class MyDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "myDatabase.SqLite";
private static final int DB_VERSION = 1;
public MyDatabaseHelper(@Nullable Context context) {
super(context, SplashActivity.DB_DIR + "/" + DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
String query = " CREATE TABLE 'person' (" +
"'personId' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , " +
"'firstName' TEXT, " +
"'lastName' TEXT, " +
"'gender' INTEGER, " +
"'email' TEXT UNIQUE, " +
"'phoneNumber' TEXT UNIQUE)";
// db.execSQL(query);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
SplashActivity:
package com.example.miplan;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
@SuppressLint("CustomSplashScreen")
public class SplashActivity extends AppCompatActivity {
public static String SDCARD = Environment.getExternalStorageDirectory().getAbsolutePath();
public static String BRAND = SDCARD + "/Self-learn";
public static String APP_DIR = BRAND + "/MiPlan";
public static String DB_DIR = APP_DIR + "/db";
public static SQLiteDatabase database;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_begin);
int grantResult = ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (grantResult == PackageManager.PERMISSION_GRANTED) {
createAppDir();
} else {
askUserForPermission();
}
}
private void askUserForPermission() {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 100:
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle("Error").setMessage("Write on External Storage is needed for this app").setPositiveButton("ask again", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
askUserForPermission();
}
}).setCancelable(false).create().show();
} else {
createAppDir();
}
break;
}
}
private void createAppDir() {
File file = new File(DB_DIR);
if (!file.exists()) {
file.mkdirs();
createDatabase();
}
createDatabase();
}
public void createDatabase() {
MyDatabaseHelper dbHelper = new MyDatabaseHelper(SplashActivity.this);
database = dbHelper.getWritableDatabase();
Log.i("testtt", "this line is working well...");
openWelcomeActivity();
}
private void openWelcomeActivity() {
Intent intent = new Intent(this, LogoActivity.class);
startActivity(intent);
this.finish();
}
这是 Logcat 错误:
原因:android.database.sqlite.SQLiteCantOpenDatabaseException: 未知错误(代码 14 SQLITE_CANTOPEN):无法打开数据库 在 android.database.sqlite.SQLiteConnection.nativeOpen(本机方法)
在 Android 10+ 中创建数据库或写入外部存储是否有任何变化?
答:
您不应该要求 Android 10+;它没有效果。WRITE_EXTERNAL_STORAGE
注意:如果您的应用以 Build.VERSION_CODES.R 或更高版本,则此权限无效。
因此,您需要在不请求Android 10 +权限的情况下继续
private void askUserForPermission() {
if (SDK_INT < Build.VERSION_CODES.Q)
ActivityCompat.requestPermissions(this, new String[]
{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
else
createAppDir();
}
请注意,您正在使用已弃用的 API 请求权限,请查看此问题以获取替代 API
另外,请考虑 CommonsWare 指出的有价值的评论
更新:
Caused by: android.database.sqlite.SQLiteCantOpenDatabaseException: unknown error (code 14 SQLITE_CANTOPEN): Could not open database at android.database.sqlite.SQLiteConnection.nativeOpen(Native Method)
正如 CommonsWare 在此评论中指出的那样,开发人员不得在 Android 11+ 上外部存储的任意位置写入。因此,提出了这个例外。
要解决此问题,它需要开发人员:
要允许用户通过使用 intent 在外部/SD 卡中导航来选择特定文件夹
ACTION_CREATE_DOCUMENT
请查看此处了解更多详情。或者使用分配给应用程序的空间。
如果要考虑第二个选项,而不是共享目录,则可以在以下位置写入:Environment.getExternalStorageDirectory().getAbsolutePath()
- 内部设备存储: 内部/本地存储(在 \databases 子目录下)中的默认数据库位置,该位置分配给你的应用,其他应用无法访问它。
getFilesDir()
- 设备外部存储、共享存储或可移动(SD卡)存储;但这是有风险的,不建议这样做,因为用户可以访问它,并且可能会损坏您的文件,甚至将它们共享到其他应用程序。
getExternalFilesDir()
因此,您必须更改以下代码:
public static String SDCARD = Environment .getExternalStorageDirectory().getAbsolutePath();
到类似的东西:
public static String SDCARD = context.getFilesDir().getAbsolutePath();
// or
public static String SDCARD = context.getExternalFilesDir(null).getAbsolutePath();
我保持原样;但这并不表示它是 SD 共享存储。SDCARD
评论
context
SDCARD
评论
WRITE_EXTERNAL_STORAGE