我想在我的应用程序中创建一个数据库.我使用了这段代码,它在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();
  }

下面是日志(log)cat 错误:

起因:android.database.sqlite.SQLiteCantOpenDatabaseException: 未知错误(代码14 SQLITE_CANTOPEN):无法打开数据库 在android.database.sqlite.SQLiteConnection.nativeOpen(Native方法中)

Android 10+在创建数据库或写入外部存储方面有什么变化吗?

推荐答案

你不应该对安卓10+索要WRITE_EXTERNAL_STORAGE美元;这没有任何效果.

In documentation:

注意:如果您的应用程序以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请求权限,请判断this question以了解替代API

另外,考虑一下CommonsWare指出的有价值的comment

Update:

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在this comment中指出的那样,开发人员不允许在Android 11+的外部存储上写入任意位置.这一例外就是因为这一点而被提出的.

To solve this, it requires the developer either:

  1. 以允许用户通过使用ACTION_CREATE_DOCUMENT intent Check here浏览外部/SD卡来 Select 特定文件夹以获得进一步的细节.

  2. 或者使用分配给应用程序的空间.

如果您想考虑第二种 Select ,而不是共享Environment.getExternalStorageDirectory().getAbsolutePath()目录,您可以在SD卡getExternalFilesDir()或内部设备存储getFilesDir()上写入.两者都分配给您的应用程序,其他任何应用程序都无法正常访问它们.

因此,您必须更改以下代码:

public static String SDCARD = Environment
                        .getExternalStorageDirectory().getAbsolutePath(); 

类似这样的东西:

public static String SDCARD = context.getFilesDir().getAbsolutePath(); 

// or

public static String SDCARD = context.getExternalFilesDir(null).getAbsolutePath(); 

我保留了SDCARD个原样;但这并不表明它是SD共享存储.

Java相关问答推荐

如何将kotlin代码转换为java

我想了解Java中的模块化.编译我的应用程序时,我有一个ResolutionException

在bash中将数组作为Java程序的参数传递

在Java中,在单个逻辑行中连接列表和单个元素的正确方法是什么?

在Spring终结点中,是否可以同时以大写和小写形式指定枚举常量?

有没有更快的方法在N个容器中删除重复项?

用户填充的数组列表永不结束循环

Docker不支持弹性APM服务器

Java构造函数分支

如何在代码中将行呈现在矩形前面?

在settings.gradle.kts和Build.gradle.kts中使用公共变量

Cordova Android Gradle内部版本组件不兼容

将关闭拍卖的TimerService

对从Spring Boot 3.1.5升级到3.2.0的方法的查询验证失败

在Spring Boot JPA for MySQL中为我的所有类创建Bean时出错?

字符串的Gzip压缩在java11和java17中给出了不同的结果

Java 21内置http客户端固定运营商线程

如何在java中从以百分比表示的经过时间和结束日期中找到开始日期

java构造函数中的冻结操作何时发生?

将天数添加到ZonedDateTime不会更改时间