Android таблица участников списка воспроизведения магазина мультимедиа становится поврежденной после повторного сканирования - PullRequest
0 голосов
/ 18 февраля 2020

У меня возникла проблема, когда данные участника списка воспроизведения в магазине мультимедиа повреждены после обновления хранилища мультимедиа сканером мультимедиа. Симптом этой проблемы состоит в том, что запросы к таблице элементов списка воспроизведения с разными проекциями возвращают разное количество строк. Если проекция запроса идентифицирует только столбец AUDIO_ID, возвращаемый размер курсора является ожидаемым числом (и курсор появляется для возврата правильных идентификаторов аудио). Если проекция запроса также идентифицирует столбец TITLE, возвращаемый размер курсора будет маленьким или нулевым. Это несоответствие подразумевает некоторое повреждение базы данных. Возвращение большего количества столбцов не должно уменьшать количество возвращаемых строк. Я подозреваю, что первый запрос выполнен успешно, потому что ему нужен только доступ к индексу таблицы.

Единственный найденный мной способ исправить эту проблему - очистить хранилище медиа-сканера и перезапустить его. Когда медиа-сканер создает таблицу участников списка воспроизведения, он делает это правильно. Однако при любом последующем перезапуске, когда сканер мультимедиа обновляет таблицу членов списка воспроизведения, результатом является поврежденная таблица. Результаты будут повреждены, как только сканер мультимедиа начнет сканирование. У меня более 4000 аудиофайлов на съемном носителе, поэтому медиа-сканер работает долго.

Я наблюдаю, что поврежденное состояние - это то же состояние, которое создано для списка воспроизведения, файл списка воспроизведения которого больше не существует. , что заставляет меня задуматься, были ли медиа-файлы недоступны при повторном сканировании.

Я наблюдал эту проблему на Motorola G6 с Android 9. Я не смог скопировать его на эмулируемом телефон работает Android 9. Разница может быть в том, что симулированная SD-карта отличается от фактического привода sh.

Обновление

Я собрал больше информации. Глядя на исходный код, я вижу, что таблица членов списка воспроизведения реализована в виде JOIN из двух таблиц, одна из которых содержит информацию о членстве в базовом списке воспроизведения c, а другая - таблица, содержащая информацию о медиа файлы. Первая таблица отображает идентификатор списка воспроизведения и номер заказа списка воспроизведения на аудио идентификатор; соединение со второй таблицей выполняется с использованием аудиоидентификатора. Запрос, который возвращает только аудио идентификаторы, использует только первую таблицу. Запрос, который также запрашивает метаданные (например, название дорожки), использует JOIN .

Наблюдаемое мной несогласованное состояние возникает из-за того, что идентификаторы аудио, возвращаемые из таблицы списка воспроизведения, не совпадают с аудио идентификаторы в таблице медиа-файлов. Сканер выделил новые идентификаторы для файлов мультимедиа, но таблица списка воспроизведения возвращает устаревшие данные. Аналогичная проблема связана с плейлистами "zomb ie", которые выживают после удаления файла плейлиста. Таблица списка воспроизведения по-прежнему содержит определение списка воспроизведения, но аудио идентификаторы устарели.

Тестовая программа

Ниже приведен некоторый тестовый код, который я использую.

import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;

import androidx.annotation.*;

public class TestPlaylists {

  private final String LOGTAG;

  private final @NonNull Context context;

  private static class Result {
    int size;
    long[] audioIds;
    @Nullable String desc;
    @Override
    public @NonNull String toString() {
      return size + (desc != null ? " [" + desc + "]" : " -----");
    }
  }

  public TestPlaylists(@NonNull Context context, @NonNull String logtag) {
    this.context = context;
    this.LOGTAG = logtag;

    String[] ccols = new String[] {
        MediaStore.Audio.Playlists._ID,
        MediaStore.Audio.Playlists.NAME
    };

    Cursor c = query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, ccols, null);
    if (c != null) {
      int count = c.getCount();
      Log.d(LOGTAG, "Playlist count: " + count);
      while (c.moveToNext()) {
        long playlistId = c.getLong(0);
        String playlistName = c.getString(1);
        Log.d(LOGTAG, "Playlist #" + playlistId + ": " + playlistName);
        String location = getPlayListFile(playlistId);
        Result r1 = getPlayListSize(playlistId);
        Result r2 = getPlayListSize2(playlistId);
        Log.d(LOGTAG, "  " + r1 + " " + r2 + " " + location);
        if (r1.size != r2.size) {
          validate(r1.audioIds);
          showTrackIdRange();
        }
      }
      c.close();
    }
  }

  @Nullable Result getPlayListSize(long plid) {
    String[] ccols = new String[] { MediaStore.Audio.Playlists.Members.AUDIO_ID };
    Cursor c = query(MediaStore.Audio.Playlists.Members.getContentUri("external", plid),
        ccols, null);
    return createResult(c);
  }

  @Nullable Result getPlayListSize2(long plid) {
    String[] ccols = new String[] {
        MediaStore.Audio.Playlists.Members.AUDIO_ID,
        MediaStore.Audio.Playlists.Members.TITLE
    };
    Cursor c = query(MediaStore.Audio.Playlists.Members.getContentUri("external", plid),
        ccols, null);
    return createResult(c);
  }

  private @Nullable Result createResult(@Nullable Cursor c) {
    if (c != null) {
      int size = c.getCount();
      Result result = new Result();
      result.size = size;
      StringBuilder b = new StringBuilder();
      if (size > 0) {
        int limit = Math.min(size, 5);
        long[] audioIds = new long[limit];
        for (int i = 0; i < limit; i++) {
          c.moveToNext();
          long id = c.getLong(0);
          audioIds[i] = id;
          b.append(" ");
          b.append(id);
        }
        result.audioIds = audioIds;
        result.desc = b.toString().trim();
      }
      c.close();
      return result;
    }
    return null;
  }

  void validate(long[] audioIds) {
    if (audioIds.length > 0) {
      for (long audioId : audioIds) {
        if (!validate(audioId)) {
          Log.e(LOGTAG, "Audio #" + audioId + " is not valid");
        }
      }
    }
  }

  boolean validate(long audioId) {
    Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
    String[] ccols = new String[] {
        MediaStore.Audio.AudioColumns.TITLE,
    };
    String selection = MediaStore.Audio.Media._ID + "=" + audioId;
    Cursor c = query(uri, ccols, selection);
    if (c != null) {
      try {
        return c.getCount() == 1;
      } finally {
        c.close();
      }
    }
    return false;
  }

  @Nullable String getPlayListFile(long plid) {
    // This query will fail in API 29+
    String[] ccols = new String[] {
        MediaStore.Audio.Playlists._ID,
        MediaStore.Audio.Playlists.DATA
    };
    String where = MediaStore.Audio.Playlists._ID + " == " + plid;
    try {
      Cursor c = query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, ccols, where);
      if (c != null) {
        try {
          if (c.getCount() > 0) {
            c.moveToNext();
            return c.getString(1);
          }
        } finally {
          c.close();
        }
      }
    } catch (Exception e) {
    }
    return null;
  }

  public @Nullable Cursor query(@NonNull Uri uri,
                                @NonNull String[] projection,
                                @Nullable String selection) {
    try {
      ContentResolver resolver = context.getContentResolver();
      if (resolver == null) {
        return null;
      }
      return resolver.query(uri, projection, selection, null, null);
    } catch (UnsupportedOperationException ex) {
      return null;
    }
  }

  void showTrackIdRange() {
    long min = Long.MAX_VALUE;
    long max = Long.MIN_VALUE;
    Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
    String[] columns = { MediaStore.Audio.Media._ID };
    Cursor c = query(uri, columns, null);
    if (c != null) {
      try {
        int count = c.getCount();
        if (count == 0) {
          Log.e(LOGTAG, "There are no valid audio files");
          return;
        }
        for (int i = 0; i < count; i++) {
          c.moveToNext();
          long id = c.getLong(0);
          if (id > max) {
            max = id;
          }
          if (id < min) {
            min = id;
          }
        }
        Log.d(LOGTAG, "The valid audio file ID range is " + min + "–" + max);
      } finally {
        c.close();
      }
    }
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...