Android 从 firebase 下载多个文件时如何管理内存
How to manage memory when downloading multiple files from firebase in Android
我在 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
'''
您正在并行下载所有文件,这意味着内存消耗将随着文件数量的增加而增加。要限制内存消耗,请一个一个地下载文件,或者使用合理的最大并行下载数。
我在 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
'''
您正在并行下载所有文件,这意味着内存消耗将随着文件数量的增加而增加。要限制内存消耗,请一个一个地下载文件,或者使用合理的最大并行下载数。