Fragment onSaveInstanceState中的Android OutOfMemory错误

时间:2016-01-18 12:21:41

标签: android android-fragments android-recyclerview android-bitmap android-savedstate

我有一个带有RecyclerView的片段,它使用ImageView保存项目(基本上像图库应用程序)。使用异步任务显示图像以在单独的线程中完成工作。图像从base64编码的字符串显示。图像也用lrucache缓存。

问题
一切正常,直到我第三次或第四次旋转设备。在onSaveInstanceState方法中,设备因内存不足错误而崩溃。

问题
有任何想法如何防止OutOfMemory错误?提前致谢

代码

活动

public class TabsActivity extends BaseActivity implements ViewPager.OnPageChangeListener,
    ActivityActions, TabLayout.OnTabSelectedListener {
private static final String TAG = TabsActivity.class.getSimpleName();

private static final String STATE_TAB_LAYOUT = "STATE_TAB_LAYOUT";
private static final String STATE_TOOLBAR = "STATE_TOOLBAR";
public static final String ACTION_TASKS = "ACTION_TASKS";
public static final String ACTION_MESSAGES = "ACTION_MESSAGES";

public static final int REQUEST_TASK_UPDATE = 1;
public static final int REQUEST_MESSAGE_UPDATE = 2;

private Toolbar toolbar;
private TabLayout tabLayout;
private ViewPager viewPager;
private FloatingActionButton createButton;

private PrefsManager prefsManager;
private ViewPagerAdapter adapter;
private LocalBroadcastManager broadcastManager;
private NotificationReceiver notificationReceiver;
private FragmentManager.OnBackStackChangedListener backStackChangedListener;

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

    prefsManager = PrefsManager.getInstance(this);
    notificationReceiver = new NotificationReceiver();
    backStackChangedListener = new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
                onSettingsFragmentStateChanged(false);
            }
        }
    };

    tabLayout = (TabLayout) findViewById(R.id.tab_layout);
    toolbar = (Toolbar) findViewById(R.id.toolbar);
    viewPager = (ViewPager) findViewById(R.id.view_pager);
    createButton = (FloatingActionButton) findViewById(R.id.floating_action_button);
    createButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            KeyboardUtils.hideSoftwareInput(TabsActivity.this);
            if (adapter.getCurrentTab(viewPager.getCurrentItem()) == Tab.TASKS) {
                createNewTask();
            } else {
                createNewConversation();
            }
        }
    });

    setSupportActionBar(toolbar);
    toolbar.setNavigationIcon(R.drawable.ic_action_back);
    toolbar.setNavigationOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            onBackPressed();
        }
    });

    final String action = getIntent().getAction();
    adapter = new ViewPagerAdapter(getSupportFragmentManager());
    adapter.setAdapterListener(new ExtendedPagerAdapter.AdapterListener() {
        @Override
        public void onAdapterInstantiated() {
            if (savedInstanceState == null && action != null) {
                if (action.equals(ACTION_TASKS)) {
                    onPageSelected(viewPager.getCurrentItem());
                } else {
                    viewPager.setCurrentItem(1);
                }
            }
        }
    });
    viewPager.addOnPageChangeListener(this);
    viewPager.setAdapter(adapter);
    tabLayout.setupWithViewPager(viewPager);
    tabLayout.setOnTabSelectedListener(this);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_TASK_UPDATE) {
        if (resultCode == Activity.RESULT_OK) {
            ((TasksFragment) adapter.getFragment(Tab.TASKS)).onTasksUpdated();
        }
    } else if (requestCode == REQUEST_MESSAGE_UPDATE) {
        TabFragment tabFragment = adapter.getFragment(Tab.MESSAGES);
        if (resultCode == Activity.RESULT_OK) {
            if (tabFragment != null) {
                ((MessagesFragment) tabFragment).onNewMessagesReceived();
            }
        } else {
            ((MessagesFragment) tabFragment).initData();
        }
    }
    super.onActivityResult(requestCode, resultCode, data);
}

@Override
protected void onResume() {
    broadcastManager = LocalBroadcastManager.getInstance(this);
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(BroadcastConfig.ACTION_NEW_MESSAGE);
    intentFilter.addAction(BroadcastConfig.ACTION_USER_STATUS);
    broadcastManager.registerReceiver(notificationReceiver, intentFilter);
    getSupportFragmentManager().addOnBackStackChangedListener(backStackChangedListener);
    super.onResume();
}

@Override
protected void onPause() {
    broadcastManager.unregisterReceiver(notificationReceiver);
    getSupportFragmentManager().removeOnBackStackChangedListener(backStackChangedListener);
    super.onPause();
}

@Override
protected void onRestoreInstanceState(Bundle inState) {
    ActionBar actionBar = getSupportActionBar();
    if (actionBar != null) {
        actionBar.setTitle(inState.getString(STATE_TOOLBAR));
    }
    if (!inState.getBoolean(STATE_TAB_LAYOUT)) {
        onSettingsFragmentStateChanged(true);
    }
    super.onRestoreInstanceState(inState);
}

@Override
public void onSaveInstanceState(Bundle outState) {
    outState.putBoolean(STATE_TAB_LAYOUT, tabLayout.getVisibility() == View.VISIBLE);
    outState.putString(STATE_TOOLBAR, toolbar.getTitle().toString());
    super.onSaveInstanceState(outState);
}

@Override
public void onBackPressed() {
    MenuItem menuItem = toolbar.getMenu().findItem(R.id.action_search);
    if (menuItem != null && !((SearchView) menuItem.getActionView()).isIconified()) {
        ((SearchView) menuItem.getActionView()).onActionViewCollapsed();
        return;
    }
    if (popSupportBackStack(SettingsFragment.class.getSimpleName())) {
        return;
    }
    setResult(RESULT_OK);
    finish();
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_contacts, menu);
    SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
    searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
    searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            return false;
        }

        @Override
        public boolean onQueryTextChange(String newText) {
            adapter.getFragment(viewPager.getCurrentItem()).onSearchPhraseChanged(newText);
            return true;
        }
    });
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.action_settings:
            onSettingsClick();
            break;
        case R.id.action_refresh:
            onRefreshClick();
            break;
        default:
            break;
    }
    return super.onOptionsItemSelected(item);
}

@Override
public void onPageSelected(int position) {
    if (adapter.isInstantiated()) {
        onToolbarTitleChanged(adapter.getPageTitle(position).toString());
        onToolbarSubtitleChanged(UserStatus.NONE);
    }
}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}

@Override
public void onPageScrollStateChanged(int state) {
}

@Override
public void onTabSelected(TabLayout.Tab tab) {
    if (adapter.getCurrentTab(tab.getPosition()).getFragmentTitle()
            == Tab.ATTACHMENT_HISTORY.getFragmentTitle()) {
        createButton.hide();
    }
    viewPager.setCurrentItem(tab.getPosition());
}

@Override
public void onTabUnselected(final TabLayout.Tab tab) {
    createButton.hide(new FloatingActionButton.OnVisibilityChangedListener() {
        @Override
        public void onHidden(FloatingActionButton fab) {
            fab.show();
        }
    });
}

@Override
public void onTabReselected(TabLayout.Tab tab) {
}

@Override
public void requestDisplayDetails(Intent intent, int requestCode) {
    startActivityForResult(intent, requestCode);
}

@Override
public void refreshTasks() {
    adapter.getFragment(Tab.TASKS).onRefresh();
}

@Override
public void refreshMessages() {
    adapter.getFragment(Tab.MESSAGES).onRefresh();
}

@Override
public boolean isNetworkAvailable() {
    return checkNetworkAvailability();
}

private void createNewTask() {
    startActivityForResult(new Intent(this, CreateTaskActivity.class), REQUEST_TASK_UPDATE);
}

private void createNewConversation() {
    MessagesFragment fragment = (MessagesFragment) adapter.getFragment(Tab.MESSAGES);
    startActivityForResult(new Intent(TabsActivity.this, MessageDetailsActivity.class)
            .setAction(MessageDetailsActivity.ACTION_CREATE_MESSAGE)
            .putExtra(MessageDetailsActivity.EXTRA_EXIST_CONV,
                    fragment.getCreatedConversations()), REQUEST_MESSAGE_UPDATE);
}

private void setTabLayoutVisible(boolean visible) {
    int visibility = visible ? View.VISIBLE : View.GONE;
    tabLayout.setVisibility(visibility);
}

private void onRefreshClick() {
    if (checkNetworkAvailability()) {
        adapter.getFragment(viewPager.getCurrentItem()).onRefresh();
    }
}

private void onSettingsClick() {
    getSupportFragmentManager()
            .beginTransaction()
            .add(R.id.fragment_container, new SettingsFragment(), SettingsFragment.class.getSimpleName())
            .addToBackStack(SettingsFragment.class.getSimpleName())
            .commit();
    onSettingsFragmentStateChanged(true);
}

private void onSettingsFragmentStateChanged(boolean visibleState) {
    if (visibleState) {
        isFragmentDialog = true;
        onToolbarTitleChanged(getString(R.string.title_settings));
        setTabLayoutVisible(false);
        createButton.hide();
    } else {
        onPageSelected(viewPager.getCurrentItem());
        setTabLayoutVisible(true);
        if (adapter.getCurrentTab(viewPager.getCurrentItem()) != Tab.ATTACHMENT_HISTORY) {
            createButton.show();
        }
    }
}

public void onToolbarTitleChanged(String title) {
    toolbar.setTitle(title);
}

public void onToolbarSubtitleChanged(UserStatus userStatus) {
    if (userStatus != null) {
        toolbar.setSubtitle(userStatus.getText());
        toolbar.setSubtitleTextColor(userStatus.getColor());
    }
}

public void onLogoutConfirmed() {
    HttpRequestManager.logout(prefsManager.getPhpSessId(), App.getPhoneId(this), prefsManager.getUserId(),
            new HttpCallback<LogoutResponse>() {
                @Override
                public void onResponse(LogoutResponse logoutResponse) {
                    prefsManager.reset();
                    DBManager.delete(TabsActivity.this, DeleteTask.DeleteType.ALL, new Callback<Boolean>() {
                        @Override
                        public void onResponseReceived(Boolean params) {
                            startActivity(new Intent(TabsActivity.this, LoginActivity.class)
                                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
                            finish();
                        }
                    });
                }
            });
}

private class NotificationReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action.equals(BroadcastConfig.ACTION_NEW_MESSAGE) || action.equals(BroadcastConfig.ACTION_USER_STATUS)) {
            TabFragment childFragment = adapter.getFragment(Tab.MESSAGES);
            if (childFragment != null) {
                ((MessagesFragment) childFragment).onNewMessagesReceived();
            }
        }
    }
}

private class ViewPagerAdapter extends ExtendedPagerAdapter {

    private final List<Tab> tabs = Tab.getAllTabs();
    private final List<TabFragment> fragments = new ArrayList<>();

    public ViewPagerAdapter(FragmentManager fragmentManager) {
        super(fragmentManager);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        fragments.add((TabFragment) super.instantiateItem(container, position));
        return fragments.get(fragments.size() - 1);
    }

    @Override
    public Fragment getItem(int position) {
        Log.e(TAG, "CreatingFragment: " + tabs.get(position).getFragmentClass().getCanonicalName());
        return Fragment.instantiate(TabsActivity.this, tabs.get(position).getFragmentClass().getCanonicalName());
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return getString(tabs.get(position).getFragmentTitle());
    }

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

    public Tab getCurrentTab(int position) {
        return tabs.get(position);
    }

    public TabFragment getFragment(int position) {
        if (getCount() > position) {
            return fragments.get(position);
        }
        return null;
    }

    public TabFragment getFragment(Tab tab) {
        for (TabFragment tabFragment : fragments) {
            if (tab.getFragmentClass() == tabFragment.getClass()) {
                return tabFragment;
            }
        }
        return null;
    }
}

}

片段

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    swipeRefresh = (SwipeRefresh) inflater.inflate(R.layout.fragment_recycler_view, container, false);
    galleryView = (RecyclerView) swipeRefresh.findViewById(R.id.recycler_view);
    return swipeRefresh;
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    swipeRefresh.setOnRefreshListener(this);
    galleryView.setHasFixedSize(true);
    galleryView.setLayoutManager(new GridLayoutManager(getContext(), getSpanCount()));
    galleryView.setAdapter(adapter = new Adapter(attachments));
    if (savedInstanceState == null) {
        onRefresh();
    } else {
        onRestoreInstanceState(savedInstanceState);
    }
}

@Override
public void onSaveInstanceState(Bundle outState) {
    try {
        outState.putString(STATE_ATTACHMENTS, Json.fromObject(attachments)); //Crashes here after 3rd or 4th rotate
    } catch (JsonProcessingException e) {
        Log.e(TAG, "Error while saving attachments", e);
    }
    super.onSaveInstanceState(outState);
}

@Override
public void onSearchPhraseChanged(String phrase) {
}

@Override
public void onRefresh() {
    swipeRefresh.setRefreshing(true);
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            initSampleData();
        }
    }, 3000);
}

private void onRestoreInstanceState(Bundle savedInstanceState) {
    try {
        attachments = Json.toCollection(savedInstanceState.getString(STATE_ATTACHMENTS),
                ArrayList.class, Attachment.class);
        adapter.notifyDataSetChanged();
    } catch (IOException e) {
        Log.e(TAG, "Error while restoring attachments", e);
    }
}

private void initSampleData() {
    swipeRefresh.setRefreshing(false);
    String image = "";
    File path = new File(Environment.getExternalStorageDirectory(), "image.txt");
    byte[] bytes = new byte[(int) path.length()];
    try {
        FileInputStream fileInputStream = new FileInputStream(path);
        fileInputStream.read(bytes);
        image = new String(bytes);
    } catch (FileNotFoundException e) {
        Log.e(TAG, "Error while finding file to read from", e);
    } catch (IOException e) {
        Log.e(TAG, "Error while writing from file to string", e);
    }

    Attachment attachment = new Attachment(image);
    attachments.clear();
    for (int i = 0; i < 100; i++) {
        attachment.setFileName(String.valueOf(i));
        attachments.add(attachment);
    }
    adapter.notifyDataSetChanged();
}

private int getSpanCount() {
    DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
    float width = displayMetrics.widthPixels / displayMetrics.density;
    float height = displayMetrics.heightPixels / displayMetrics.density;
    int size;
    if (Math.min(width, height) >= 600) {
        size = Math.round(width / THUMBNAIL_SIZE_TABLET);
    } else {
        size =  Math.round(width / THUMBNAIL_SIZE_PHONE);
    }
    return size < 7 ? size : 6;
}

private class ViewHolder extends BaseHolder<Attachment> implements View.OnClickListener {

    private ImageView imageView;

    public ViewHolder(ViewGroup viewGroup, int layoutRes) {
        super(viewGroup, layoutRes);
        imageView = (ImageView) itemView;
    }

    @Override
    public void bind(Attachment attachment) {
        itemView.setOnClickListener(this);
        if (attachment.getImage() != null && !attachment.getImage().isEmpty()) {
            DisplayThumbnailRequest.loadBitmap(attachment, imageView);
        }
    }

    @Override
    public void onClick(View v) {
        // TODO: 2016-01-15 Open image in fullscreen
    }
}

private class Adapter extends RecyclerView.Adapter<ViewHolder> {

    private List<Attachment> attachments;

    public Adapter(List<Attachment> attachments) {
        this.attachments = attachments;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolder(parent, R.layout.adapter_item_attachment_history);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.bind(attachments.get(position));
    }

    @Override
    public int getItemCount() {
        return attachments.size();
    }
}


AsyncTask

public class DisplayThumbnailRequest extends AsyncHttpTask<Attachment, Void, Bitmap> {
private static final String TAG = DisplayThumbnailRequest.class.getSimpleName();

private WeakReference<ImageView> imageViewRef;
private Attachment attachment;

public DisplayThumbnailRequest(ImageView imageView) {
    this.imageViewRef = new WeakReference<>(imageView);
}

@Override
protected void onPreExecute() {
    if (imageViewRef != null) {
        ImageView imageView = imageViewRef.get();
        if (imageView != null) {
            if (imageView.getVisibility() != View.VISIBLE) {
                imageView.setVisibility(View.VISIBLE);
            }
            imageView.setImageResource(R.mipmap.ic_launcher);
        }
    }
}

@Override
protected Bitmap doInBackground(Attachment... params) {
    attachment = params[0];
    Bitmap bitmap = BitmapCache.getBitmap(attachment.getFileName());
    if (bitmap == null) {
        bitmap = BitmapUtils.fromBase64(attachment.getImage());
        BitmapCache.addBitmap(attachment.getFileName(), bitmap);
    }
    return bitmap;
}

@Override
protected void onPostExecute(Bitmap bitmap) {
    if (imageViewRef != null && bitmap != null) {
        ImageView imageView = imageViewRef.get();
        if (imageView != null) {
            imageView.setImageBitmap(bitmap);
        }
    }
}

@Override
protected void onResponseReceived() {
}

public Attachment getAttachment() {
    return attachment;
}

public static void loadBitmap(Attachment attachment, ImageView imageView) {
    if (cancelDownloadRequest(attachment, imageView)) {
        DisplayThumbnailRequest request = new DisplayThumbnailRequest(imageView);
        AsyncBitmapDrawable drawable = new AsyncBitmapDrawable(App.getRes(), null, request);
        imageView.setImageDrawable(drawable);
        request.execute(attachment);
    }
}

private static boolean cancelDownloadRequest(Attachment attachment, ImageView imageView) {
    DisplayThumbnailRequest request = getDownloadTask(imageView);
    if (request != null) {
        String filePath = request.getAttachment().getFileName();
        if (filePath == null || filePath.isEmpty() || !filePath.equals(attachment.getFileName())) {
            request.cancel(true);
        } else {
            return false;
        }
    }
    return true;
}

private static DisplayThumbnailRequest getDownloadTask(ImageView imageView) {
    if (imageView != null) {
        Drawable drawable = imageView.getDrawable();
        if (drawable instanceof AsyncBitmapDrawable) {
            return ((AsyncBitmapDrawable) drawable).getDisplayThumbnailRequest();
        }
    }
    return null;
}
}


BitmapCache

public class BitmapCache {
private static final String TAG = BitmapCache.class.getSimpleName();

private static final int MAX_MEMORY = (int)((Runtime.getRuntime().maxMemory() / 1024) / 4);
private static LruCache<String, Bitmap> lruCache = new LruCache<String, Bitmap>(MAX_MEMORY) {
    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getByteCount() / 1024;
    }
};

public static void addBitmap(String key, Bitmap bitmap) {
    if (getBitmap(key) == null) {
        lruCache.put(key, bitmap);
    }
}

public static Bitmap getBitmap(String key) {
    return lruCache.get(key);
}
}


BitmapUtils.fromBase64

public static Bitmap fromBase64(String string) {
    if (string != null && !string.isEmpty()) {
        byte[] decodedString = Base64.decode(string, Base64.DEFAULT);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length, options);

        options.inSampleSize = getInSampleSize(options, Metrics.dp2px(100), Metrics.dp2px(100));
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length, options);
    }
    return null;
}

修改
我尝试将onSavedInstanceState中保存的arraylist大小从100减少到50,并且不显示OOM错误(无论你旋转多少次设备)。可以,保存的实例太长(如果列表有100个项目)并且它会淹没内存?

1 个答案:

答案 0 :(得分:0)

问题出在您的身上 onRefresh()方法。

如果活动停止,您应该从处理程序中删除所有回溯。

试试这个:

private Handler handler = new Handler();

@Override
public void onRefresh() {
    swipeRefresh.setRefreshing(true);
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            initSampleData();
        }
    }, 3000);
}

@Override
protected void onStop() {
   super.onStop();
   handler.removeCallbacksAndMessages(null);
}