This repository has been archived by the owner on Sep 13, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathandroid-contextli-jie-yu-xian-jing.html
362 lines (305 loc) · 32 KB
/
android-contextli-jie-yu-xian-jing.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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Android Context理解与陷阱</title>
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="华科美团点评技术俱乐部 Full Atom Feed" />
<link href="/feeds/android.atom.xml" type="application/atom+xml" rel="alternate" title="华科美团点评技术俱乐部 Categories Atom Feed" />
<!-- Bootstrap Core CSS -->
<link href="/theme/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="/theme/css/clean-blog.min.css" rel="stylesheet">
<!-- Code highlight color scheme -->
<link href="/theme/css/code_blocks/darkly.css" rel="stylesheet">
<!-- Custom Fonts -->
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href='https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/css'>
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<meta name="description" content="Context? Context在安卓开发时是一个非常常见的组件,我们会在许多地方使用它,举一些例子: 启动新的Activity Service 发送广播,接收广播 填充View 获取资源 相信每一个开发者在看见它时都有过这样一些疑问: Context是什么...">
<meta name="author" content="Di Wu">
<meta name="tags" content="android">
<meta property="og:locale" content="zh_CN.UTF-8">
<meta property="og:site_name" content="华科美团点评技术俱乐部">
<meta property="og:type" content="article">
<meta property="article:author" content="/author/di-wu.html">
<meta property="og:url" content="/android-contextli-jie-yu-xian-jing.html">
<meta property="og:title" content="Android Context理解与陷阱">
<meta property="article:published_time" content="2017-07-19 19:07:42+08:00">
<meta property="og:description" content="Context? Context在安卓开发时是一个非常常见的组件,我们会在许多地方使用它,举一些例子: 启动新的Activity Service 发送广播,接收广播 填充View 获取资源 相信每一个开发者在看见它时都有过这样一些疑问: Context是什么...">
<meta property="og:image" content="//images/bg.jpg">
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-default navbar-custom navbar-fixed-top">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header page-scroll">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">华科美团点评技术俱乐部</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li><a href="/categories.html">分类</a></li>
<li><a href="/archives.html">归档</a></li>
<li><a href="/authors.html">作者</a></li>
<li><a href="/tags.html">标签</a></li>
<li><a href="/pages/about/index.html">关于</a></li>
<li><a href="/pages/friendlinks/index.html">友链</a></li>
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container -->
</nav>
<!-- Page Header -->
<header class="intro-header" style="background-image: url('/images/bg.jpg')">
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<div class="post-heading">
<h1>Android Context理解与陷阱</h1>
<span class="meta">Posted by
<a href="/author/di-wu.html">Di Wu</a>
on 2017年 7月19日 周三
</span>
</div>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<!-- Post Content -->
<article>
<h2>Context?</h2>
<p><code>Context</code>在安卓开发时是一个非常常见的组件,我们会在许多地方使用它,举一些例子:</p>
<ul>
<li>启动新的<code>Activity</code> <code>Service</code></li>
<li>发送广播,接收广播</li>
<li>填充<code>View</code></li>
<li>获取资源</li>
</ul>
<p>相信每一个开发者在看见它时都有过这样一些疑问:</p>
<ul>
<li><code>Context</code>是什么</li>
<li><code>Context</code>的作用</li>
<li><code>Context</code>从哪里来</li>
</ul>
<p>同时,我们也经历过需要一个<code>Context</code>但不知道如何去正确获取/传递的情况,事实上不正确地保存一个<code>Context</code>的引用可能会导致部分内存不能被正确GC从而造成事实上的内存泄漏。</p>
<p>本文将着重对上面这些内容进行讲解。</p>
<!--more-->
<h2>Context的定义</h2>
<p>字面上解释,<code>Context</code>意为“环境”,这个解释比较符合它的作用。</p>
<p>官方文档中对<code>Context</code>的解释是:</p>
<blockquote>
<p>Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.</p>
<p>关于应用环境的全局信息的接口。它是一个抽象类,具体由安卓系统来实现。它允许我们去访问特定的应用的资源和类,同时也可以经由它去向上请求应用级别的操作例如启动<code>Activity</code>、发送广播、接收<code>intents</code>等等。</p>
</blockquote>
<p>我们可以把它看作是一个连接我们代码与安卓系统的“桥梁”,我们开发的应用是与运行在设备上的操作系统紧密相关的,只能通过操作系统,我们才能去启动一个新的<code>Activity</code>,向其他应用发送广播,启动一个新的<code>Service</code>或是访问我们存放在<code>apk</code>中的资源文件。</p>
<p><code>Context</code>就是系统为我们提供上述功能的一个接口,我们需要使用它去完成与系统的信息交换。</p>
<h2>Context从哪里来</h2>
<p><code>Context</code>作为一个依赖于系统的类,<code>SDK</code>中只给了我们一个抽象类,具体的实现由系统完成,下文举例使用的<code>ContextImpl</code>就是<code>AOSP</code>中安卓源码对于<code>Context</code>的一个实现。</p>
<h2>Context的作用</h2>
<h3>Context中封装的信息</h3>
<p>我们可以看看<code>Context</code>里面包含了哪些东西(部分)。</p>
<div class="highlight"><pre><span></span><span class="kd">private</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">mBasePackageName</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">mOpPackageName</span><span class="o">;</span> <span class="c1">//软件包名</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">Resources</span> <span class="n">mResources</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">ResourcesManager</span> <span class="n">mResourcesManager</span><span class="o">;</span> <span class="c1">//用于管理资源文件</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">Display</span> <span class="n">mDisplay</span><span class="o">;</span> <span class="c1">//为View填充等提供屏幕尺寸、像素密度等信息</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">DisplayAdjustments</span> <span class="n">mDisplayAdjustments</span> <span class="o">=</span> <span class="k">new</span> <span class="n">DisplayAdjustments</span><span class="o">();</span>
<span class="kd">private</span> <span class="n">Resources</span><span class="o">.</span><span class="na">Theme</span> <span class="n">mTheme</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="c1">//主题信息</span>
<span class="kd">private</span> <span class="n">File</span> <span class="n">mCacheDir</span><span class="o">;</span>
<span class="nd">@GuardedBy</span><span class="o">(</span><span class="s">"mSync"</span><span class="o">)</span>
<span class="kd">private</span> <span class="n">File</span> <span class="n">mCodeCacheDir</span><span class="o">;</span>
<span class="o">...</span>
<span class="nd">@GuardedBy</span><span class="o">(</span><span class="s">"mSync"</span><span class="o">)</span>
<span class="kd">private</span> <span class="n">File</span><span class="o">[]</span> <span class="n">mExternalObbDirs</span><span class="o">;</span>
<span class="nd">@GuardedBy</span><span class="o">(</span><span class="s">"mSync"</span><span class="o">)</span>
<span class="kd">private</span> <span class="n">File</span><span class="o">[]</span> <span class="n">mExternalFilesDirs</span><span class="o">;</span>
<span class="nd">@GuardedBy</span><span class="o">(</span><span class="s">"mSync"</span><span class="o">)</span>
<span class="kd">private</span> <span class="n">File</span><span class="o">[]</span> <span class="n">mExternalCacheDirs</span><span class="o">;</span>
<span class="nd">@GuardedBy</span><span class="o">(</span><span class="s">"mSync"</span><span class="o">)</span>
<span class="kd">private</span> <span class="n">File</span><span class="o">[]</span> <span class="n">mExternalMediaDirs</span><span class="o">;</span> <span class="c1">//各种文件路径</span>
</pre></div>
<p>这些域的存在为功能提供了必要的信息,例如在<code>LayoutInflater</code>填充<code>View</code>时需要一个<code>context</code>作为参数,我们查看这个<code>context</code>如何被使用:</p>
<div class="highlight"><pre><span></span><span class="kd">final</span> <span class="n">XmlResourceParser</span> <span class="n">childParser</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">getResources</span><span class="o">().</span><span class="na">getLayout</span><span class="o">(</span><span class="n">layout</span><span class="o">);</span>
</pre></div>
<p>我们传入的<code>ResourceId</code>最终会被通过<code>context</code>的<code>getResource()</code>方法获取的<code>Resource</code>对象的<code>getLayout()</code>方法定位到对应的<code>xml</code>文件提供给<code>Inflater</code>进行解析。</p>
<div class="highlight"><pre><span></span><span class="c1">// Apply a theme wrapper, if allowed and one is specified.</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">ignoreThemeAttr</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">TypedArray</span> <span class="n">ta</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">obtainStyledAttributes</span><span class="o">(</span><span class="n">attrs</span><span class="o">,</span> <span class="n">ATTRS_THEME</span><span class="o">);</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">themeResId</span> <span class="o">=</span> <span class="n">ta</span><span class="o">.</span><span class="na">getResourceId</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">themeResId</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">context</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ContextThemeWrapper</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">themeResId</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">ta</span><span class="o">.</span><span class="na">recycle</span><span class="o">();</span>
<span class="o">}</span>
</pre></div>
<p>在这里调用了<code>context</code>的<code>obtainStyledAttributes()</code>方法:</p>
<div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">final</span> <span class="n">TypedArray</span> <span class="nf">obtainStyledAttributes</span><span class="o">(</span>
<span class="n">AttributeSet</span> <span class="n">set</span><span class="o">,</span> <span class="nd">@StyleableRes</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">attrs</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">getTheme</span><span class="o">().</span><span class="na">obtainStyledAttributes</span><span class="o">(</span><span class="n">set</span><span class="o">,</span> <span class="n">attrs</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="o">}</span>
</pre></div>
<p>最终使用了<code>context</code>中存放的主题信息为填充的<code>view</code>设置属性。</p>
<p>现在我们知道,我们存放在<code>res</code>文件夹下的内容(布局文件、字符串文件、图片、主题……)都需要通过一个<code>context</code>去向系统获取。</p>
<p>那么为什么在启动<code>activity</code>、启动<code>service</code>、发送广播时都需要使用<code>context</code>呢?因为这些操作与系统是紧密相关的,我们知道启动这些东西都需要使用一个叫<code>intent</code>的东西(关于<code>intent</code>的内容会在另外的文章讲),以<code>startActivity()</code>方法为例,我们一路向上追溯,可以发现启动<code>activity</code>最终是由<code>AcitivityManagerNative.getDefault()</code>的本地方法<code>startActivity()</code>执行的:</p>
<div class="highlight"><pre><span></span><span class="k">try</span> <span class="o">{</span>
<span class="n">intent</span><span class="o">.</span><span class="na">migrateExtraStreamToClipData</span><span class="o">();</span>
<span class="n">intent</span><span class="o">.</span><span class="na">prepareToLeaveProcess</span><span class="o">();</span>
<span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="n">ActivityManagerNative</span><span class="o">.</span><span class="na">getDefault</span><span class="o">().</span><span class="na">startActivity</span><span class="o">(</span>
<span class="n">whoThread</span><span class="o">,</span> <span class="n">who</span><span class="o">.</span><span class="na">getBasePackageName</span><span class="o">(),</span> <span class="n">intent</span><span class="o">,</span>
<span class="n">intent</span><span class="o">.</span><span class="na">resolveTypeIfNeeded</span><span class="o">(</span><span class="n">who</span><span class="o">.</span><span class="na">getContentResolver</span><span class="o">()),</span> <span class="n">token</span><span class="o">,</span>
<span class="n">target</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">target</span><span class="o">.</span><span class="na">mEmbeddedID</span> <span class="o">:</span> <span class="kc">null</span><span class="o">,</span> <span class="n">requestCode</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">options</span><span class="o">);</span>
<span class="n">checkStartActivityResult</span><span class="o">(</span><span class="n">result</span><span class="o">,</span> <span class="n">intent</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">RemoteException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
</pre></div>
<p>这个时候我们发现,传入的<code>context</code>已经变成了上面代码中的<code>who</code>,利用这个 <code>context</code>获取了包名与方法的第四个参数<code>who.getContentResolver()</code>。它的作用是提供信息来解析<code>intent</code>的<a href="https://en.wikipedia.org/wiki/Media_type"><code>MIME type</code></a>,帮助系统决定<code>intent</code>的目标。</p>
<p>可以看到<code>context</code>在这里同样起到了一个提供必要信息的作用。</p>
<h3>Context的作用</h3>
<p>在这里再重复一遍上面说过的话,配合之前的例子,是不是可以更好地理解了呢?</p>
<blockquote>
<p>我们可以把它看作是一个连接我们代码与安卓系统的“桥梁”,我们开发的应用是与运行在设备上的操作系统紧密相关的,只能通过操作系统,我们才能去启动一个新的<code>Activity</code>,向其他应用发送广播,启动一个新的<code>Service</code>或是访问我们存放在<code>apk</code>中的资源文件。</p>
<p><code>Context</code>就是系统为我们提供上述功能的一个接口,我们需要使用它去完成与系统的信息交换。</p>
</blockquote>
<h2>Context的使用</h2>
<h3>Context分类</h3>
<p><code>Context</code>并不是都是相同的,根据获取方式的不同,我们得到的<code>Context</code>的各类也有所不同。</p>
<h4><code>Activity</code>/<code>Service</code></h4>
<p>我们知道<code>Acitivity</code>类继承自<code>ContextThemeWrapper</code>,<code>ContextThemeWrapper</code>继承自<code>ContextWrapper</code>,最后<code>ContextWrapper</code>继承自<code>Context</code>。顾名思义,<code>ContextWrapper</code>与<code>ContextThemeWrapper</code>只是将<code>Context</code>进行了再次的包装,加入了更多的信息,同时对一些方法做了转发。</p>
<p>所以我们在<code>Activity</code>或<code>Service</code>中需要<code>Context</code>时就可以直接使用<code>this</code>,因为它们本身就是<code>Context</code>。</p>
<p>当系统创建一个新的<code>Activity</code>/<code>Service</code>实例时,它也会创建一个新的<code>ContextImpl</code>实例来封装所有的信息。</p>
<p><strong>对于每一个<code>Activity</code>/<code>Service</code>实例,它们的基础<code>Context</code>都是独立的。</strong></p>
<h4><code>Application</code></h4>
<p><code>Application</code>同样继承于<code>ContextWrapper</code>,但是<code>Application</code>本身是以单例模式运行在应用进程中的,它可以被任何<code>Activity</code>/<code>Service</code>用<code>getApplication()</code>或是被任何<code>Context</code>使用<code>getApplicationContext()</code>方法获取。</p>
<p><strong>不管使用什么方法去获取<code>Application</code>,获取的总是同一个<code>Application</code>实例。</strong></p>
<h4><code>BroadcastReciver</code></h4>
<p><code>BroadcastReciver</code>本身并不是一个<code>Context</code>或在内部保存了一个<code>Context</code>,但是系统会在每次调用其<code>onRecive()</code>方法时向它传递一个<code>Context</code>对象,这个<code>Context</code>对象是一个<code>ReceiverRestrictedContext</code>(接收器限定<code>Context</code>),与普通<code>Context</code>不同在它的<code>registerReceiver()</code>与<code>bindSerivce()</code>方法是被禁止使用的,这意味着我们不能在<code>onRecive()</code>方法中调用该<code>Context</code>的这两个方法。</p>
<p><strong>每次调用<code>onReceive()</code>方法传递的<code>Context</code>都是全新的。</strong></p>
<h4><code>ContentProvider</code></h4>
<p>它本身同样不是一个<code>Context</code>,但它在创建时会被赋予一个<code>Context</code>并可以通过<code>getContext()</code>方法获取。</p>
<p><strong>如果这个内容提供器运行在调用它的应用中,将会返回该应用的<code>Application</code>单例,如果它是由其他应用提供的,返回的<code>Context</code>将会是一个新创建的表示其他应用环境的<code>Context</code>。</strong></p>
<h3>使用<code>Context</code>时的陷阱</h3>
<p>现在我们知道<code>Context</code>的几种分类,其实上面的分类也就是我们获取它的方式。着重标出的内容说明了它们被提供的来源,也暗指了它们的生命周期。</p>
<p>我们常常会在类中保存对<code>Context</code>的引用,但是我们要考虑生命周期的问题:如果被引用的这个<code>Context</code>是一个<code>Acitivity</code>,<strong>如果存放这个引用的类的生命周期大于<code>Activity</code>的生命周期,那么<code>Activity</code>在停止使用之后还被这个类引用着,就会引致无法被GC,造成事实上的内存泄露。</strong></p>
<p>举一个例子,如果使用下面的一个单例来保存<code>Context</code>的引用来加载资源:</p>
<div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomManager</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="n">CustomManager</span> <span class="n">sInstance</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">CustomManager</span> <span class="nf">getInstance</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">sInstance</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">sInstance</span> <span class="o">=</span> <span class="k">new</span> <span class="n">CustomManager</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">sInstance</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="n">Context</span> <span class="n">mContext</span><span class="o">;</span>
<span class="kd">private</span> <span class="nf">CustomManager</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mContext</span> <span class="o">=</span> <span class="n">context</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<p>这段程序的问题在于不知道传入的<code>Context</code>会是什么类型的,可能在初始化的时候传入的是一个<code>Activity</code>/<code>Serivce</code>,那么几乎可以肯定的是,这个<code>Activity</code>/<code>Service</code>将不会在结束以后被垃圾回收。如果是一个<code>Activity</code>,那么这意味着与它相关联的<code>View</code>或是其他庞大的类都将留在内存中而不会被回收。</p>
<p>为了避免这样的问题,我们可以改正这个单例:</p>
<div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomManager</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="n">CustomManager</span> <span class="n">sInstance</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">CustomManager</span> <span class="nf">getInstance</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">sInstance</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//Always pass in the Application Context</span>
<span class="n">sInstance</span> <span class="o">=</span> <span class="k">new</span> <span class="n">CustomManager</span><span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">getApplicationContext</span><span class="o">());</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">sInstance</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="n">Context</span> <span class="n">mContext</span><span class="o">;</span>
<span class="kd">private</span> <span class="nf">CustomManager</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mContext</span> <span class="o">=</span> <span class="n">context</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<p>我们只修改了一处,第7行中我们使用<code>context.getApplicationContext()</code>这个方法来获取<code>Application</code>这个单例,而不是直接保存<code>context</code>本身,这样就可以保证不会出现某<code>context</code>因为被这个单例引用而不能回收的情况。而<code>Application</code>本身是单例这个特性保证了生命周期的一致,不会造成内存的浪费。</p>
<h3>为什么不总是使用<code>application</code>作为<code>context</code></h3>
<p>既然它是一个单例,那么我们为什么不直接在任何地方都只使用它呢?</p>
<p>这是因为各种<code>context</code>的能力有所不同:</p>
<p><img alt="" src="/images/context01.png"></p>
<p>(图片出处见文末)</p>
<p>对几个注解的地方作说明:</p>
<ol>
<li>一个<code>application</code>可以启动一个<code>activity</code>,但是需要新建一个<code>task</code>,在特殊情况下可以这么做,但是这不是一个好的行为因为这会导致一个不寻常的返回栈。</li>
<li>虽然这是合法的,但是会导致填充出来的<code>view</code>使用系统默认的主题而不是我们设置的主题。</li>
<li>如果接收器是<code>null</code>的话是被允许的,通常在4.2及以上的版本中用来获取一个粘性广播的当前值。</li>
</ol>
<p>我们可以发现与<code>UI</code>有关的操作除<code>activity</code>之外都不能完成,在其他地方这些<code>context</code>能做的事情都差不多。</p>
<p>但是我们回过头来想,这三个与<code>UI</code>相关的操作一般都不会在一个<code>activity</code>之外进行,这个特性很大程度上就是系统为我们设计成这样的,如果我们试图去用一个<code>Application</code>去显示一个<code>dialog</code>就会导致异常的抛出和应用的崩溃。</p>
<p>对上面的第二点再进一步解释,虽然我们可以使用<code>application</code>作为<code>context</code>去填充一个<code>view</code>,但是这样填充出的<code>view</code>使用的将会是系统默认的主题,这是因为只有<code>acitivity</code>中才会存有我们定义在<code>manifest</code>中的主题信息,其他的<code>context</code>将会使用默认的主题去填充<code>view</code>。</p>
<h3>如何使用正确的<code>Context</code></h3>
<p>既然我们不能将<code>Activity</code>作为<code>context</code>保存在另外一个比该<code>Activity</code>生命周期长的类中,那么如果我们需要在这个类中完成与<code>UI</code>有关的操作(比如显示一个<code>dialog</code>)该怎么办?</p>
<p>如果真的遇到了这样的情况:我们不得不保存一个<code>activity</code>在一个比该<code>Activity</code>生命周期长的类中以进行<code>UI</code>操作,就说明我们的设计是有问题的,系统的设计决定了我们不应该去进行这样的操作。</p>
<p>所以我们可以得出结论:</p>
<p>我们应该在<code>Activity</code>/<code>Service</code>的生命周期范围内直接使用该<code>Activity</code>/<code>Service</code>作为<code>context</code>,在它们的范围之外的类,应该使用<code>Application</code>单例这个<code>context</code>(并且不应该出现<code>UI</code>操作)。</p>
<h2>Reference</h2>
<p><a href="https://possiblemobile.com/2013/06/context/">https://possiblemobile.com/2013/06/context/</a></p>
<p><a href="https://web.archive.org/web/20170621005334/http://levinotik.tumblr.com/post/15783237959/demystifying-context-in-android">https://web.archive.org/web/20170621005334/http://levinotik.tumblr.com/post/15783237959/demystifying-context-in-android</a></p>
<p><a href="http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/app/ContextImpl.java?av=f">http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/app/ContextImpl.java?av=f</a></p>
</article>
<div class="tags">
<p>tags: <a href="/tag/android.html">android</a></p>
</div>
<hr>
</div>
</div>
</div>
<hr>
<!-- Footer -->
<footer>
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<ul class="list-inline text-center">
<li>
<a href="https://github.com/HUSTMeituanClub">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-github fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
<li>
<a href="mailto:@hustmeituan.club">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-envelope fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
</ul>
<p class="copyright text-muted">
Blog powered by <a href="http://getpelican.com">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</p> </div>
</div>
</div>
</footer>
<!-- jQuery -->
<script src="/theme/js/jquery.min.js"></script>
<!-- Bootstrap Core JavaScript -->
<script src="/theme/js/bootstrap.min.js"></script>
<!-- Custom Theme JavaScript -->
<script src="/theme/js/clean-blog.min.js"></script>
</body>
</html>