位置: 编程技术 - 正文

【android】正确保存view的状态(android_zh)

编辑:rootadmin

推荐整理分享【android】正确保存view的状态(android_zh),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:android方法大全,“android”,android使用方法,android怎么写,android怎么说,android怎么写,android方法大全,android怎么说,内容如对您有帮助,希望把文章链接给更多的朋友!

英文原文:

转载此译文须注明出处。

今天我们聊一聊安卓中保存和恢复view状态的问题。我刻意强调View状态是因为我发现这个过程要比保存 Activity 和 Fragment状态稍微复杂,还有一个原因是因为网上有太多“重复造的轮子”(有时还是奇丑无比的轮子)。

为什么我们需要保存View的状态?

这个问题问的好!我坚信移动应用应该帮助你解决问题,而不是制造问题。

想象一下一个非常复杂的设置页面:

这并不是从一个移动应用的截图(这不是典型的win程序吗。。),但是适合用于说明我们的问题:

这里有非常多的文字输入控件,多选框,开关(switch)等等,你花了分钟填完所有这些&#;子,总算轮到点击"完成"按钮了,但是突然,你不小心旋转了下屏幕,omg,所有的改动都没了,一切都回归到了初始状态。

当然,总有一些用户喜欢你的app简直到不行,不在乎重新填一次。但是老实说,这样做真的正确吗?(原文有老外常喜欢的喋喋不休的幽默句子,略了)。

别犯傻,我们需要保存用户的修改,除非用户特意让我们不要这样做。

如何保存View的状态?

假设我们这里有一个带有图像,文字和 Switch toggle控件的简单布局:

<LinearLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:padding="@dimen/activity_horizontal_margin"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher"/> <TextView android:layout_width="0dip" android:layout_weight="1" android:layout_height="wrap_content" android:text="My Text"/> <Switch android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dip"/> </LinearLayout>

看吧,非常简单的布局。但是当我们滑动一下switch开关然后旋转屏幕方向,switch又回到了原来的状态。

通常,安卓会自动保存这些View(一般是系统控件)的状态,但是为什么在我们的案例中不起作用了呢?

让我们暂时后退异步,弄明白安卓是如何管理View状态的。这里是正常情况下保存与恢复的示意图:

saveHierarchyState(SparseArray<Parcelable> container)

- 当状态需要保存的时候被安卓framework调用,通常会调用dispatchSaveInstanceState() 。

dispatchSaveInstanceState(SparseArray<Parcelable> container)

- 被saveHierarchyState()调用。 在其内部调用onSaveInstanceState(),并且返回一个代表当前状态的Parcelable。这个Parcelable被保存在container参数中,container参数是一个键&#;对的map集合。View的ID是加键Parcelable是&#;。如果这是一个ViewGroup,还需要遍历其子view,保存子View的状态。

Parcelable onSaveInstanceState()

- 被 dispatchSaveInstanceState()调用。这个方法应该在View的实现中被重写以返回实际的View状态。

restoreHierarchyState(SparseArray<Parcelable> container)

- 在需要恢复View状态的时候被android调用,作为传入的SparseArray参数,包含了在保存过程中的所有view状态。

dispatchRestoreInstanceState(SparseArray<Parcelable> container)

- 被restoreHierarchyState()调用。根据View的ID找出相应的Parcelable,同时传递给onRestoreInstanceState()。如果这是一个ViewGroup,还要恢复其子View的数据。

onRestoreInstanceState(Parcelable state)

- 被dispatchRestoreInstanceState()调用。如果container中有某个view,ViewID所对应的状态被传递在这个方法中。

理解这个过程的重点是,container在整个view层级中是被共享的。我们将看到为什么它这么重要。

既然View的状态是基于它的ID存储的 , 因此如果一个VIew没有ID,那么将不会被保存到container中。没有保存的支点(id),我们也无法恢复没有ID的view的状态,因为不知道这个状态是属于哪个View的。- 译者注。

其实这是安卓的策略,假如我们来做也许会这样设计,大致这样:所有view按照一定的顺序依次存储,在恢复的时候只需知道这个View在保存的时候的顺序就可以了,不过显然这样要耗费更多的开销。

看样子这就是switch开关没有被保存的原因。那我们试试在switch开关上添加id(其他的View也加上id):

<LinearLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:padding="@dimen/activity_horizontal_margin"> <ImageView android:id="@&#;id/image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher"/> <TextView android:id="@&#;id/text" android:layout_width="0dip" android:layout_weight="1" android:layout_height="wrap_content" android:text="My Text"/> <Switch android:id="@&#;id/toggle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dip"/> </LinearLayout>

ok,看结果,确实可行。在configuration changes期间状态是可以保持的。下面是SparseArray的示意图:

就如你看到的那样,每个view都有一个id来把状态保存在container的SparseArray中。

你可能会问这是如何发生的 - 我们并没有提供任意Parcelable来代表状态。答案是 - 安卓处理好了这个事情,安卓知道如何保存系统自带控件的状态。 在经过上面的一番解释之后,这句话来的太迟了吧 -译者注。

除了ID之外,你还需要明确的告诉安卓你的view需要保存状态,调用setSaveEnabled(true)就可以了。通常你不需要对自带的控件这样做,但是如果你从零开始开发一个自定义的view,则需要手动设置(setSaveEnabled)。

【android】正确保存view的状态(android_zh)

要保存view的状态,至少有两点需要满足:

view要有id

要调用setSaveEnabled(true)

现在我们知道如何保存自带控件的状态,但是如果我们有一些自定义的状态,想在configuration变化的时候保持这些状态又该如何呢?

保存自定义的状态

下面,让我们举一个更为复杂的例子。我想在继承自Switch的的类中添加一个自定义的状态:

public class CustomSwitch extends Switch { private int customState;//所谓状态其实就是数据 ....... public void setCustomState(int customState) { this.customState = customState; } }

下面是我们将如何保存这个状态的过程:

5public class CustomSwitch extends Switch { private int customState; ............. public void setCustomState(int customState) { this.customState = customState; } @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); ss.state = customState; return ss; } @Override public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); setCustomState(ss.state); } static class SavedState extends BaseSavedState { int state; SavedState(Parcelable superState) { super(superState); } private SavedState(Parcel in) { super(in); state = in.readInt(); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(state); } public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } }

让我来解释一下上面所做的事情。

首先,既然重写了onSaveInstanceState,我就必须调用其父类的相应方法让父类保存它想保存的所有东西。在我的情况中,Switch将创建一个Parcelable,将状态放进去然后返回给自己。不幸的是,我们无法在这个parcelable中添加更多的状态,因此需要创建一个封装类来封装这个父类的状态,然后放入额外的状态。在安卓中有一个类(View.BaseSavedState)专门做这件事情 - 通过继承它来实现保存上一级的状态同时允许你保存自定义的属性。

在onRestoreInstanceState()期间我们则需要做相反的事情 - 从指定的Parcelable中获取上一级的状态 ,同时让你的父类通过调用super.onRestoreInstanceState(ss.getSuperState())来恢复它的状态。之后我们才能恢复我们自己的状态。

Since you override onSaveInstanceState() - always save super state - state of your super class.

View 的ID必须唯一

现在我们决定将布局放在一个 自定义的view中达到重用的效果,然后在其他的布局中include几次:

注:这里是include了两次。

当我们改变configuration之后,所有的状态都一团糟了,让我们看看在SparseArray中是什么情况:

哈哈!因为状态的保存是基于view id的,而SparseArray container是整个View层次结构中共享的 ,所以view的id必须唯一。否则你的状态就会被另外一个具有相同id的view覆盖。在这里有两个view的id都是@id/toggle,而container只持有一个它的实例- 存储过程中最后到来的一个。

到了恢复数据的时候 - 这两个view都从container那里得到一个相同的状态。

那么该如何解决这个问题?

最直接的答案是 - 每个子view都具有独立的SparseArray container,这样就不会重叠了:

public class MyCustomLayout extends LinearLayout { ......... @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); ss.childrenStates = new SparseArray(); for (int i = 0; i < getChildCount(); i&#;&#;) { getChildAt(i).saveHierarchyState(ss.childrenStates); } return ss; } @Override public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); for (int i = 0; i < getChildCount(); i&#;&#;) { getChildAt(i).restoreHierarchyState(ss.childrenStates); } } @Override protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { dispatchFreezeSelfOnly(container); } @Override protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { dispatchThawSelfOnly(container); } static class SavedState extends BaseSavedState { SparseArray childrenStates; SavedState(Parcelable superState) { super(superState); } private SavedState(Parcel in, ClassLoader classLoader) { super(in); childrenStates = in.readSparseArray(classLoader); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeSparseArray(childrenStates); } public static final ClassLoaderCreator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() { @Override public SavedState createFromParcel(Parcel source, ClassLoader loader) { return new SavedState(source, loader); } @Override public SavedState createFromParcel(Parcel source) { return createFromParcel(null); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } }

让我们过一遍这段乱麻了的代码:

在自定义的布局中没我创建了一个特殊的SaveState类,它持有父类状态以及保存子view状态的独立SparseArray。

在onSaveInstanceState()中我主动存储父类与子view的状态到独立的SparseArray中。

在onRestoreInstanceState()中我主动从保存期间创建的SparseArray中恢复父类和子view的状态。

记住如果这是一个ViewGroup - dispatchSaveInstanceState()还需要遍历子View然后保存它们的状态吗?既然我们现在是手动的了,我需要废弃这种行为。幸运的是使用dispatchFreezeSelfOnly()方法可以告诉安卓只保存viewGroup的状态,不要碰它的子View(在dispatchSaveInstanceState()中调用)。

dispatchRestoreInstanceState()需要做同样的事情 - 调用dispatchThawSelfOnly()。告诉安卓只恢复自身的状态 ,子view我们自己来处理。

下面是SparseArray的示意图:

正如你看到的每个view group都有了独自的SparseArray,因此他们就不会重叠和覆盖彼此了。

状态保存了 赚大了!

这篇文章的代码可以在 GitHub上 找到。

----------------------------------

最后关于id需要独立这一节,要多说几句。

不同布局节点之下的view如果具有相同id,不会影响findViewById() ,正常的安卓程序,很有可能出现id重复,这就是说即使没有使用可重用的自定义的view,也是有必要做如上工作的。

-- 译者注,如果增加了理解的难度忽略次段话。

android app 开发过程中 对于性能优化的总结 一款手机应用从开发过程中就要做好性能优化,这样才能让用户体验度提升,假如我们打开一个应用出现卡顿,不流畅,则会很影响用户对该应用的态

Android UI简介 AndroidSDK包含许多控件,可以使用它们来为应用程序构建用户界面。AndroidSDK提供了文本字段、按钮、列表、网等控件。此外,Android还提供了一组适合移

android-service service和线程有什么关系?有什么区别?1.没关系。service不线程。ServicesAServiceisanapplicationcomponentthatcanperformlong-runningoperationsinthebackgroundanddoesnotprovideauserin

标签: android_zh

本文链接地址:https://www.jiuchutong.com/biancheng/374559.html 转载请保留说明!

上一篇:Android核心基础-5.Android 数据存储与访问-4. ContentProvider 内容提供者(安卓核心架构)

下一篇:android app 开发过程中 对于性能优化的总结(android app 开发框架)

  • 税务局纳税申报网站官网
  • 建筑业如何分项目核算
  • 小额零星开支税前扣除
  • 个体工商户需要交税吗?怎么交?
  • 房地产开发企业土地增值税预缴
  • 没有发票的费用可以抵扣企业所得税吗
  • 文化建设税是含税价吗
  • 一般纳税人能不能开3个点的专票
  • 用友u8如何计提折旧生成凭证
  • 实收资本如何网上转账
  • 通行费电子发票可以抵扣吗
  • 合同印花税用不用计提
  • 逾期缴纳车辆购置税后情况说明怎么写
  • 从联营企业分回利润可以在税前扣除吗
  • 跨年冲销营业外支出
  • 员工年终福利发什么
  • 填开了红字信息表但未开红字发票
  • 企业所得税本季度可以弥补上季度亏损吗?
  • 冲红发票开具
  • 吊车租赁费计入什么会计科目
  • 少数股东损益为正,归属毌公司为负
  • 电脑美式键盘怎么设置
  • 系统之家有没有官网
  • php字符串定义的三种方式
  • url是什么格式的文件怎么打开
  • 教育发展基金计提比例
  • php获取网页源码
  • 莱姆 惨败
  • Symfony2中被遗弃的getRequest()方法分析
  • 工资福利支出包括五险一金吗
  • 企业清算分配的财产金额怎么算出来
  • 国税打印发票
  • php链式操作
  • 新所得税会计准则
  • 【机器学习】KNN算法及K值的选取
  • uni-app是干嘛的
  • man-s命令
  • nfs4挂载
  • 小规模纳税人无进项票怎么办
  • 购买方已认证怎么开具红字信息表
  • 小规模纳税人免征增值税的账务处理
  • 大学生创新创业平台
  • 合并报表中的抵损益
  • 不征税收入和免税收入有哪些项目
  • 社保年度申报错误可以调整吗
  • sql server 2016 always on
  • 其他应收款主要是什么
  • 小规模第一次申报流程
  • 增值税是否计入在建工程
  • 车船税是什么样子的?
  • 怎么才能不开发票
  • 应付账款收不回发票该如何调整
  • 土地价款抵减销项税
  • 收到党支部经费如何做账
  • 土地出让金抵减销项税计算
  • 产权转移书据纳税义务时间
  • 装饰公司购买的安装服务怎么入账
  • sqlserver存储过程怎么查看
  • Win10预览版怎么变回正式版
  • xp简单实用的网络连接
  • ubuntu系统安装教程
  • win7电脑开机提示oxcoooooe9
  • win7打开网页显示证书有问题
  • win7怎么更改用户名和密码
  • 3d动态锁屏壁纸下载
  • redis开机自动启动linux
  • python读json文件和写json文件
  • python windows gui
  • Node.js中的什么模块是用于处理文件和目录的
  • linux如何批量执行脚本
  • nodejs入门教程
  • javascript编程基础
  • 安卓动态图标怎么实现
  • 不同版本安卓控制台区别
  • python设计程序
  • 置顶是怎么弄的
  • 禁止所有陌生人的来电设置
  • 重新税务登记程序有哪些
  • 北京供暖 2020
  • 企业代收行政事业性收费
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设