-
Notifications
You must be signed in to change notification settings - Fork 0
/
fix-video-view-leak.html
249 lines (179 loc) · 12.6 KB
/
fix-video-view-leak.html
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
<!DOCTYPE html>
<html>
<head>
<!-- [[! Document Settings ]] -->
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!-- [[! Page Meta ]] -->
<title>修复VideoView引起的内存泄露小计</title>
<meta name="description" content="与机器,人,神共舞 - 编程,读书,思考,旅行,与机器对话,与人交谈,对神发问,探索,体验人生美丽的风景" />
<meta name="HandheldFriendly" content="True" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="/assets/images/favicon.ico" >
<!-- [[! Styles'n'Scripts ]] -->
<link rel="stylesheet" type="text/css" href="/assets/css/screen.css" />
<link rel="stylesheet" type="text/css"
href="//fonts.googleapis.com/css?family=Merriweather:300,700,700italic,300italic|Open+Sans:700,400" />
<link rel="stylesheet" type="text/css" href="/assets/css/syntax.css" />
<!-- [[! Ghost outputs important style and meta data with this tag ]] -->
<link rel="canonical" href="/" />
<meta name="referrer" content="origin" />
<link rel="next" href="/page2/" />
<meta property="og:site_name" content="与机器,人,神共舞" />
<meta property="og:type" content="website" />
<meta property="og:title" content="与机器,人,神共舞" />
<meta property="og:description" content="编程,读书,思考,旅行,与机器对话,与人交谈,对神发问,探索,体验人生美丽的风景" />
<meta property="og:url" content="/" />
<meta property="og:image" content="/assets/images/cover1.jpg" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="与机器,人,神共舞" />
<meta name="twitter:description" content="编程,读书,思考,旅行,与机器对话,与人交谈,对神发问,探索,体验人生美丽的风景" />
<meta name="twitter:url" content="/" />
<meta name="twitter:image:src" content="/assets/images/cover1.jpg" />
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "Website",
"publisher": "Tao's Page",
"url": "/",
"image": "/assets/images/cover1.jpg",
"description": "编程,读书,思考,旅行,与机器对话,与人交谈,对神发问,探索,体验人生美丽的风景"
}
</script>
<meta name="generator" content="Jekyll 3.0.0" />
<link rel="alternate" type="application/rss+xml" title="与机器,人,神共舞" href="/rss.xml" />
</head>
<body class="home-template nav-closed">
<div class="nav">
<h3 class="nav-title">Menu</h3>
<a href="#" class="nav-close">
<span class="hidden">Close</span>
</a>
<ul>
<li class="nav-home " role="presentation"><a href="/">Home</a></li>
<li class="nav-about " role="presentation"><a href="/about.html">About</a></li>
<li class="nav-fables " role="presentation"><a href="/tag/machine/">Machine</a></li>
<li class="nav-speeches " role="presentation"><a href="/tag/human/">Human</a></li>
<li class="nav-fiction " role="presentation"><a href="/tag/god/">God</a></li>
<li class="nav-author " role="presentation"><a href="/author/hetao/">Author</a></li>
</ul>
<a class="subscribe-button icon-feed" href="/vocab.html">Apps</a>
</div>
<span class="nav-cover"></span>
<div class="site-wrapper">
<!-- [[! Everything else gets inserted here ]] -->
<!-- default -->
<!-- The comment above "< default" means - insert everything in this file into -->
<!-- the [body] of the default.hbs template, which contains our header/footer. -->
<!-- Everything inside the #post tags pulls data from the post -->
<!-- #post -->
<header class="main-header post-head no-cover">
<nav class="main-nav clearfix">
</nav>
</header>
<main class="content" role="main">
<article class="post tag-machine">
<header class="post-header">
<h1 class="post-title">修复VideoView引起的内存泄露小计</h1>
<section class="post-meta">
<!-- <a href='/'>Tao He</a> -->
<time class="post-date" datetime="2019-09-20">20 Sep 2019</time>
<!-- [[tags prefix=" on "]] -->
on
<a href='/tag/machine'>Machine</a>
</section>
</header>
<section class="post-content">
<p>最近写了非常简单的新手引导视频页面,逻辑很简单,就是新手用户在第一次使用App时可以点击引导视频入口,然后进入一个视频播放页面,为了快速实现功能,就直接使用了VideoView,从需求开发到交付也都没什么问题,需求上线后我打开LeakCanary,想观察下最近有没有新增的内存泄露,竟然发现这个视频页面竟然泄露了。排查了一圈也没有发现有什么会阻止Activity销毁。但是LeakCanary打出了引用链,发现和VideoView有关,通过Google发现,竟然是VideoView自身的bug!这种情况也不是第一次遇见,那也得解决啊,所以开始想办法。</p>
<p>首先显明确是谁导致了Activity的销毁,通过查看VideoView的源码,发现罪魁祸首是AudioManager,它可能会长期持有Context(即泄露的Activity)。很明显是因为生命周期不一致导致的泄露,因此最先想到的就是在创建VideoView时不要传Activity的Context,传给它ApplicationContext。当然了,在布局中创建的VideoView传入的就是Activity的Context,所以需要用代码动态创建:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mVideoView = new VideoView(getApplicationContext());
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
mContainer.addView(mVideoView, layoutParams);
</code></pre></div></div>
<p>这样修改后还是会有内存泄露,只是引用链变了,需要在Activity的onDestroy回调中做一些处理</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Override
protected void onDestroy() {
super.onDestroy();
if (mVideoView != null) {
mVideoView.stopPlayback();
mVideoView.setOnCompletionListener(null);
mVideoView.setOnPreparedListener(null);
mVideoView.setOnErrorListener(null);
mVideoView = null;
}
if (mContainer != null) {
mContainer.removeAllViews();
}
</code></pre></div></div>
<p>以上解决办法需要注意三点:</p>
<ul>
<li>给VideoView设置的Listener都要分别置空,否则仍然会泄露</li>
<li>VideoView的父容器要删掉VideoView,光置空VideoView不够</li>
<li>需设置VideoView的OnErrorListener且返回true,防止弹出弹窗使用ApplicationContext导致崩溃</li>
</ul>
<p>传递ApplicationContext还有人提出另一种方法,但是我test发现没有效果,这种方法我也贴出来:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Override Activity的attachBaseContext的行为
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(new ContextWrapper(newBase){
@Override
public Object getSystemService(String name) {
if(Context.AUDIO_SERVICE.equals(name)){
return getApplicationContext().getSystemService(name);
}
return super.getSystemService(name);
}
});
}
</code></pre></div></div>
<p>去规避系统API的bug真是很烦人的一件事,既不优雅,也不安全!</p>
</section>
<footer class="post-footer">
<!-- Everything inside the #author tags pulls data from the author -->
<!-- #author-->
<!-- Add Disqus Comments -->
</footer>
</article>
</main>
<aside class="read-next">
<!-- [[! next_post ]] -->
<a class="read-next-story no-cover" href="/custom-image-span">
<section class="post">
<h2>ImageSpan的定制使用</h2>
<p>最近在做一个具体业务需求时,为了实现UI设计的要求,需要实现在同一段文字中,有一段文字是需要有形状的背景色,有一段文字中还有网络图片的插入,如果要达到这两个要求,简单的使用SpannableStringBuilder肯定是达不到要求的,虽然SpannableStringBuilder可以设置背景色,但是并不能绘制形状,说白了就是只能设置颜色,不能设置文字的背景图片。想要在文字中插入图片,可以直接使用ImageSpan,但是ImageSpan并不能加载网络图片。因此这种简单的使用方式都是行不通的,需要考虑去定制ImageSpan实现我们特殊的要求。 * 实现给同一段文字的一部分文字区域设置背景图片 这个功能的实现还是要继承ImageSpan,将我们的背景图片传进去,并且override ImageSpan的draw方法,根据对文字区域的测量,分别绘制出背景和文字。实现代码如下: ``` // BgImageSpan public class BgImageSpan extends ImageSpan { private...</p>
</section>
</a>
<!-- [[! /next_post ]] -->
<!-- [[! prev_post ]] -->
<a class="read-next-story prev no-cover" href="/serializable-vs-parcelable">
<section class="post">
<h2>Parcelable vs Serializable</h2>
<p>在实际的Android开发中Serializable和Parcelable我们都会用到,只是需要区分使用场景。刚开始遇到这两个接口的时候比较疑惑,貌似都是用来做序列化的,虽然能分得清什么时候用哪个,但是对这两个接口并没有清晰的认识,要是冷不丁的问我这俩有什么区别,可能还无法清晰的界定和阐述。因此有必要用一篇文章来彻底捋清这些东西。 相同点 相同点其实很明显,主要有如下两点: 都可以序列化反序列化 都可以通过Intent传递 区别 Serializable是Java API,Parcelable是Android SDK API,设计目的不同。Serializable是一个通用的序列化机制,通过将文件保存到本地文件、网络流等实现便数据的传递,这种数据传递不仅可以在单个程序中进行,也可以在两个不同的程序中进行;Parcelable是Android SDK API,为了在同个程序的不同组件之间和不同程序(AIDL)之间高效的传输数据,是通过IBinder通信的消息的载体。从设计目的上可以看出Parcelable就是为了Android高效传输数据而生的。 Serializable序列化过程使用反射机制,速度慢,且产生很多临时对象,容易触发GC;Parcelable是直接在内存中读写的,自已实现封送和解封(marshalled &unmarshalled)操作,将一个完整的对象分解成Intent所支持的数据类型,不需要使用反射,所以Parcelable具有效率高,内存开销小的优点。 Serializable是通用的序列化机制,将数据存储在磁盘,可以做到有限持久化保存,文件的生命周期不受程序影响,Parcelable的序列化操作完全由底层实现,不同版本的Android是实现方式可能不相同,所以不能进行持久化存储。...</p>
</section>
</a>
<!-- [[! /prev_post ]] -->
</aside>
<!-- /post -->
<footer class="site-footer clearfix">
<section class="copyright"><a href="/">与机器,人,神共舞</a> © 2024</section>
</footer>
</div>
<!-- [[! Ghost outputs important scripts and data with this tag ]] -->
<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
<!-- [[! The main JavaScript file for Casper ]] -->
<script type="text/javascript" src="/assets/js/jquery.fitvids.js"></script>
<script type="text/javascript" src="/assets/js/index.js"></script>
<!-- Add Google Analytics -->
<!-- Google Analytics Tracking code -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-78960009-1', 'auto');
ga('send', 'pageview');
</script>
</body>
</html>