美文网首页
浅谈Android中手动保存Activity状态方案

浅谈Android中手动保存Activity状态方案

作者: f1abf4d2779c | 来源:发表于2017-07-20 14:03 被阅读78次

    众所周知,一个Activity一旦调用了它的finish方法,那么这个Activity将会被destroy掉。无论这个Activity是否有引起内存泄漏,也只是让Activity的对象被引用,它作为窗口宿主,它内部的所有控件状态都会被释放。那么,如果我们遇到这种需求,我们要如何才能保存Activity 的状态以让它可以在下次打开时恢复呢?

    方案一:

    手动记录当前Activity需要恢复的控件状态,并在Activity重新执行onCreate时恢复保存的控件状态。
    这里以最常见的保存RecyclerView的滚动状态为例:

    /**
     * 保存RecyclerView的滚动状态并恢复
     *
     * @author zhushuhang
     * @Date 17/6/8 下午3:55
     */
    public class ChatListStateHelper {
    
        private static Map<String, StateInfo> mStateInfos = new HashMap<>();
    
        // 绑定要保存状态的RecyclerView
        public void attachToRecylerView(final ChatRecyclerView chatRecyclerView) {
            try {
                chatRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                    @Override
                    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                        super.onScrollStateChanged(recyclerView, newState);
                        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                            // 停止滚动时保存状态
                            attachRecyclerViewStateInfo(chatRecyclerView);
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        private void attachRecyclerViewStateInfo(ChatRecyclerView recyclerView) {
            //获取可视的第一个view
            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
            if (linearLayoutManager == null) {
                return;
            }
            View topView = linearLayoutManager.getChildAt(0);
            if (topView != null) {
                //获取与该view的顶部的偏移量
                int lastOffset = topView.getBottom() - recyclerView.computeVerticalScrollExtent();
                //得到该View的数组位置
                int lastPosition = linearLayoutManager.getPosition(topView);
                updateStateInfo(recyclerView, lastOffset, lastPosition);
            }
        }
    
        // 更新RecyclerView状态信息
        private void updateStateInfo(ChatRecyclerView recyclerView, int offset, int position) {
            String identitier = recyclerView.getIdentitier();
            StateInfo stateInfo = mStateInfos.get(identitier);
            if (stateInfo == null) {
                stateInfo = new StateInfo();
            }
            stateInfo.offset = offset;
            stateInfo.position = position;
            BaseChatAdapter adapter = (BaseChatAdapter) recyclerView.getAdapter();
            ChatMessageInfo messageInfo = (ChatMessageInfo) adapter.getData(position);
            stateInfo.chatId = messageInfo.getChatId();
            mStateInfos.put(identitier, stateInfo);
        }
    
        // 获取RecyclerView状态信息
        private StateInfo getStateInfo(ChatRecyclerView chatRecyclerView) {
            String identitier = chatRecyclerView.getIdentitier();
            return mStateInfos.get(identitier);
        }
    
        // 恢复RecyclerView的上次保存状态
        public void restoreStatus(ChatRecyclerView chatRecyclerView) {
            StateInfo stateInfo = getStateInfo(chatRecyclerView);
            if (stateInfo == null) {
                return;
            }
            int position = stateInfo.position;
            int offset = stateInfo.offset;
            long chatId = stateInfo.chatId;
            BaseChatAdapter adapter = (BaseChatAdapter) chatRecyclerView.getAdapter();
            for (int i = position; i < adapter.getDataCount(); i++) {
                ChatMessageInfo chatMessageInfo = (ChatMessageInfo) adapter.getData(i);
                if (chatMessageInfo != null && chatMessageInfo.getChatId() == chatId) {
                    LinearLayoutManager linearLayoutManager = (LinearLayoutManager) chatRecyclerView.getLayoutManager();
                    if (chatRecyclerView.getLayoutManager() != null && i >= 0) {
                        linearLayoutManager.scrollToPositionWithOffset(i, -offset);
                        stateInfo.position = i;
                        updateStateInfo(chatRecyclerView, offset, i);
                    }
                    break;
                }
            }
        }
    
        // RecyclerView状态信息
        private static class StateInfo {
            int offset; // 当前position的偏移量
            int position; // 当前列表位置, 下次进入时从此位置开始向上寻找目标消息索引
            long chatId; // 目标消息ID
        }
    
    }
    

    缺点:保存状态十分麻烦,如果有多个控件的状态需要保存,则每个控件都需要单独处理。而且不利于扩展。

    方案二:

    基于第一种方案的实现,我们会得出一个结论,为了避免状态被释放,我们应该尽量让Activity不被销毁, 那么除了第一种思路外,我们有没有什么办法能让Activity既不被销毁,又可以达到回退到上层页面的效果呢?

    为了实现这种效果,我们这里先简单介绍一下INTENT FLAG:
    FLAG_ACTIVITY_REORDER_TO_FRONT

    官方API解释

    If set in an Intent passed to Context.startActivity(), this flag will cause the launched activity to be brought to the front of its task's history stack if it is already running.

    如果在Context.startActivity()时传递的Intent中设置此标志,那么启动的Activity如果已经在当前栈中存在,那么将它加载到栈顶,如果不存在,那将生成一个新的Activity。

    如Google所解释,FLAG_ACTIVITY_REORDER_TO_FRONT这个FLAG可以将一个已存在的Activity移动至栈顶,而不用重新创建。结合这个点,我们可以在当前需要保存状态的Activity回退的时候,使用FLAG_ACTIVITY_REORDER_TO_FRONT标志将它之前的页面移动至栈顶,配合Activity间的切换动画,我们可以让用户感觉当前页面已经被关闭,回到了上个页面,但实际只是调整了栈内的Activity 顺序,在需要恢复Activity的时候,同样使用FLAG_ACTIVITY_REORDER_TO_FRONT标志将已存在的Activity移动至栈顶。因为Activity实际上并没有并销毁,所以也无需保存任何状态。
    代码示例:

    Intent intent = new Intent(context, Global.getGameActivity().getClass());
    intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
    context.startActivity(intent);
    

    方案三:

    启动模式 + 亲和力 + 多个栈管理, 这种方式是第二种方式的延伸,虽然可以解决状态恢复的问题,但并不建议这种方案,请在特定的场景下使用。
    具体思路如下:
    我们知道activity 的四个启动模式,singleTop、singleTask、singleInstance、standard,除了singleInstance外,其他几个其他模式都是在当前栈中判断。我们可以让需要恢复状态的Activity群组在单独的栈中运行,使用不同的启动模式来达到重用的目的。

    相关文章

      网友评论

          本文标题:浅谈Android中手动保存Activity状态方案

          本文链接:https://www.haomeiwen.com/subject/wtprkxtx.html