从Android中的firebase下载多个文件时如何管理内存
Posted
技术标签:
【中文标题】从Android中的firebase下载多个文件时如何管理内存【英文标题】:How to manage memory when downloading multiple files from firebase in Android 【发布时间】:2021-11-24 19:07:38 【问题描述】:我在 Firebase 存储中有一个图像目录,我正在尝试将该目录中的所有文件下载到我的应用程序中。每个图像在数据库中都有一个相应的字段来存储其名称。下载大多成功,但是当您尝试与 ui 交互时,应用程序冻结并收到应用程序无响应提示,或者在您等待一段时间后崩溃。
下面是我用来将图像下载到我的应用程序存储目录的代码 '''
private void downloadAllTopicsAndFirstItems(Activity activity)
databaseReference.child(FIREBASE_TOPIC_NODE).addValueEventListener(new ValueEventListener()
@Override
public void onDataChange(@NonNull DataSnapshot snapshot)
int count = 0;
for (DataSnapshot snap : snapshot.getChildren())
Topic topic = snap.getValue(Topic.class);
if (topic == null) return;
if (topic.getItems() != null) topic.setTotalItems(topic.getItems().size());
File fileTopic = new File(activity.getFilesDir(), topic.getFirebaseImageNode() + ".jpg");
Topic topic1 = new Topic();
LiveData<Topic> topicWithId = viewModel.getTopicWithId(topic.getId());
topicWithId.observe((LifecycleOwner) activity, topic2 ->
if (topic2 != null)
topic1.setId(topic2.getId());
topic1.setFirebaseImageNode(topic2.getFirebaseImageNode());
topic1.setImageUrl(topic2.getImageUrl());
topic1.setTitle(topic2.getTitle());
topic1.setDescription(topic2.getDescription());
topicWithId.removeObservers((LifecycleOwner) activity);
);
if (!fileTopic.exists())
int finalCount = count;
storageReference.child(FIREBASE_APP_IMAGES).child(topic.getFirebaseImageNode() + ".jpg")
.getFile(fileTopic)
.addOnSuccessListener(taskSnapshot ->
topic.setLocalStorageUri(fileTopic.getAbsolutePath());
if (finalCount <6) getTopicItems(topic.getItems(), activity);
if (topic.equals(topic1)) viewModel.updateTopic(topic);
else viewModel.insertTopics(topic);
Log.d("Topic Image", "onSuccess: downloaded to " + fileTopic.getAbsolutePath());
)
.addOnFailureListener(e -> Log.d("Topic Image", "onFailure: " + topic.getFirebaseImageNode() + ".jpg" + e.toString()));
else
topic.setLocalStorageUri(fileTopic.getAbsolutePath());
if (count<6) getTopicItems(topic.getItems(), activity);
if (topic.equals(topic1)) viewModel.updateTopic(topic);
else viewModel.insertTopics(topic);
topics.add(topic);
viewModel.insertTopics(topic);
Log.d("Topic ", topic.toString());
count++;
if (topics.size() == snapshot.getChildrenCount())
Log.d("Topics", "onDataChange: " + topics.toString());
topicRvAdapter.setItems(topics);
progressBar.setVisibility(View.GONE);
@Override
public void onCancelled(@NonNull DatabaseError error)
if (error.getCode() == DatabaseError.DISCONNECTED)
errorView.setText(R.string.no_internet);
else
errorView.setText(R.string.something_went_wrong);
progressBar.setVisibility(View.GONE);
);
'''
以下方法下载单个主题中包含的项目。 注意:有些主题有超过 200 项
'''
private void getTopicItems(ArrayList<Item> items, Activity activity)
if (items != null)
for (Item item : items)
Item item1 = new Item();
LiveData<Item> itemWithId = viewModel.getItemWithId(item.getId());
itemWithId.observe((LifecycleOwner) activity, item2 ->
if (item2 != null)
item1.setId(item2.getId());
item1.setImageUrl(item2.getImageUrl());
item1.setEnglishWord(item2.getEnglishWord());
item1.setCategory(item2.getCategory());
item1.setTopic(item2.getTopic());
item1.setRutooroWord(item2.getRutooroWord());
item1.setFirebaseImageNode(item2.getFirebaseImageNode());
itemWithId.removeObservers((LifecycleOwner) activity);
);
// download the item image to storage;
String itemNode = item.getFirebaseImageNode();
File file = new File(activity.getFilesDir(), itemNode + ".jpg");
if (itemNode != null)
if (!file.exists())
storageReference.child(FIREBASE_APP_IMAGES).child(itemNode + ".jpg")
.getFile(file)
.addOnSuccessListener(taskSnapshot ->
item.setLocalStorageUri(file.getAbsolutePath());
if (item.equals(item1)) viewModel.updateItem(item);
else viewModel.insertItems(item);
Log.d("Topic Item Image", "onSuccess: downloaded to " + file.getAbsolutePath());
)
.addOnFailureListener(e -> Log.d("Topic Item Image", "onFailure: " + itemNode + ".jpg" + e.toString()));
else
item.setLocalStorageUri(file.getAbsolutePath());
if (item.equals(item1)) viewModel.updateItem(item);
else viewModel.insertItems(item);
else
if (item.equals(item1)) viewModel.updateItem(item);
else viewModel.insertItems(item);
'''
一两分钟后,应用程序崩溃并出现以下错误
''' E/androidRuntime: 致命异常: main 进程:com.allez.san.myapplication,PID:18718 java.lang.OutOfMemoryError: 无法分配 24 字节分配,其中 2000472 空闲字节和 1953KB 直到 OOM,目标占用空间 201326592,增长限制 201326592;由于碎片而失败(最大可能的连续分配 150470656 字节) 在 java.lang.reflect.Executable.getMethodOrConstructorGenericInfoInternal(Executable.java:708) 在 java.lang.reflect.Executable.getGenericParameterTypes(Executable.java:270) 在 java.lang.reflect.Method.getGenericParameterTypes(Method.java:212) 在 com.google.firebase.database.core.utilities.encoding.CustomClassMapper$BeanMapper.deserialize(CustomClassMapper.java:588) 在 com.google.firebase.database.core.utilities.encoding.CustomClassMapper$BeanMapper.deserialize(CustomClassMapper.java:563) 在 com.google.firebase.database.core.utilities.encoding.CustomClassMapper.convertBean(CustomClassMapper.java:433) 在 com.google.firebase.database.core.utilities.encoding.CustomClassMapper.deserializeToClass(CustomClassMapper.java:232) 在 com.google.firebase.database.core.utilities.encoding.CustomClassMapper.deserializeToType(CustomClassMapper.java:179) 在 com.google.firebase.database.core.utilities.encoding.CustomClassMapper.deserializeToParameterizedType(CustomClassMapper.java:246) 在 com.google.firebase.database.core.utilities.encoding.CustomClassMapper.deserializeToType(CustomClassMapper.java:177) 在 com.google.firebase.database.core.utilities.encoding.CustomClassMapper.access$100(CustomClassMapper.java:48) 在 com.google.firebase.database.core.utilities.encoding.CustomClassMapper$BeanMapper.deserialize(CustomClassMapper.java:593) 在 com.google.firebase.database.core.utilities.encoding.CustomClassMapper$BeanMapper.deserialize(CustomClassMapper.java:563) 在 com.google.firebase.database.core.utilities.encoding.CustomClassMapper.convertBean(CustomClassMapper.java:433) 在 com.google.firebase.database.core.utilities.encoding.CustomClassMapper.deserializeToClass(CustomClassMapper.java:232) 在 com.google.firebase.database.core.utilities.encoding.CustomClassMapper.convertToCustomClass(CustomClassMapper.java:80) 在 com.google.firebase.database.DataSnapshot.getValue(DataSnapshot.java:203) 在 com.allez.san.learnrutooro.utils.DownloadUtil$3.onDataChange(DownloadUtil.java:296) 在 com.google.firebase.database.core.ValueEventRegistration.fireEvent(ValueEventRegistration.java:75) 在 com.google.firebase.database.core.view.DataEvent.fire(DataEvent.java:63) 在 com.google.firebase.database.core.view.EventRaiser$1.run(EventRaiser.java:55) 在 android.os.Handler.handleCallback(Handler.java:938) 在 android.os.Handler.dispatchMessage(Handler.java:99) 在 android.os.Looper.loop(Looper.java:246) 在 android.app.ActivityThread.main(ActivityThread.java:8512) 在 java.lang.reflect.Method.invoke(本机方法) 在 com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602) 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1139) I/Process:发送信号。 PID:18718 SIG:9
'''
【问题讨论】:
您在该目录中有多少张图片? 正如我上面提到的,有些商品有 200 多张图片,因此粗略估计大约有 1000 张图片 尝试使用recursive method。 【参考方案1】:您正在并行下载所有文件,这意味着内存消耗会随着文件数量的增加而增加。要限制内存消耗,请逐个下载文件,或使用合理的最大并行下载次数。
【讨论】:
我认为 'storageReference.getFile()' api 会在不同线程上下载文件。如果是这样,我有办法更好地管理这种行为。 下载发生在哪里并不重要:只要它们都并行发生,内存使用量就会随着您下载的文件数量而增加。解决方案是限制您并行下载的文件数量。执行此操作的最简单方法是仅在当前文件完成后从下一个文件开始,例如保留要下载的项目列表,然后检查当前文件完成后是否还有更多项目要下载。以上是关于从Android中的firebase下载多个文件时如何管理内存的主要内容,如果未能解决你的问题,请参考以下文章
Firebase 存储 - 在 Android 中上传多个图像文件
如何从android下载firebase数据库作为CSV文件
从 Fabric Beta 下载 Android APK 文件