为啥 ContentProvider 在 App 更新后返回空游标?

Posted

技术标签:

【中文标题】为啥 ContentProvider 在 App 更新后返回空游标?【英文标题】:Why ContentProvider returns empty cursors after App update?为什么 ContentProvider 在 App 更新后返回空游标? 【发布时间】:2019-12-14 11:54:00 【问题描述】:

我最近为我的应用推出了更新,我收到了来自一些用户的反馈,他们的数据丢失了。经过一些分析,我确定我使用的单个内容提供程序总是返回一个空光标。

我没有更改内容提供者本身的代码或查询它的代码。数据格式没有改变。该问题仅在少数设备上可见,并且似乎仅限于 android 6.0(主要是三星,但也有一些 LG 设备)。不仅丢失了初始数据,而且所有后续插入都没有持久化

可以通过全新安装应用程序(删除 + 安装)来解决该问题。

有没有人知道如何解决/解决这个问题?

我认为这无关紧要,但这里是代码:

Cursor cursor = contentResolver.query(Columns.contentUri(), Columns.ALARM_QUERY_COLUMNS, null, null, Columns.DEFAULT_SORT_ORDER);

List<AlarmActiveRecord> alarms = new ArrayList<AlarmActiveRecord>();
try 
    if (cursor.moveToFirst()) 
        do 
            alarms.add(factory.create(cursor));
         while (cursor.moveToNext());
    
 finally 
    cursor.close();

return alarms;

还有内容提供者:

@Override
public Cursor query(Uri url, String[] projectionIn, String selection, String[] selectionArgs, String sort) 
    SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

    // Generate the body of the query
    Preconditions.checkArgument(sURLMatcher.match(url) == ALARMS, "Invalid URL %s", url);
    qb.setTables("alarms");

    SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    Cursor ret;
    try 
        ret = qb.query(db, projectionIn, selection, selectionArgs, null, null, sort);
     catch (SQLException e) 
        log.e("query failed because of " + e.getMessage() + ", recreating DB");
        db.execSQL("DROP TABLE IF EXISTS alarms");
        // I know this is not nice to call onCreate() by ourselves :-)
        mOpenHelper.onCreate(db);
        ret = qb.query(db, projectionIn, selection, selectionArgs, null, null, sort);
    
    if (ret != null) 
        ret.setNotificationUri(getContext().getContentResolver(), url);
    

    return ret;

还有数据库助手:

public class AlarmDatabaseHelper extends SQLiteOpenHelper 
private static final String DATABASE_NAME = "alarms.db";
private static final int DATABASE_VERSION = 5;
private final Logger log;

public AlarmDatabaseHelper(Context context, Logger log) 
    super(context, DATABASE_NAME, null, DATABASE_VERSION);
    this.log = log;


@Override
public void onCreate(SQLiteDatabase db) 
    // @formatter:off
    db.execSQL("CREATE TABLE alarms (" +
            "_id INTEGER PRIMARY KEY," +
            "hour INTEGER, " +
            "minutes INTEGER, " +
            "daysofweek INTEGER, " +
            "alarmtime INTEGER, " +
            "enabled INTEGER, " +
            "vibrate INTEGER, " +
            "message TEXT, " +
            "alert TEXT, " +
            "prealarm INTEGER, " +
            "state STRING);");
    // @formatter:on
    // insert default alarms
    String insertMe = "INSERT INTO alarms " + "(hour, minutes, daysofweek, alarmtime, enabled, vibrate, "
            + "message, alert, prealarm, state) VALUES ";
    db.execSQL(insertMe + "(8, 30, 31, 0, 0, 1, '', '', 0, '');");
    db.execSQL(insertMe + "(9, 00, 96, 0, 0, 1, '', '', 0, '');");


@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) 
    log.d("Upgrading alarms database from version " + oldVersion + " to " + currentVersion
            + ", which will destroy all old data");
    db.execSQL("DROP TABLE IF EXISTS alarms");
    onCreate(db);


public Uri commonInsert(ContentValues values) 
    SQLiteDatabase db = getWritableDatabase();
    long rowId = db.insert("alarms", Columns.MESSAGE, values);
    if (rowId < 0) throw new SQLException("Failed to insert row");
    log.d("Added alarm rowId = " + rowId);

    return ContentUris.withAppendedId(Columns.contentUri(), rowId);


【问题讨论】:

我没有更改数据库版本或表结构。大部分用户都很好,只有一小部分受到影响。 为什么你首先有一个ContentProvider?您是否专门尝试将您的数据提供给其他应用程序?如果没有,请删除 ContentProvider,因为您不需要它。 我将它用作存储库,将警报数据存储在表中。以前我确实已经导出了这个提供程序,但我不再这样做了。我从来没有想过要更换这个机制,因为它工作得很好。你能提出任何替代方案吗?我必须保存 5 到 50 行,每行 10 列。 【参考方案1】:

迁移脚本导致了所描述的行为。 DROP TABLE 确实如其所言,因此所有先前输入的记录都将丢失,除非旧版本的数据库已备份到云端。此问题可能不仅限于三星或 LG 设备,因为该部分是明确的:

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) 
    db.execSQL("DROP TABLE IF EXISTS alarms");

您需要将数据库从一个版本迁移到下一个版本;为example。

Room 也支持Migrating Room databases(就是同一个场景)。

【讨论】:

对不起,你错了。首先,数据库版本没有改变。因此不会调用 onUpgrade。其次,删除数据库后有一个 onCreate 调用,它将两行放入数据库中。这显然不会发生,因为光标保持为空。在创建之后它会有两行。 @YuriyKulikov onUpgrade() 可能会在 APK 更新后运行,而您没有比较 oldVersioncurrentVersion。文档阅读“需要升级数据库时调用”。 (无论何时)。 无论如何,在更新方法中,我首先删除数据库,然后添加两行。所以它永远不会是空的。

以上是关于为啥 ContentProvider 在 App 更新后返回空游标?的主要内容,如果未能解决你的问题,请参考以下文章

ContentProvider详解

从App启动理解ContentProvider的创建

Android 控制 ContentProvider的创建

Android 控制 ContentProvider的创建

Android 四大组件 ContentProvider介绍

安卓当中contentprovider问题