当谈到 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”完成之前第二次调用服务,我的数据将被覆盖。我想出的解决方案是,我需要在服务中实现一个队列。新数据不会覆盖旧数据,而是添加到队列中。一旦下载完成,该项目就会从队列中删除。看起来很复杂。我错过了另一个选择吗?这里的最佳做法是什么?
我希望我能得到一些关于我的问题的最佳实践信息。也许我做得太复杂了:-)
如果我的解释不是很清楚,我很抱歉。由于我没有解决这个问题的方法,我对这个话题的想法有点模糊。
谢谢你的帮助!担。