Johnny Shieh

人如果没有梦想,跟咸鱼有什么分别


  • 首页

  • 分类

  • 归档

  • 标签

  • 读书

  • 小伙伴

  • 关于

  • 搜索

[译] Android 中由 Handler 和内部类引起的内存泄漏

发表于 2015-09-03 | 分类于 Android | | 阅读次数

原文链接:How to Leak a Context: Handlers & Inner Classes

作者:Alex Lockwood

泄露场景

在 Android 中我们经常用 Handler 来处理异步消息,把非主线程的操作结果放在主线程中更新 UI。请思考一下下面的代码有没有问题:

1
2
3
4
5
6
7
8
9
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
}

虽然看起来好像没什么问题,但是这段代码可能引起内存泄露。Android Lint 工具会给出以下警告:

In Android, Handler classes should be static or leaks might occur, Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class

但是什么东西泄漏了,内存泄漏怎么发生的我们还是不清楚。接下来我们慢慢分析泄漏的原因:

  1. 当一个 Android 应用启动时,框架会分配到一个 Looper 实例给应用的主线程。这个 Looper 的主要工作就是处理一个接着一个的消息对象。在 Android 中,所有 Android 框架的事件(比如 Activity 的生命周期方法的调用和按钮的点击等)都是放到消息中,然后加入到 Looper 要处理的消息队列中,由 Looper 依次处理。主线程的 Looper 的生命周期和应用的一样长。

  2. 当在主线程中初始化一个 Handler 时,它就会关联到 Looper 的消息队列。发送到消息队列的消息本身就持有 Handler 的引用,只有这样 Looper 在处理这个条消息的时候才能调用 Handler#handleMessage(Message) 处理消息。

  3. 在 Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,而静态的内部类则不会。

上面的代码还是很难察觉哪里会导致内存泄漏,那么再看看下面这个例子:

阅读全文 »

[转] Java HashMap 的工作原理

发表于 2015-08-24 | 分类于 Java | | 阅读次数

本文转载自 ImportNew - miracle1919, 翻译自 javacodegeeks。

面试的时候经常会遇见诸如:“Java 中的 HashMap 是怎么工作的”,“HashMap 的 get 和 put 内部的工作原理”这样的问题。本文将用一个简单的例子来解释下 HashMap 内部的工作原理。首先我们从一个例子开始,而不仅仅是从理论上,这样,有助于更好地理解,然后,我们来看下 get 和 put 到底是怎样工作的。

我们来看个非常简单的例子。有一个“国家”(Country)类,我们将要用 Country 对象作为 key,它的首都的名字(String 类型)作为 value。下面的例子有助于我们理解 key-value 对在 HashMap 中是如何存储的。

Country.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class Country {
String name;
long population;
public Country(String name, long population) {
super();
this.name = name;
this.population = population;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getPopulation() {
return population;
}
public void setPopulation(long population) {
this.population = population;
}
// If length of name in country object is even then return 31(any random number) and if odd then return 95(any random number).
// This is not a good practice to generate hashcode as below method but I am doing so to give better and easy understanding of hashmap.
@Override
public int hashCode() {
if(this.name.length() % 2 == 0)
return 31;
else
return 95;
}
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(null == obj || !(obj instanceof Country)) {
return false;
}
Country other = (Country) obj;
if (name.equalsIgnoreCase((other.name)))
return true;
return false;
}
}
阅读全文 »

UTF-8 编码去除 BOM 头的 python 脚本

发表于 2015-08-02 | 分类于 Python | | 阅读次数

前几天我在 Windows 写完一篇 markdown 格式的文章,上传到 github page 后,这个文章的页面把 jekyll 的 YAML 头信息也显示出来了,没有像我之前在 Ubuntu 中写的文章一样正常解析 YAML 头信息,最后谷歌了半天发现原因是在 Windows 用记事本保存 UTF-8 编码的文件时会默认加上 BOM 头字符。而 jekyll 的文档中也明确写明了如果使用 UTF-8 编码,那么在文件中一定不要出现 BOM 头字符。

所以我把如何在 Windows 和 Linux 系统中去除 UTF-8 的 BOM 头的方法做个记录。

UTF-8 与 UTF-8 + BOM 的区别

下面是我从网上复制过来 BOM 的简介:

BOM – Byte Order Mark,中文名译作“字节顺序标记”。在 UCS 编码中有一个叫做 “Zero Width No-Break Space” ,中文译名作“零宽无间断间隔”的字符,它的编码是 FEFF。而 FFFE 在 UCS 中是不存在的字符,所以不应该出现在实际传输中。UCS 规范建议我们在传输字节流前,先传输字符 “Zero Width No-Break Space”。这样如果接收者收到 FEFF,就表明这个字节流是 Big-Endian 的;如果收到 FFFE,就表明这个字节流是 Little- Endian 的。因此字符 “Zero Width No-Break Space” (“零宽无间断间隔”)又被称作 BOM。

UTF-8 不需要 BOM 来表明字节顺序,但可以用 BOM 来表明编码方式。字符 “Zero Width No-Break Space” 的 UTF-8 编码是 EF BB BF。所以如果接收者收到以 EF BB BF (十六进制)开头的字节流,就知道这是 UTF-8编码了。Windows 就是使用 BOM 来标记文本文件的编码方式的。

所以 UTF-8 + BOM 其实就是在文件的开头加上了 0xEF 0xBB 0xBF 这三个字节,而这个三个字节是一串隐藏字符。

阅读全文 »

Android 中 ScrollView 监听滚动停止的方案

发表于 2015-07-17 | 分类于 Android | | 阅读次数

前几天工作上遇到一个需要监听 Scrollview 滚动停止的问题,然后开始谷歌解决方案,但是都不是很满意,最后在和同事的讨论中获得灵感得出了下面的解决方案,经实测有效,供大家参考一下。

思路

思路是这样的:首先要确定监听的时机,因为 ScrollView 在滚动的过程中和绘制的时候需要 computeScroll,所以在 computeScroll 的时候监听; 然后用反射检测滚动是否停止; 最后由于工作的需求是在 Touch 的情况下都不算停止所以还要监听 touch event。

其中有用到反射,对反射不是很了解的可以看我的这篇博客 Java 反射机制

Code

关键代码如下:

阅读全文 »

Android 中利用 ContentResolver 查询数据库的四种方式

发表于 2015-06-25 | 分类于 Android | | 阅读次数

直接使用 ContentResolver 的 query 方法在主线程中查询数据(非异步查询)

由于是在主线程中执行查询操作,为了不阻塞主线程,尽量在数据量小的时候使用,而且要注意及时关闭 cursor

代码示例

1
2
3
Cursor cursor = getContentResolver().query(ContactsContract.Data.CONTENT_URI, null, null, null, null);
// 获得 cursor 中数据 ...
cursor.close();

利用 Activity 的 managedQuery 方法在主线程中查询数据(非异步查询)

相对于上一种方式,我们不需要处理 cusor 的关闭,但是同样只适合在数据量小的时候。

对于 Android 3.0 HONEYCOMB 以上的版本,Android 官方建议使用 LoaderManager 替代,这也是下一种查询方式。

代码示例:

1
2
Cursor cursor = managedQuery(ContactsContract.Data.CONTENT_URI, null, null, null, null);
// 获得 cursor 中数据 ...

利用 LoaderManager 机制中的 CursorLoader 异步查询数据库(异步查询)

Android3.0 以上可以直接使用,3.0 以下需要使用 support 库.

注:LoaderManager 开始一个 Loader 之后,如果没有调用 destroyLoader 方法,在 activity 的 onStart 时候会重新开始 Loader 中的任务

阅读全文 »
1…121314…16
Johnny Shieh

Johnny Shieh

我本微末,心向天空

76 日志
13 分类
43 标签
GitHub Weibo Gmail 简书
  • 热门系列
  • Dagger 完全解析(6)
  • AspectJ in Android(3)
  • Kotlin 写 Android 单元测试(4)
  • Kotlin 协程完全解析(5)
© 2015 - 2019 Johnny Shieh
访客数 访问量