运行 内存不足,有碎片

running out of memory with fragments

我正在使用 android studio 帮助制作一个图书应用程序。这本书由 35 张图片组成,每张图片的文件大小约为 4.8MB(参见编辑)。我们目前通过使用选项卡式活动并为每个页面创建一个片段来处理它们。但这意味着同时处理所有片段(所有 35 张图像),这会导致内存问题。在 android 工作室崩溃之前,我可以在我的 phone 上获得 16 张图像,而项目中的其他人可以使用模拟器在他的笔记本电脑上显示 33 张图像。

我们有 35 个 java 类 遵循此模板:

package com.example.leesie.bookautism;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;

/**
 * Created by leesie on 23/08/2017.
 */

public class Tab1Fragment extends Fragment {
    private static final String TAG = "Tab1Fragment";

    private Button btnTEST1;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.tab1_fragment,container,false);

        btnTEST1 = (Button) view.findViewById(R.id.btnTEST1);

        btnTEST1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(getActivity(), "TESTING BUTTON CLICK 1",Toast.LENGTH_SHORT).show();
            }
        });

        return view;
    }
}

谁的 xml 文件如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:textSize="25sp"
        android:text="Tab1"
        android:layout_marginTop="40dp"
        android:id="@+id/textTab1"/>

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/Tab1ImageView"
        android:src="@drawable/tab1"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btnTEST1"
        android:text="TESTBTN 1"/>

</RelativeLayout>

主要activity是:

        package com.example.leesie.bookautism;

    import android.os.Bundle;
    //import android.support.design.widget.TabLayout;
    import android.support.v4.view.ViewPager;
    import android.support.v7.app.AppCompatActivity;
    //import android.support.v7.widget.Toolbar;
    import android.util.Log;
    //import android.view.LayoutInflater;
    //import android.view.View;
    //import android.view.ViewGroup;
    //import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

private static final String TAG = "MainActivity";

private SectionsPageAdapter mSectionsPageAdapter;

private ViewPager mViewPager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Log.d(TAG, "onCreate: Starting.");

    /* In case we need a toolbar THIS OR THAT
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    */

    // Create the adapter that will return a fragment for each of the three
    // primary sections of the activity.
    mSectionsPageAdapter = new SectionsPageAdapter(getSupportFragmentManager());

    // Set up the ViewPager with the sections adapter.
    mViewPager = (ViewPager) findViewById(R.id.container);
    setupViewPager(mViewPager);

    /* In case we need tabs THIS OR THAT
    mSectionsPageAdapter = new SectionsPageAdapter(getSupportFragmentManager());

    // Set up the ViewPager with the sections adapter.
    mViewPager = (ViewPager) findViewById(R.id.container);
    setupViewPager(mViewPager);

    TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
    tabLayout.setupWithViewPager(mViewPager);
    */

}

private void setupViewPager(ViewPager viewPager) {
    SectionsPageAdapter adapter = new SectionsPageAdapter(getSupportFragmentManager());
    adapter.addFragment(new CoverFragment(), "COVER");
    adapter.addFragment(new CopyrightFragment(), "COPYRIGHT");
    adapter.addFragment(new Tab1Fragment(), "TAB1");
    adapter.addFragment(new Tab2Fragment(), "TAB2");
    adapter.addFragment(new Tab3Fragment(), "TAB3");
    ...
    adapter.addFragment(new Tab33Fragment(), "TAB33");

    viewPager.setAdapter(adapter);
}

}

最后,页面适配器:

package com.example.leesie.bookautism;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by JohnB on 23/08/2017.
 */

public class SectionsPageAdapter extends FragmentPagerAdapter {

    private final List<Fragment> mFragmentList = new ArrayList<>();
    private final List<String> mFragmentTitleList = new ArrayList<>();

    public void addFragment(Fragment fragment, String title) {
        mFragmentList.add(fragment);
        mFragmentTitleList.add(title);
    }

    public SectionsPageAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return mFragmentTitleList.get(position);
    }

    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }

    @Override
    public int getCount() {
        return mFragmentList.size();
    }
}

我们认为可能可行的解决方案是创建三个活动,其中每个 activity 有三分之一的片段,并且只需从 activity A 移动到 activity B 一次显示 activity A 中的最后一张图片。 这是一个可行的解决方案吗?

这似乎是一种很糟糕的代码编写方式,有没有更好的解决方案?

此外,是否有任何资源可以帮助解决我们的问题?

非常感谢

编辑

我错了,所有图片加起来的文件大小为4.8MB。

首先,4.8MB 的 PNG、JPEG 或 WebP 文件巨大。该图像的分辨率必须在每侧数万像素。没有 Android 设备具有该分辨率的屏幕。另外,作为 Bitmap 的解压缩图像将是数十或数百 MB 的堆 space,这是您不会拥有的。坦率地说,我很难相信您可以在 运行 之前将 16 加载到 OutOfMemoryError.

所以,您需要决定的第一件事是:拥有如此高分辨率的图像有什么意义?现在,您正在使用 ImageView,这意味着您的首要任务是 将这些图像的分辨率降低到合理的水平 。 1080p(1920x1080,取决于所需的宽高比)将是一个合理的起点,或者可能更低,具体取决于您的目标受众。

接下来,您似乎将这些图像作为可绘制资源。如果是这样,请确保它们位于 res/drawable-nodpi/ 或可能 res/drawable-anydpi/ 中,而不是任何其他可绘制目录中。理想情况下,根本不要将它们作为资源,而是使用资产或其他东西,以便您完全控制内存管理。

您也可以重新考虑媒体格式的选择,切换到 HTML。例如,my main book 作为 APK 文件发布,并且磁盘 space 少于您的应用所需,我有 4,500 多页 material,包括数百个屏幕截图和其他图像。

Would this be a possible solution?

不是真的。您的问题不直接与片段有关。您的问题出在图像上,特别是因为您正在使用资源。仅仅因为您使用第二个 activity 不会导致第一个 activity 的内存神奇地释放出来。

This seems a rather poor way to write our code, is there a better solution?

您没有说明您是如何实现这些选项卡的。如果您使用的是基于 ViewPager 的东西,请确保您使用的是 FragmentStatePagerAdapter 或其他一些 PagerAdapter 实现,这些实现不会保留内存中的所有内容。您需要确保其中一些图像尽快从内存中取出,一种方法是确保 ViewPagerPagerAdapter 可以在用户离开时释放页面。

如果您一次显示一个页面,您可以为每个具有生命周期的页面使用一个 ViewPager,这将破坏您未使用的(看不见的)位图。

如果你必须显示所有的拇指,你可以加载 options.inScale = 4 或更多,这样你占用更少的内存 space。