【问题标题】:SQLiteCursors aren't being notified, aidl files don't existSQLiteCursors 没有被通知,aidl 文件不存在
【发布时间】:2015-02-03 03:58:55
【问题描述】:

(编辑两次)

我的 sqlite 游标没有收到更改通知,即使我在创建它们时设置了通知并在访问数据库时通知它们。我尝试了这样的方法来测试方法:

weatherCursor.getCount(); // returns 1
deleteAllRecords();
Uri uri = weatherCursor.getNotificationUri();
getContext().getContentResolver().notifyChange(uri, null);
weatherCursor.getCount(); // still returns 1
weatherCursor.close();
weatherCursor = getContext().getContentResolver().query(uri, null, null, null, null);
weatherCursor.getCount(); //finally returns 0

当我尝试挖掘ContentResolver.notifyChange 的源代码时,我发现它尝试使用一个名为IContentObserver 的类,但无法解析导入。它在 try 块中执行此操作,而 catch 块是空的,所以它会默默地失败。

同样,AbstractCursor.setNotificationUri 调用ContentResolver.registerContentObserver,它尝试使用一个名为IContentService 的类,也在一个带有空catch 的try 块中。与上面不同,我什至没有看到IContentService 的导入语句。

评论者 (@Lawrence Choy) 解释说“IContentObserver 实际上是在IContentObserver.aidl 中定义的自动生成文件。”我在整个系统中搜索“IContentObserver.aidl”,唯一成功的是 Android Studio 用来存储最近搜索的文件,所以我的电脑上没有这个文件。我不确定该文件应该在哪里,但我尝试通过 SDK 管理器删除并重新安装构建工具和 API,但我仍然没有它。

[另一个编辑] 上面的 sn-p (在评估器中运行)旨在将以下所有代码归结为相关部分,但这里有一组更完整的代码。首先是deleteAllRecords() 的代码,这是我的一个测试单元中失败的方法。它成功删除了所有记录,但未能更新我认为理所当然的游标。

public void deleteAllRecords() {

    Cursor weatherCursor = queryWholeTable(mContext, WeatherEntry.CONTENT_URI);
    Cursor locationCursor = queryWholeTable(mContext, LocationEntry.CONTENT_URI);

    int initialWeatherCount = weatherCursor.getCount();
    int initialLocationCount = locationCursor.getCount();

    int deletedWeatherCount = mContext.getContentResolver().delete(
            WeatherEntry.CONTENT_URI,
            null,
            null
    );
    int finalWeatherCount = weatherCursor.getCount();
    int deletedLocationCount = mContext.getContentResolver().delete(
            LocationEntry.CONTENT_URI,
            null,
            null
    );
    int finalLocationCount = locationCursor.getCount();

    assertEquals(initialWeatherCount, deletedWeatherCount);
//ASSERT BELOW FAILS!
    assertEquals(0, finalWeatherCount);
    weatherCursor.close();

    assertEquals(initialLocationCount, deletedLocationCount);
//ASSERT BELOW FAILS!
    assertEquals(0, finalLocationCount);
    locationCursor.close();

    //Added when above tests failed to see if records were actually deleted.

    weatherCursor = queryWholeTable(mContext, WeatherEntry.CONTENT_URI);
    locationCursor = queryWholeTable(mContext, LocationEntry.CONTENT_URI);

    int weatherVeryFinal = weatherCursor.getCount();
    int locationVeryFinal = locationCursor.getCount();

    assertEquals(0, weatherVeryFinal);
    assertEquals(0, locationVeryFinal);

    locationCursor.close();
    weatherCursor.close();

}

这里是.queryWholeTable

public static Cursor queryWholeTable(Context context, Uri uri){
    return context.getContentResolver().query(uri, null, null, null, null);
}

这里是WeatherProvider.delete.query方法:

@Override
public int delete(Uri targetUri, String selection, String[] selectionArgs) {
    final int match = sUriMatcher.match(targetUri);
    final String table;
    final Uri notificationUri;
    switch (match) {
        case WEATHER:
            table = WeatherEntry.TABLE_NAME;
            notificationUri = WeatherEntry.CONTENT_URI;
            break;
        case LOCATION:
            table = LocationEntry.TABLE_NAME;
            notificationUri = LocationEntry.CONTENT_URI;
            break;
        default:
            throw new UnsupportedOperationException("Unknown delete uri: " + targetUri.toString());
    }
    int affected = mOpenHelper.getWritableDatabase().delete(table, selection, selectionArgs);
    if (selection == null || affected > 0) {
        getContext().getContentResolver().notifyChange(notificationUri, null);
    }
    return affected;
}

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                    String sortOrder) {
    // Here's the switch statement that, given a URI, will determine what kind of request it is,
    // and query the database accordingly.
    Cursor retCursor;
    switch (sUriMatcher.match(uri)) {
        // "weather/*/*"
        case WEATHER_WITH_LOCATION_AND_DATE: {
            retCursor = getWeatherByLocationSetting(uri, projection, sortOrder, true);
            break;
        }
        // "weather/*"
        case WEATHER_WITH_LOCATION: {
            retCursor = getWeatherByLocationSetting(uri, projection, sortOrder, false);
            break;
        }
        // "weather"
        case WEATHER: {
            retCursor = mOpenHelper.getReadableDatabase().query(
                    WeatherEntry.TABLE_NAME,
                    projection,
                    selection,
                    selectionArgs,
                    null,
                    null,
                    sortOrder
            );
            break;
        }
        // "location/*"
        case LOCATION_ID: {
            retCursor = mOpenHelper.getReadableDatabase().query(
                    LocationEntry.TABLE_NAME,
                    projection,
                    LocationEntry._ID + " = " + ContentUris.parseId(uri),
                    selectionArgs,
                    null,
                    null,
                    sortOrder
            );
            break;
        }
        // "location"
        case LOCATION: {
            retCursor = mOpenHelper.getReadableDatabase().query(
                    LocationEntry.TABLE_NAME,
                    projection,
                    selection,
                    selectionArgs,
                    null,
                    null,
                    sortOrder
            );
            break;
        }

        default:
            throw new UnsupportedOperationException("Unknown uri: " + uri);
    }
    retCursor.setNotificationUri(getContext().getContentResolver(), uri);
    return retCursor;
}

如您所见,.setNotificationUri 总是在创建游标时调用,.notifyChange 总是在删除任何内容时调用。

【问题讨论】:

  • 您是要覆盖ContentResolver 还是根本无法正常使用构建项目?
  • 我并没有尝试覆盖 ContentResolver,实际上该项目确实构建并运行,但我的光标没有收到更改通知,当我尝试查看 ContentResolver.notifyChange() 的源时,我发现它叫 IContentObserver.notifyChange` 并且被难住了。这让我不确定是我的代码导致了问题,还是文件丢失。
  • 我怀疑这不是您的问题的原因。 IContentObserver 实际上是在IContentObserver.aidl 中定义的自动生成文件。此文件是在您从 Android 源文件构建时生成的,您在 SDK 管理器中下载的源文件中可能不存在该文件。当您运行应用程序时,设备最终将拥有此文件。
  • 谢谢!我编辑了问题。
  • 与 sdk 捆绑的源代码仅供参考,从未构建。调用是在 frameworks.jar 中完成的,因此框架出现错误的可能性很小。你能把你注册回调的代码贴出来吗?

标签: android android-sqlite android-contentresolver aidl


【解决方案1】:

AIDL 不是原因。原因是你如何加载数据——你是直接从 SQLiteDatabase 访问它,还是使用 Loader 来查询它?

光标只是那些“延迟加载”模式之一,它不知道底层数据发生了什么。要观察内容变化,您需要一个观察者 - 例如 Loader。

1) 如果你不使用 loader/contentprovider,你的游标不会被通知,你必须自己处理游标失效。

2) 如果你使用 Loader,我猜它是一个 CursorLoader,它从某个 Uri 加载数据。在这种情况下,通常有一个 ContentProvider,它负责 SQLite CRUD 操作并通知所有订阅者(例如现有的加载器)内容更改。

看来您要么坚持 1),要么执行 2) 是错误的。

我建议您进一步了解 ContentProvider、Loader 并查看一些示例。您可以从my repo 签出其中一个应用程序,例如this one(注意 CursorLoader 的使用、LoaderManager 的实现、ContentProvider 的实现),并在您的 IDE 中观察它们的运行情况。

【讨论】:

  • 我正在尝试使用 ContentProvider,但我知道我的实现是错误的。
  • 我查看了加载器,是的,它们会自动更新,但这不是 .notifyChange.setNotificationUri 应该为常规游标做什么吗?看起来加载器的基本优势是它们异步工作,这很高兴知道但不能解决我的问题。
  • .setNotificationUri 告诉 Cursor 监视 ContentProvider Uri 的变化。如果您的 ContentProvider 实现错误 - 那么您的 Cursor 可能不会收到通知。如果您将数据直接插入 SQLite 数据库,并且不为此目的使用 ContentResolver - 您的 Cursor 将不会更新。 Loader 的优点:异步、防内存泄漏、为您管理游标、监控数据变化
  • 无论如何,如果没有更多代码细节,很难说出更具体的内容
  • 好的,所以我可以(可能)使用CursorLoader 完全回避这个问题,但我想知道为什么这不起作用。基本上我在问为什么.setNotifactionUri.notifyChange 不起作用,而你们都只是告诉我从顶部开始使用不同的实现。 Google 提供这些方法并不是为了欺骗我。一定有一种方法可以正确使用它们,我错过了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多