1

当谈到 Android 上的后台服务时,我遇到了一些理解问题。我有一种感觉,对于那些有丰富经验的人来说,我的问题很容易回答,我只需要指出正确的方向:-)

让我首先解释一下我想要实现的目标:我想下载带有一组数据(下载 url、标题、ID、所有者 ID 和文件名)的图片。用户通过文本字段输入该数据。用户可以输入该数据集的多个实体,因此所有内容都存储在 ArrayLists 中。例如,URL 有一个 ArrayList,ID 有一个 ArrayList。从长远来看,这些信息不应该来自用户,而是来自服务器。用户界面基本上只是用于调试。下载完成后,将数据集放入 SQLite DB。

我的目标是拥有一个单独的后台服务,负责下载并在完成后立即将数据插入数据库。因此,从用户输入数据的 Activity 中,我创建了一个 Intent 并将用户数据添加为附加数据。这是第一个问题:没有额外的 ArrayList。这引出了我的第一个问题:

如何在服务/活动之间传递 ArrayList?我读过一些关于包裹的东西,尽管 long 是一种原始数据类型,但这是要走的路吗?

我的第二个问题是在再次调用服务之前我不能确定下载是否完成。因此,当再次调用该服务时,会再次调用 onStartCommand() 方法。现在我的服务类看起来像这样(ArrayList 中的数据还没有实现):

public class DownloadService extends Service implements DownloadAlbumPictures.DownloadCompletedListener {

    public final static String EXTRA_ALBUMPICTURE_URLS = "ALBUM_PICTURE_URLS";
    public final static String EXTRA_ALBUMPICTURE_NAMES = "ALBUM_PICTURE_NAMES";
    public final static String EXTRA_ALBUMPICTURE_TITLES = "ALBUM_PICTURE_TITLES";

    private ArrayList<String> picUrls;
    private ArrayList<String> namesForFiles;
    private ArrayList<Long> ownerIds;
    private ArrayList<Long> pictureIds;
    private ArrayList<Long> albumIds;
    private ArrayList<Integer> positions;
    private ArrayList<String> titles;
    private String pathToFiles;

    // download interface
     public void albumPicDownloadCompleted(Context context, Uri uri, int indexOfCompletedFile) {
         // init
          AlbumPicture newAlbumPic;

         // data from download
          long ownerId = 0 //ownerIds.get(indexOfCompletedFile);
          long pictureId = 0 //pictureIds.get(indexOfCompletedFile);
          int positionInAlbum = 0 //positions.get(indexOfCompletedFile);
          String title = titles.get(indexOfCompletedFile);
          String pathToPic = uri.toString();
          long albumId = 0 //albumIds.get(indexOfCompletedFile);

          newAlbumPic = new AlbumPicture(ownerId, pictureId, positionInAlbum, title, pathToPic, albumId);

         // put picture into DB
          newAlbumPic.putIntoDb(this);
     }

    @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         picUrls = intent.getStringArrayListExtra(EXTRA_ALBUMPICTURE_URLS);
         namesForFiles = intent.getStringArrayListExtra(EXTRA_ALBUMPICTURE_NAMES);
         titles = intent.getStringArrayListExtra(EXTRA_ALBUMPICTURE_TITLES);

          DownloadAlbumPictures dwnldAlbumPics = new DownloadAlbumPictures(newPicUrls, newNamesForFiles);
          dwnldAlbumPics.execDownload(this);
          return Service.START_REDELIVER_INTENT;
     }
}

DownloadAlbumPictures.execDownload 调用 Androids 下载管理器。它还为下载完成事件注册一个广播接收器,然后调用接口函数albumPicDownloadCompleted():

public class DownloadAlbumPictures {

public interface DownloadCompletedListener {
    public void albumPicDownloadCompleted(Context context, Uri uri, int indexOfCompletedFile);
}

private ArrayList<String> picUrls;
private ArrayList<String> namesForFiles;
private ArrayList<Long> downloadReferences; 
private String pathToFiles;

private DownloadCompletedListener dwnldCompletedListener;

public DownloadAlbumPictures(ArrayList<String> _picUrls, ArrayList<String> _namesForFiles) {
    picUrls = _picUrls;
    namesForFiles = _namesForFiles;
}

public DownloadAlbumPictures() {

}

public boolean execDownload(Context context) {
    // check if source info is available
    if (picUrls == null || namesForFiles == null)
        return false;

    fetchPics(context);
    return true;
}

public boolean execDownload(Context context, ArrayList<String> _picUrls, ArrayList<String> _namesForFiles) {
    picUrls = _picUrls;
    namesForFiles = _namesForFiles;

    return execDownload(context);
}

// example use to download pictures
private void fetchPics(Context context) {
    // path
    pathToFiles = createPathForAlbumPictures();
    // listener interface
    try {
        dwnldCompletedListener = (DownloadCompletedListener) context;
    } catch (ClassCastException e) {
        throw new ClassCastException(context.toString() + " must implement DownloadCompletedListener for AlbumPicture download.");
    }

    // sending files to download manager and saving reference
    // TODO file type check missing => security issue
    DownloadFiles downloadFiles = new DownloadFiles(picUrls, pathToFiles, namesForFiles);
    if (downloadFiles.startDownload(context)) {
        downloadReferences = downloadFiles.getDownloadReferences();
    }
    else {
        downloadReferences = null;
    }
    // add listeners for when file download is completed
    IntentFilter broadcastFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
    BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            long reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);    // get reference of completed download (if no completed download -1)
            // check all downloaded files
            for (int i=0; i<downloadReferences.size(); i++) {
                if (reference == downloadReferences.get(i)) {
                    // actual path to file needs to be updated
                    DownloadManager dwnldmngr = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
                    Uri uriOfFile = dwnldmngr.getUriForDownloadedFile(reference);
                    // hand over uri to interface
                    dwnldCompletedListener.albumPicDownloadCompleted(context, uriOfFile, i);    // interface
                }
            }
        }
    };
    context.registerReceiver(receiver, broadcastFilter);
}
}

现在我的问题是:如果我在第一个“ArrayList of downloads”完成之前第二次调用服务,我的数据将被覆盖。我想出的解决方案是,我需要在服务中实现一个队列。新数据不会覆盖旧数据,而是添加到队列中。一旦下载完成,该项目就会从队列中删除。看起来很复杂。我错过了另一个选择吗?这里的最佳做法是什么?

我希望我能得到一些关于我的问题的最佳实践信息。也许我做得太复杂了:-)

如果我的解释不是很清楚,我很抱歉。由于我没有解决这个问题的方法,我对这个话题的想法有点模糊。

谢谢你的帮助!担。

4

1 回答 1

0

没有额外的 ArrayList

是的,有ArrayList<Integer>,ArrayList<String>ArrayList<Parcelable>)。

如何在服务/活动之间传递 ArrayList?

在您的情况下,显然使用putStringArrayListExtra().

DownloadAlbumPictures.execDownload 调用 Androids 下载管理器。

让服务这样做是没有意义的。相反,让活动开始下载,并在下载完成后将清单注册的BroadcastReceiver传递控制权传递给IntentService数据库插入。

或者,自己下载。

或者,数据库是否首先插入自己(IntentService或活动产生的简单线程),但在某些列中带有“进行中”状态标志。然后,只需在下载完成后翻转标志。

我想出的解决方案是,我需要在服务中实现一个队列。

我上面概述的第二个和第三个选项应该避免这个要求。

于 2013-11-16T12:25:27.240 回答