从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 文件

从多个文件上传 Firebase 存储中获取下载 url

无法从 Android 中的 Firebase 存储获取下载网址 [重复]

如何从firebase检索多个数据到android中的列表视图?