OnSharedPreferenceChangeListener详解及出现不触发解决办法

之前使用OnSharedPreferenceChangeListener,遇到了点小问题,就是有些时候OnSharedPreferenceChangeListener没有被触发。最近花了点时间研究了一下,小做整理。本文将会介绍监听器不被触发的原因,解决方法,以及其中隐含的一些技术细节。

问题再现

OnSharedPreferenceChangeListener是Android中SharedPreference文件发生变化的监听器。通常我们想要进行监听,会实现如下的代码。

protected void onCreate(Bundle savedInstanceState) {
 PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
  .registerOnSharedPreferenceChangeListener(new OnSharedPreferenceChangeListener() {
  @Override
  public void onSharedPreferenceChanged(
   SharedPreferences sharedPreferences, String key) {
   Log.i(LOGTAG, "testOnSharedPreferenceChangedWrong key =" + key);
  }
 });
}

这种写法看上去没有什么问题,而且很多时候开始几次onSharedPreferenceChanged方法也可以被调用。但是过一段时间(简单demo不容易出现,但是使用DDMS中的gc会立刻导致接下来的问题),你会发现前面的方法突然不再被调用,进而影响到程序的处理。

原因剖析

简而言之,就是你注册的监听器被移除掉了。
首先我们先了解一下registerOnSharedPreferenceChangeListener注册的实现。

private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
   new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
//some code goes here
public void More ...registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
 synchronized(this) {
  mListeners.put(listener, mContent);
 }
}

从上面的代码可以得知,一个OnSharedPreferenceChangeListener对象实际上是放到了一个WeakHashMap的容器中,执行完示例中的onCreate方法,这个监听器对象很快就会成为垃圾回收的目标,由于放在WeakHashMap中作为key不会阻止垃圾回收,所以当监听器对象被回收之后,这个监听器也会从mListeners中移除。所以就造成了onSharedPreferenceChanged不会被调用。

关于WeakHashMap相关,请阅读译文:理解Java中的弱引用进而更多了解。

如何解决

改为对象成员变量(推荐)

将监听器作为Activity的一个成员变量,在Activity的onResume进行注册,在onPause时进行注销。推荐在这两个Activity生命周期中进行处理,尤其是当SharedPreference值发生变化后,对Activity展示的UI进行处理操作的情况。这种方法是最推荐的解决方案。

private OnSharedPreferenceChangeListener mListener = new OnSharedPreferenceChangeListener() {

 @Override
 public void onSharedPreferenceChanged(
  SharedPreferences sharedPreferences, String key) {
  Log.i(LOGTAG, "instance variable key=" + key);
 }
};

@Override
protected void onResume() {
 PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).registerOnSharedPreferenceChangeListener(mListener);
 super.onResume();
}

@Override
protected void onPause() {
 PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).unregisterOnSharedPreferenceChangeListener(mListener);
 super.onPause();
}

改为静态变量(不推荐)

如下,将一个指向匿名的内部类对象的变量sListener使用static修饰,这个内部类对象则不会持有外部类的引用。
但是这种做法并不推荐,因为一个静态变量和与外部实例不相关,我们很难和外部实例进行一些操作。

private static OnSharedPreferenceChangeListener sListener = new OnSharedPreferenceChangeListener() {
 @Override
 public void onSharedPreferenceChanged(
  SharedPreferences sharedPreferences, String key) {
  Log.i(LOGTAG, "static variable key=" + key);
 }
};

为什么这样设计

可能会有人认为这是系统设计的猫腻或者bug,其实不然,这正是Android设计人员的高明之处。

正如我们示例的代码一样,将一个(隐式的)局部变量添加到监听器容器中,如果该容器只是一个普通的HashMap,这样会导致内存泄露,因为该容器还有局部变量指向的对象,该对象又隐式持有外部Activity的对象,导致Activity无法被销毁。关于非静态内部类持有隐式持有外部类引用,请参考细话Java:”失效”的private修饰符

除此之外,因为局部变量无法在其所在方法外部访问,这样就导致了我们只可以使用方法中使用局部变量就行注册,在合适的时机却无法使用局部变量进行注销。

以上就是对 Android OnSharedPreferenceChangeListener的介绍,以及出现问题解决办法,谢谢大家对本站的支持!

(0)

相关推荐

  • Android仿微信列表滑动删除之可滑动控件(一)

    这次是列表滑动删除的第三波,仿微信的列表滑动删除.先上个效果图: 前面的文章里面说过开源框架SwipeListView的实现原理是每个列表item中包含上下两层view,普通状态下上层的view覆盖着下层的view,当用户滑开上层的view,下层的view就显示出来了.但是仔细观察微信列表的item,很明显并非这个实现方案,微信的item应该一个单层view,只不过这个item超出了所在的ListView的宽度,在用户滑动item的时候,item超出屏幕的view则会显示在屏幕之上,这种滑动实现

  • android换肤功能 如何动态获取控件中背景图片的资源id?

    这个是在在做一个换肤功能时遇到的问题. 对于换肤,网上都有示例,可以从别的皮肤安装包中读取所要的资源,前提是你必须先持有这个资源的引用名称,像R.drawable.background(喂,这不是废话嘛).这个换肤的方案原理就是,自身应用的资源名称是R.drawable.background,那皮肤包中应该也是这个名称,然后通过这个名称获取该资源在皮肤包中的具体id,代码: //先获取本地资源引用名称,type name是R.drawable.background中的"drawable"

  • Android 代码JIT友好度检测工具

    利用周末的时间,写了一个检测Android代码JIT友好度的工具,取个名字为DroidJitChecker.希望可以帮助大家快速发现有坏味道的代码,并且及时修正. 名词解释 JIT:JIT全称Just-in-time compilation.意思为实时编译,是JVM中一种优化技术,对频繁调用并且符合条件的方法进行优化将字节码翻译成机器代码,提升执行效率. 方法大小:每个方法的方法体实现都可用字节作为单位进行衡量,通常情况下,方法体越大,其方法大小也越大. JIT友好:通常方法体实现越小越简单,越

  • Android 中Handler引起的内存泄露

    在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用.通常我们的代码会这样实现. public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } } 但是,其实上面的代码可能导致内存泄露,当你使用Androi

  • 如何从UA分辨出Android设备类型

    随着Android设备增多,不少网站都开始设备Android设备,而Android主流设备类型以手机和平板为主.网站在适配时通过User Agent(用户代理,以下简称UA)又如何区分呢,本文部分内容翻译自Google官方博客Mo' better to also detect "mobile" user-agent. 一针见血 标准判断规则:Mobile Android has "Mobile" string in the User-Agent header. Ta

  • Android实现仿微信tab高亮icon粘着手的滑动效果

    微信的主页分为3个tab,被选中的tab的tabwidget下面会有一个高亮的长条icon,而当切换tab页面的时候,这个icon不是等到tab切换完成后再出现在当前被选中的tab的tabwidget的下面,而是会随着viewpager滑动页面的动作也进行滑动,从前一个tabwidget滑到当前被选中的tabwidget,像viewpager一样有一种粘着你的手的效果,体验很赞.上个图: 本篇分析如何实现这个效果. 首先基本知识是,实现不同tab页之间可以滑动切换需要用到TabPageIndic

  • Android点击事件派发机制源码分析

    概述 一直想写篇关于Android事件派发机制的文章,却一直没写,这两天刚好是周末,有时间了,想想写一篇吧,不然总是只停留在会用的层次上但是无法了解其内部机制.我用的是4.4源码,打开看看,挺复杂的,尤其是事件是怎么从Activity派发出来的,太费解了.了解Windows消息机制的人会发现,觉得Android的事件派发机制和Windows的消息派发机制挺像的,其实这是一种典型的消息"冒泡"机制,很多平台采用这个机制,消息最先到达最底层View,然后它先进行判断是不是它所需要的,否则就

  • Android ListView滑动删除操作(SwipeListView)

    新版本的微信和QQ上引入的滑动删除功能是现在比较流行的一个功能.其实这个滑动删除的控件,github上已经有了,是一个热门的开源框架SwipeListView.不过,这个SwipeListView是一个framelayout,即是一个两层的布局,上面的布局front覆盖了下面的布局back,滑动的时候则会滑开front,这样下面的back就显示出来了.但是看了一下微信的滑动删除好像不是这样的,感觉更像是一个超出了屏幕的单层布局,滑动的时候是右边超出屏幕的button进入屏幕,猜测应该不是使用Sw

  • Android高仿微信对话列表滑动删除效果

    前言 用过微信的都知道,微信对话列表滑动删除效果是很不错的,这个效果我们也可以有.思路其实很简单,弄个ListView,然后里面的每个item做成一个可以滑动的自定义控件即可.由于ListView是上下滑动而item是左右滑动,因此会有滑动冲突,也许你需要了解下android中点击事件的派发流程,请参考Android源码分析-点击事件派发机制.我的解决思路是这样的:重写ListView的onInterceptTouchEvent方法,在move的时候做判断,如果是左右滑动就返回false,否则返

  • OnSharedPreferenceChangeListener详解及出现不触发解决办法

    之前使用OnSharedPreferenceChangeListener,遇到了点小问题,就是有些时候OnSharedPreferenceChangeListener没有被触发.最近花了点时间研究了一下,小做整理.本文将会介绍监听器不被触发的原因,解决方法,以及其中隐含的一些技术细节. 问题再现 OnSharedPreferenceChangeListener是Android中SharedPreference文件发生变化的监听器.通常我们想要进行监听,会实现如下的代码. protected vo

  • 详解maven BUILD FAILURE的解决办法

    问题描述 在使用IDEA的maven创建项目时总是失败,错误信息如下截图所示: 从图中可以看到是maven出了问题,在控制终端中执行maven help:system命令,测试能否正常build项目,执行错误信息如下所示: 尝试的解决办法: 删除C:\user\用户名.m2文件夹下的repository文件夹,选择maven–>clean–>install,重新build项目.(无效) 删除原有的maven文件夹,官网下载最新的maven包并进行相关配置.(无效) 修改.m2和maven\co

  • 详解用js代码触发dom事件的实现方案

    背景 前端编写测试用例时,在测试界面上的一些效果时,通常都希望能够模拟一些用户操作,而模拟用户操作最主要的方式就是用代码触发指定事件.通常一些元素上会自带一些触发事件的方法,例如click.focus等,但是如果是其他的事件,例如mousedown.mouseup.mouseenter,这些事件怎么模拟呢? 思路 其实思路很简单,web标准中定义了一系列API接口,其中dispatchEvent允许我们向一个指定的事件目标派发一个事件,而且使用这个方法触发的事件是可以正常触发我们的标准事件处理规

  • 详解maven依赖冲突以及解决方法

    什么是依赖冲突 依赖冲突是指项目依赖的某一个jar包,有多个不同的版本,因而造成类包版本冲突 依赖冲突的原因 依赖冲突很经常是类包之间的间接依赖引起的.每个显式声明的类包都会依赖于一些其它的隐式类包,这些隐式的类包会被maven间接引入进来,从而造成类包冲突 如何解决依赖冲突 首先查看产生依赖冲突的类jar,其次找出我们不想要的依赖类jar,手工将其排除在外就可以了.具体执行步骤如下 1.查看依赖冲突 a.通过dependency:tree是命令来检查版本冲突 mvn -Dverbose dep

  • 详解Tomcat出现404的解决方法

    Tomcat测试出现404问题出现的问题如下: HTTP状态 404 - 未找到 类型 状态报告 消息 请求的资源[/chapter06/IndexServlet]不可用 描述 源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的资源表示. Apache Tomcat/9.0.37 问题原因,浏览器不能直接访问Java文件 问题的解决方法 1.提交表单时遇到此问题 <form name = "reg" action="/login" method=&qu

  • 详解docker-compose速度太慢解决方式

    解决办法只有一个一个一个,那就是换源! 使用的是github的源基本都是超时,此时使用: curl -L https://get.daocloud.io/docker/compose/releases/download/1.25.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose 记得修改权限,否则会报错: sudo chmod +x /usr/local/bin/docker-compose 最后查看版本

  • 详解Java利用深度优先遍历解决迷宫问题

    目录 什么是深度优先 一个简单的例子 程序实现 什么是深度优先 什么是深度,即向下,深度优先,即向下优先,一口气走到底,走到底发现没路再往回走. 在算法实现上来讲,深度优先可以考虑是递归的代名词,深度优先搜索必然需要使用到递归的思路. 有的人可能会说了,我可以用栈来实现,以迭代的方式,那么问题来了,栈这种数据结构,同学们认为是否也囊括了递归呢?Java语言的方法区本身也是实现在一个栈空间上的. 一个简单的例子 我们以一个简单的迷宫为例,以1代表墙,0代表路径,我们构造一个具有出入口的迷宫. 1

  • 详解Mysql数据库平滑扩容解决高并发和大数据量问题

    目录 1 停机方案 2 停写方案 3 平滑扩容之双写方案(中小型数据) 4 平滑扩容之2N方案大数据量问题解决 4.1 扩容问题 4.2 解决方案 4.3 双主架构思想 4.4 环境部署 5 数据库秒级平滑2N扩容实践 5.1 新增数据库VIP 5.2 应用服务增加动态数据源 5.3 解除原双主同步 5.4 安装MariaDB扩容服务器 5.5 增加KeepAlived服务实现高可用 5.6 清理数据并验证 1 停机方案 发布公告 停止服务 离线数据迁移(拆分,重新分配数据) 数据校验 更改配置

  • 详解mysql5.7密码忘记解决方法

    ENV: [root@centos7 ~]# uname -r 3.10.0-514.el7.x86_64 [root@centos7 ~]# cat /etc/redhat-release CentOS Linux release 7.3.1611 (Core) [root@centos7 ~]# rpm -qa mysql [root@centos7 ~]# rpm -qa |grep mysql mysql-community-common-5.7.26-1.el7.x86_64 mysq

  • 详解maven安装教程以及解决安装不成功的解决办法

    1.下载地址 http://maven.apache.org/download.cgi 2.将文件解压到D:\maven\apache-maven-3.3.9目录下 3.配置maven环境变量 我的电脑右键点击属性,然后点击高级系统设置找到环境变量: 1)在系统变量中新建一个变量,变量名为MAVEN_HOME,变量值为Maven的安装目录, D:\maven\apache-maven-3.3.9 如图: 2)编辑PATH变量,添加MAVEN的bin目录到PATH最后,如%MAVEN_HOME\b

随机推荐