前言
在上篇软件设计原则和设计模式小记(一)中,主要介绍了面向对象的S.O.L.I.D原则和高内聚、低耦合的概念。本文将继续介绍常用的几种设计模式:单例模式、简单工厂模式、策略模式、适配器模式、模板方法模式、装饰者模式以及享元模式。
有空的博客
在上篇软件设计原则和设计模式小记(一)中,主要介绍了面向对象的S.O.L.I.D原则和高内聚、低耦合的概念。本文将继续介绍常用的几种设计模式:单例模式、简单工厂模式、策略模式、适配器模式、模板方法模式、装饰者模式以及享元模式。
从部门老大对于软件设计原则和设计模式的分享,再到近阶段的工作都涉及了软件设计相关的知识,都让我对软件设计这一块难啃的骨头有了小小的认识。本文中的软件设计原则也只简要论述了面向对象的S.O.L.I.D原则 ,设计模式也只介绍了常用的几种。
SOLID是五个面向对象编程的重要原则的缩写,它是由Robert C. Martin(Bob大叔)在21世纪初定义的。它是每个开发者必备的基本知识。运用这些原则能让我们写出更优质的代码,能使我们的代码更健壮,更易于维护,有更好的可持续性、扩展性和鲁棒的代码。
内聚:一个模块内各个元素彼此结合的紧密程度;高内聚就是每个模块尽可能独立完成自己的功能,不依赖模块外部的代码。
一个类或者模块应该有且只有一个改变的原因
职责单一原则的核心思想是:一个类或模块只负责一个功能,并且只有一个引起它变化的原因。单一职责原则可以看作是低耦合,高内聚在面向对象的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。 职责越多,可能引起它变化的原因就越多,也将导致职责依赖严重,从而使耦合度增大。
首先我们先看一个用户信息类(UserInfo):
1 | public interface IUserInfo{ |
上面的代码将业务对象和业务逻辑的内容放到了一个类中,业务对象和业务逻辑都会引起IUserInfo类的变化,上面的代码就违反了单一职责原则。按照SRP原则,我们应该为不同的职责创建不同的类,修改如下:
1 | public interface IUserBro{ |
这样,我们就有了两个类,但是每个类都有单一的职责,我们使它变成了低耦合高内聚。
使用多个专用的接口比使用一个通用接口好
ISP原则意思是把功能实现在接口中,而不是类中;并且一个类绝不要实现不会用到的接口。不遵循这个原则意味着我们在实现里会依赖很多我们并不需要的方法,但又不得不去定义。所以,实现多个特定的接口比实现一个通用接口要好。一个接口被需要用到的类所定义,所以这个接口不应该有这个类不需要实现的其他方法。
我们有一个ICar的接口:
1 | public interface ICar { |
同时也有一个实现ICar接口的Mustang类:
1 | public class Mustang implements ICar { |
现在要添加一个新的车型:一辆DeloRean,可以穿梭时空。
我们修改ICar接口类以满足此需求:
1 | public interface ICar { |
新增DeloRean类实现ICar接口:
1 | public class DeloRean implements ICar { |
同时修改Mustang类:
1 | public class Mustang implements ICar { |
这种情况下,Mustang违反了接口隔离原则,它实现了它不需要的方法。
使用接口隔离的解决方法:
重构ICar接口:
1 | public interface ICar { |
新增ITimeMachine接口:
1 | public interface ITimeMachine { |
重构Mustang(只实现ICar接口)
1 | public class Mustang implements ICar { |
重构DeloRean(同时实现ICar和ITimeMachine)
1 | public class DeloRean implements ICar, ITimeMachine { |
耦合:模块与模块之间接口的复杂程度,模块之间联系越复杂耦合度越高,牵一发动全身;低耦合就是要尽可能减少模块之间的交互复杂程度。
软件中的对象(类、模块、函数等等)应该对于扩展是开放的,但是对于修改是封闭的
根据这一原则,一个实体是允许在不改变它的源代码的前提下变更它的行为。
对于面向对象来说,需要你依赖抽象,而不是实现。
首先我们有两个不同形状的类:
1 | public class Rectangle{ |
其次我们还有一个类可以画出不同的形状:
1 | public class ShapePrinter{ |
从上面可以看到,当我们每次想画一个新的形状就要修改ShapePrinter类的drawShape方法来接受这个新的形状。这样当形状种类多了之后,ShapePrinter类就会存在大量的1
2
3
4
5
6
7
8
9
按照OCP原则,修改如下:
我们添加一个Shape接口类:
```java
public interface Shape{
void draw();
}
重构Rectangle类和Square类以实现Shape:
1 | public class Rectangle implements Shape{ |
ShapePrinter类的重构:
1 | public void drawShape(Shape shape){ |
派生类(子类)对象可以在程式中代替其基类(超类)对象。“Subtypes must be substituable for their base types”
根据定义所说,程序里的对象都应该可以被它的子类实例替换而不用更改程序,另外不应该在代码中出现if/else之类对子类类型进行判断的条件。里氏替换原则是使代码符合开闭原则的一个重要保证。正是由于子类的可替换性才使得父类型的模块在无需修改的情况下就可以扩展。
我们有一个Rectangle类:
1 | public class Rectangle { |
还有一个Square类(从数学上讲正方形也是长方形的一种):
1 | public class Square extends Rectangle { |
然后我们写个测试函数:
1 | public class LiskovSubstitutionTest { |
同样的函数却不适用Square:
1 | public class LiskovSubstitutionTest { |
从代码上看出Square并不能正确替代Rectangle类,因为它不遵循Rectangle的行为规则,违反了LSP原则。
解决方法:
用IShape接口来获取面积:
1 | public interface IShape { |
重构Rectangle和Square以实现IShape:
1 | public class Rectangle implements IShape { |
修改函数:
1 | public class LiskovSubstitutionTest { |
高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口
抽象接口不应该依赖于具体实现,而具体实现则应该依赖于抽象接口
在面向对象编程领域中,依赖反转原则是指一个特定的类不应该直接依赖于另一个类,但是可以依赖于这个类的抽象(接口)。
附上来自维基百科上的图:
图1中,高层对象A依赖于底层对象B的实现;图2中,把高层对象A对底层对象的需求抽象为一个接口A,底层对象B实现了接口A,这就是依赖反转。
我们有个DeliveryDriver类代表着一个司机为快递公司工作:
1 | public class DeliveryDriver{ |
其次有个DeliveryCompany类处理货物装运:
1 | public class DeliveryCompany{ |
在上述代码中,DeliveryCompany创建并使用DeliveryDriver实例,所以DeliveryCompany是一个依赖于低层次类的高层次的类,这就违反了依赖反转原则(在上述代码中DeliveryCompany需要运送货物,必须需要一个DeliveryDriver参与,但如果以后对DeliveryDriver有更多的要求,那我们既要修改DeliveryDriver也要修改上述代码,这样造成的依赖,耦合度高。)
解决方法:
创建DeliveryService接口:
1 | public interface DeliveryService{ |
重构DeliveryDriver类以实现DeliveryService接口:
1 | public class DeliveryDriver implements DeliveryService{ |
重构DeliveryCompany类,使它依赖于一个抽象而不是一个具体的类:
1 | public class DeliveryCompany{ |
最近在工作中第一次接触UML相关内容,本篇为学习笔记,写得不好,还请大神们多多指教。以下是本篇文章的大体结构。
以下定义引自维基百科:
统一建模语言(Unified Modeling Language,缩写UML)是非专利的第三代建模和规约语言。UML是一种开放的方法,用于说明、可视化、构建和编写一个正在开发的、面向对象的、软件密集系统的制品的开放方法。
UML中的三个主要模型:
在UML 2.2中一共定义了14种图示。这里只介绍序列图和类图。
序列图(Sequence Diagram),也叫做循序图,是一种UML行为图。大多数人翻译为时序图(维基百科上称这种叫法并不准确),UML规范中对Sequence Diagram是这么解释的:
A sequence diagram describes an Interaction by focusing on the sequence of Messages that are exchanged, along with their corresponding Occurrence Specifications on the Lifelines
事实上,序列图与我们中学时代学的流程图大同小异,它描述的是消息在生命线上按照约定顺序执行一种交互行为。它是属于一种动态模型。
类图是统一建模语言中一种对象模型,这种图描述的是系统的类集合,类的属性和类之间的关系。
上图是类图的示例,一个类有三个区域,最上面的是类名称,中间部分是类的属性,底部是类的方法。
UML提供机制,以代表类的成员,如属性和方法。指定一个类成员(即任何属性和方法)的可见性有下列符号:
1 | + public 公有 |
即继承(extend)的反方向。在UML类图中用带空心三角形的直线表示。如下图所示:
即对应implements关键字。用带空心三角形的虚线表示
依赖关系可以理解为一个类A使用到了另一个类B,被依赖的对象只是作为一种工具在使用,而并不持有对它的引用。
这种使用关系是具有偶然性、临时性的、非常弱的
但是B类的变化会影响到类A,表现在代码层面:类B作为参数被类A在某个方法中使用。用带燕尾箭头的虚线表示。
聚合是表示整体与部分的一类特殊的关联关系,是弱的包含关系,成分类可以不依靠聚合类而单独存在,可以具有各自的生命周期,部分可以属于多个整体,也可以为多个整体对象共享。用以带空心的菱形剪尾的实线表示。
组合是一类强的整体与部分的包含关系。成分类必须依靠合成类而存在,整体与部分是不可分的(这是与聚合关系最大的区别),整体的生命周期结束也就意味着部分的生命周期结束。图形中用带实心的菱形剪尾的实线表示。
关联是两个类之间,或类与接口之间一种强依赖关系,是一种长期的稳定关系。关联关系又分为单向关联、双向关联和自关联。
前言:
只能说对魅族手机系统很是无奈啊!!!为了分析魅族手机无法通过常见的方法跳到系统设置的无障碍服务,只好通过豌豆荚将settings.apk导出然后进行反编译,跟踪源码…….
首先跟踪Settings的启动类,查看package/app/Settings/AndroidManifest.xml文件
1 | <activity-alias android:label="@string/settings_label_launcher" android:launchMode="singleTask" android:name="com.android.settings.Settings" android:targetActivity="com.android.settings.Settings" android:taskAffinity="com.android.settings"> |
第一次遇到
从AndroidManifest.xml可以看到,Settings的启动类为Settings.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
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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414public class Settings extends SettingsActivity
{
public static class AccessibilityDaltonizerSettingsActivity
extends SettingsActivity
{}
public static class AccessibilitySettingsActivity
extends SettingsActivity
{}
public static class AccountSettingsActivity
extends SettingsActivity
{}
public static class AccountSyncSettingsActivity
extends SettingsActivity
{}
public static class AdvancedAppsActivity
extends SettingsActivity
{}
public static class AdvancedWifiSettingsActivity
extends SettingsActivity
{}
public static class AllApplicationsActivity
extends SettingsActivity
{}
public static class AndroidBeamSettingsActivity
extends SettingsActivity
{}
public static class ApnEditorActivity
extends SettingsActivity
{}
public static class ApnSettingsActivity
extends SettingsActivity
{}
public static class AppDrawOverlaySettingsActivity
extends SettingsActivity
{}
public static class AppMemoryUsageActivity
extends SettingsActivity
{}
public static class AppNotificationSettingsActivity
extends SettingsActivity
{}
public static class AppWriteSettingsActivity
extends SettingsActivity
{}
public static class AvailableVirtualKeyboardActivity
extends SettingsActivity
{}
public static class BackgroundCheckSummaryActivity
extends SettingsActivity
{}
public static class BatterySaverSettingsActivity
extends SettingsActivity
{}
public static class BluetoothSettingsActivity
extends SettingsActivity
{}
public static class CaptioningSettingsActivity
extends SettingsActivity
{}
public static class ChooseAccountActivity
extends SettingsActivity
{}
public static class ConfigureNotificationSettingsActivity
extends SettingsActivity
{}
public static class CryptKeeperSettingsActivity
extends SettingsActivity
{}
public static class DataUsageSummaryActivity
extends SettingsActivity
{}
public static class DateTimeSettingsActivity
extends SettingsActivity
{}
public static class DevelopmentSettingsActivity
extends SettingsActivity
{}
public static class DeviceAdminSettingsActivity
extends SettingsActivity
{}
public static class DeviceInfoSettingsActivity
extends SettingsActivity
{}
public static class DeviceSettings
extends SettingsActivity
{}
public static class DisplaySettingsActivity
extends SettingsActivity
{}
public static class DomainsURLsAppListActivity
extends SettingsActivity
{}
public static class DreamSettingsActivity
extends SettingsActivity
{}
public static class FingerprintEnrollSuggestionActivity
extends FingerprintEnrollIntroduction
{}
public static class FingerprintSuggestionActivity
extends FingerprintSettings
{}
public static class HighPowerApplicationsActivity
extends SettingsActivity
{}
public static class HomeSettingsActivity
extends SettingsActivity
{}
public static class IccLockSettingsActivity
extends SettingsActivity
{}
public static class ImeiInformationActivity
extends SettingsActivity
{}
public static class InputMethodAndLanguageSettingsActivity
extends SettingsActivity
{}
public static class KeyboardLayoutPickerActivity
extends SettingsActivity
{}
public static class LocalePickerActivity
extends SettingsActivity
{}
public static class LocationSettingsActivity
extends SettingsActivity
{}
public static class ManageApplicationsActivity
extends SettingsActivity
{}
public static class ManageAssistActivity
extends SettingsActivity
{}
public static class MemorySettingsActivity
extends SettingsActivity
{}
public static class NotificationAccessSettingsActivity
extends SettingsActivity
{}
public static class NotificationAppListActivity
extends SettingsActivity
{}
public static class NotificationStationActivity
extends SettingsActivity
{}
public static class NotificationStatusbarSettingsActivity
extends SettingsActivity
{}
public static class OtherSoundSettingsActivity
extends SettingsActivity
{}
public static class OverlaySettingsActivity
extends SettingsActivity
{}
public static class PaymentSettingsActivity
extends SettingsActivity
{}
public static class PersonalSettings
extends SettingsActivity
{}
public static class PhysicalKeyboardActivity
extends SettingsActivity
{}
public static class PowerUsageSummaryActivity
extends SettingsActivity
{}
public static class PrintJobSettingsActivity
extends SettingsActivity
{}
public static class PrintSettingsActivity
extends SettingsActivity
{}
public static class PrivacySettingsActivity
extends SettingsActivity
{}
public static class PrivateVolumeForgetActivity
extends SettingsActivity
{}
public static class PrivateVolumeSettingsActivity
extends SettingsActivity
{}
public static class PublicVolumeSettingsActivity
extends SettingsActivity
{}
public static class RunningServicesActivity
extends SettingsActivity
{}
public static class SavedAccessPointsSettingsActivity
extends SettingsActivity
{}
public static class ScreenLockSuggestionActivity
extends ChooseLockGeneric
{}
public static class SecuritySettingsActivity
extends SettingsActivity
{}
public static class SimSettingsActivity
extends SettingsActivity
{}
public static class SimStatusActivity
extends SettingsActivity
{}
public static class SoundSettingsActivity
extends SettingsActivity
{}
public static class SpellCheckersSettingsActivity
extends SettingsActivity
{}
public static class StatusActivity
extends SettingsActivity
{}
public static class StorageSettingsActivity
extends SettingsActivity
{}
public static class StorageUseActivity
extends SettingsActivity
{}
public static class SystemSettings
extends SettingsActivity
{}
public static class TestingSettingsActivity
extends SettingsActivity
{}
public static class TetherSettingsActivity
extends SettingsActivity
{}
public static class TextToSpeechSettingsActivity
extends SettingsActivity
{}
public static class TopLevelSettings
extends SettingsActivity
{}
public static class TrustedCredentialsSettingsActivity
extends SettingsActivity
{}
public static class UsageAccessSettingsActivity
extends SettingsActivity
{}
public static class UserDictionarySettingsActivity
extends SettingsActivity
{}
public static class UserSettingsActivity
extends SettingsActivity
{}
public static class VpnSettingsActivity
extends SettingsActivity
{}
public static class VrListenersSettingsActivity
extends SettingsActivity
{}
public static class WallpaperSettingsActivity
extends SettingsActivity
{}
public static class WallpaperSuggestionActivity
extends SettingsActivity
{}
public static class WifiAPITestActivity
extends SettingsActivity
{}
public static class WifiCallingSettingsActivity
extends SettingsActivity
{}
public static class WifiCallingSuggestionActivity
extends SettingsActivity
{}
public static class WifiDisplaySettingsActivity
extends SettingsActivity
{}
public static class WifiInfoActivity
extends SettingsActivity
{}
public static class WifiP2pSettingsActivity
extends SettingsActivity
{}
public static class WifiSettingsActivity
extends SettingsActivity
{}
public static class WirelessSettings
extends SettingsActivity
{}
public static class WirelessSettingsActivity
extends SettingsActivity
{}
public static class WriteSettingsActivity
extends SettingsActivity
{}
public static class ZenAccessSettingsActivity
extends SettingsActivity
{}
public static class ZenModeAutomationSettingsActivity
extends SettingsActivity
{}
public static class ZenModeAutomationSuggestionActivity
extends SettingsActivity
{}
public static class ZenModeEventRuleSettingsActivity
extends SettingsActivity
{}
public static class ZenModeExternalRuleSettingsActivity
extends SettingsActivity
{}
public static class ZenModePrioritySettingsActivity
extends SettingsActivity
{}
public static class ZenModeScheduleRuleSettingsActivity
extends SettingsActivity
{}
public static class ZenModeSettingsActivity
extends SettingsActivity
{}
public static class ZenModeVisualInterruptionSettingsActivity
extends SettingsActivity
{}
}
Settings.java定义了大量静态内部类,没有任何跟界面UI相关的内容。这时候我就很困惑了,跳转到无障碍服务设置界面的内部实现是什么样的,不过从源码上看应该是AccessibilitySettingsActivity这个类,其继承自SettingsActivity类,所以继续跟踪。
1 | public class SettingsActivity extends SettingsDrawerActivity |
SettingsDrawerActivity类最终继承自Activity,所以我们应该从onCreate()入手。1
2
3
4
5
6
7
8
9 protected void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
System.currentTimeMillis();
Object localObject = Settings.Secure.getUriFor("mz_current_power_mode");
getContentResolver().registerContentObserver((Uri)localObject, false, this.mPowerSaveObserver, -1);
// Should happen before any call to getIntent()
getMetaData();
localObject = getIntent();
这里有个获取MetaData的方法,我们看一下这个方法的具体实现,同时我们从注释还可以看到这个方法需要在调用getIntent()之前进行调用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20private void getMetaData()
{
try
{
ActivityInfo localActivityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
if (localActivityInfo != null)
{
if (localActivityInfo.metaData == null) {
return;
}
this.mFragmentClass = localActivityInfo.metaData.getString("com.android.settings.FRAGMENT_CLASS");
return;
}
}
catch (PackageManager.NameNotFoundException localNameNotFoundException)
{
Log.d("Settings", "Cannot get Metadata for: " + getComponentName().toString());
return;
}
}
这个函数的作用就是从Activity标签中获取meta-data中key为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```java
<activity android:configChanges="keyboardHidden|orientation|screenSize" android:icon="@drawable/mz_function_list_ic_accessibility" android:label="@string/accessibility_settings" android:name="com.android.settings.Settings$AccessibilitySettingsActivity" android:taskAffinity="">
<intent-filter android:priority="1">
<action android:name="android.settings.ACCESSIBILITY_SETTINGS"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:host="com.android.settings" android:path="/accessibility_settings" android:scheme="flyme_3dtouch"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.VOICE_LAUNCH"/>
<category android:name="com.android.settings.SHORTCUT"/>
</intent-filter>
<intent-filter android:priority="3">
<action android:name="com.android.settings.action.SETTINGS"/>
</intent-filter>
<meta-data android:name="com.android.settings.category" android:value="com.android.settings.category.system"/>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS" android:value="com.meizu.settings.accessibility.FlymeAccessibilitySettings"/>
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED" android:value="true"/>
<meta-data android:name="usagestats.event" android:value="accessibility_settings-click"/>
</activity>
从上面可以看出mFragmentClass = “com.meizu.settings.accessibility.FlymeAccessibilitySettings”。
找到了Fragment,就要看看它是怎么加载Fragment的,继续看onCreate函数,接下来就跟踪到了getIntent()方法: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
40public Intent getIntent()
{
Intent localIntent1 = super.getIntent();
Object localObject = getStartingFragmentClass(localIntent1);
if (localObject != null)
{
Intent localIntent2 = new Intent(localIntent1);
localIntent2.putExtra(":settings:show_fragment", (String)localObject);
localObject = localIntent1.getExtras();
if (localObject != null) {}
for (localObject = new Bundle((Bundle)localObject);; localObject = new Bundle())
{
((Bundle)localObject).putParcelable("intent", localIntent1);
localIntent2.putExtra(":settings:show_fragment_args", (Bundle)localObject);
return localIntent2;
}
}
return localIntent1;
}
private String getStartingFragmentClass(Intent paramIntent)
{
if (this.mFragmentClass != null) {
return this.mFragmentClass;
}
String str = paramIntent.getComponent().getClassName();
if (str.equals(getClass().getName())) {
return null;
}
if ((!"com.android.settings.ManageApplications".equals(str)) && (!"com.android.settings.RunningServices".equals(str)))
{
paramIntent = str;
if (!"com.android.settings.applications.StorageUse".equals(str)) {}
}
else
{
paramIntent = ManageApplications.class.getName();
}
return paramIntent;
}
从源码可以看出getIntent()就是返回了一个Intent,并传了一个参数(key为1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24分析了getIntent,继续往下看onCreate函数,看这个intent是如何使用的。
```java
if (!this.mIsShowingDashboard)
{
this.mDisplaySearch = false;
if (this.mIsShortcut) {
this.mDisplayHomeAsUpEnabled = bool;
}
for (;;)
{
setTitleFromIntent((Intent)localObject);
switchToFragment(str1, ((Intent)localObject).getBundleExtra(":settings:show_fragment_args"), true, false, this.mInitialTitleResId, this.mInitialTitle, false);
break;
if (bool)
{
this.mDisplayHomeAsUpEnabled = true;
this.mDisplaySearch = false;
}
else
{
this.mDisplayHomeAsUpEnabled = true;
}
}
}
从上面源码大概能看出关键部分应该是switchToFragment()函数。我们再看一下switchToFragment的实现: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
36private Fragment switchToFragment(String paramString, Bundle paramBundle, boolean paramBoolean1, boolean paramBoolean2, int paramInt, CharSequence paramCharSequence, boolean paramBoolean3)
{
if (FlymeDashboardSummary.isConnectivitySettingsFragment(paramString))
{
FlymeDashboardSummary.startWithFragment(this, paramString, paramBundle, null, 0, this.mInitialTitleResId, this.mInitialTitle);
finish();
return null;
}
if ((!paramBoolean1) || (isValidFragment(paramString)))
{
paramString = Fragment.instantiate(this, paramString, paramBundle);
paramBundle = getFragmentManager().beginTransaction();
paramBundle.replace(this.mMainContentId, paramString);
if (paramBoolean3) {
TransitionManager.beginDelayedTransition(this.mContent);
}
if (paramBoolean2) {
paramBundle.addToBackStack(":settings:prefs");
}
if (paramInt <= 0) {
break label145;
}
paramBundle.setBreadCrumbTitle(paramInt);
}
for (;;)
{
paramBundle.commitAllowingStateLoss();
getFragmentManager().executePendingTransactions();
return paramString;
throw new IllegalArgumentException("Invalid fragment for this activity: " + paramString);
label145:
if (paramCharSequence != null) {
paramBundle.setBreadCrumbTitle(paramCharSequence);
}
}
}
从上面可以看出先通过指定的mFragment实例化fragment,然后通过FragmentTransaction的replace方法加载fragment。
通过上述分析,我们知道了Settings.apk如何通过隐式的Intent调转到对应的Activity布局。
当然到这一步还是不能帮我解决问题,还是没有解决魅族手机无法通过常见的方法跳到系统设置的无障碍服务的问题。
从上面的分析我们已经能够知道魅族手机设置–>辅助功能对应的Fragment是FlymeAccessibilitySettings这个类,源码如下:
1 | public class FlymeAccessibilitySettings extends SettingsPreferenceFragment |
可以看出FlymeAccessibilitySettings是继承自PreferenceFragment类,它一个最重要的方法是调用addPreferencesFromResource()参数为xml资源id,来加载静态xml资源文件 (在res文件夹下新建xml文件夹,再在xml文件中新建对应的xml资源),完成Preference界面的构建。所以我们可以依次找到FlymeAccessibilitySettings对应的xml资源文件为mz_accessibility_settings.xml,内容如下: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<?xml version="1.0" encoding="utf-8"?>
<android.preference.PreferenceScreen android:title="@string/accessibility_settings"
xmlns:android="http://schemas.android.com/apk/prv/res/android">
<android.preference.PreferenceCategory android:title="@string/interactive" android:key="interactive" />
<android.preference.Preference android:title="@string/multi_task_title" android:key="multi_task" android:widgetLayout="@167968832" android:fragment="com.meizu.settings.accessibility.FlymeMultiTaskFragment" />
<android.preference.Preference android:title="@string/quick_wakeup_title" android:key="quick_wakeup" android:summary="@string/quick_wakeup_tips" android:widgetLayout="@167968832" android:fragment="com.meizu.settings.accessibility.FlymeQuickWakeupFragment" />
<android.preference.Preference android:title="@string/smart_voice_wakeup" android:key="smart_voice_wakeup" android:summary="@string/smart_voice_wakeup_tips" android:widgetLayout="@167968832" />
<android.preference.Preference android:title="@string/smart_touch_title" android:key="smart_touch" android:summary="@string/smart_touch_tips" android:widgetLayout="@167968832" android:fragment="com.meizu.settings.accessibility.FlymeSmartTouchFragment" />
<android.preference.Preference android:title="@string/force_touch_title" android:key="force_touch" android:widgetLayout="@167968832" android:fragment="com.meizu.settings.accessibility.ForceTouchSettingsFragment" />
<android.preference.PreferenceCategory android:title="@string/header_category_lab" android:key="interactive" />
<android.preference.Preference android:title="@string/safe_family_title" android:key="safe_family" android:widgetLayout="@167968832" />
<android.preference.Preference android:title="@string/children_mode_title" android:key="children_mode" android:widgetLayout="@167968832" />
<android.preference.Preference android:title="@string/app_clone_title" android:key="app_clone" android:widgetLayout="@167968832" android:fragment="com.meizu.settings.appclone.AppCloneSettings" />
<android.preference.Preference android:title="@string/game_mode_title" android:key="game_mode" android:widgetLayout="@167968832" android:fragment="com.meizu.settings.accessibility.GameModeSettings" />
<android.preference.Preference android:title="@string/red_envelope_title" android:key="red_envelope_assistant" android:widgetLayout="@167968832" android:fragment="com.meizu.settings.notificationstatusbar.FlymeRedEnvelopeAssistantSettings" />
<android.preference.Preference android:title="@string/flashlamp_effects_title" android:key="flashlamp_effects" android:widgetLayout="@167968832" android:fragment="com.meizu.settings.accessibility.FlymeFlashLampEffectsSettings" />
<android.preference.Preference android:title="@string/classic_mode" android:key="classic_mode" android:widgetLayout="@167968832" android:fragment="com.meizu.settings.accessibility.FlymeClassicModeFragment" />
<android.preference.PreferenceCategory android:title="@string/accessiblity_category_system" android:key="accessiblity_category_system" />
<ListPreference android:persistent="false" android:entries="@array/home_key_behavior_double_click_entries" android:title="@string/home_key_behavior_double_click" android:key="home_double_click" android:entryValues="@array/home_key_behavior_double_click_values" />
<ListPreference android:persistent="false" android:entries="@array/home_key_behavior_entries" android:title="@string/home_key_behavior" android:key="home_long_press" android:entryValues="@array/home_key_behavior_values" />
<ListPreference android:persistent="false" android:entries="@array/keyboard_back_behavior_entries" android:title="@string/keyboard_back_behavior" android:key="keyboard_back_behavior" android:entryValues="@array/keyboard_back_behavior_values" />
<com.meizu.common.preference.SwitchPreference android:persistent="false" android:title="@string/headset_middlekey_wakeup" android:key="headset_middlekey_wakeup" android:summary="@string/headset_middlekey_wakeup_summay" />
<com.meizu.common.preference.SwitchPreference android:title="@string/light_feedback_enable_title" android:key="light_feedback" android:defaultValue="true" />
<com.meizu.common.preference.SwitchPreference android:title="@string/keyguard_palm_rejection_title" android:key="palm_rejection" android:summary="@string/keyguard_palm_rejection_summary" android:defaultValue="true" />
<com.meizu.common.preference.SwitchPreference android:title="@string/hall_switch_title" android:key="use_cover" android:summary="@string/hall_switch_summary" android:defaultValue="true" />
<com.meizu.common.preference.SwitchPreference android:title="@string/mcharge_title" android:key="fast_charge" android:defaultValue="true" />
<android.preference.Preference android:title="@string/search_settings_title" android:key="search_settings" android:widgetLayout="@167968832" />
<android.preference.Preference android:title="@string/scheduled_power_onandoff_title" android:key="scheduled_power" android:widgetLayout="@167968832" android:fragment="com.meizu.settings.scheduledpower.ScheduledPowerFragment" />
<android.preference.Preference android:title="@string/drive_mode_title" android:key="drive_mode" android:widgetLayout="@167968832" android:fragment="com.meizu.settings.drivemode.MzDriveModeFragment" />
<android.preference.PreferenceScreen android:title="@string/accessible" android:key="accessible_settings" android:fragment="com.android.settings.accessibility.AccessibilitySettings" />
<com.meizu.settings.calibration.ProximityCalibraton android:title="@string/sensor_adjust_title" android:key="sensor_adjust_proximity" />
<com.meizu.settings.calibration.GsensorCalibration android:title="@string/gsensor_calibrate_text" android:key="sensor_adjust_gsensor" />
<android.preference.Preference android:title="@string/development_settings_title" android:key="development_settings" android:widgetLayout="@167968832" android:fragment="com.android.settings.DevelopmentSettings" />
<com.meizu.common.preference.SwitchPreference android:title="@string/data_collection_title" android:key="data_collection" />
</android.preference.PreferenceScreen>
对照着手机界面,我们可以确认这个xml文件就是FlymeAccessibilitySettings类的静态资源文件,从中我们可以找到魅族手机无障碍服务对应的Fragment:1
<android.preference.PreferenceScreen android:title="@string/accessible" android:key="accessible_settings" android:fragment="com.android.settings.accessibility.AccessibilitySettings" />
在PreferenceFragment中点击列表项的点击监听方法为onPreferenceTreeClick();在FlymeAccessibilitySettings类中的源码如下:
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
123public boolean onPreferenceTreeClick(PreferenceScreen paramPreferenceScreen, Preference paramPreference)
{
int k = 0;
int m = 0;
int i = 0;
int j = 1;
this.mUsageStatsProxy.reportData("AccessibilitySettings", paramPreference.getKey(), paramPreference);
if (paramPreference == this.mHomeLightSwitchPreference)
{
paramPreferenceScreen = this.mContentResolver;
if (this.mHomeLightSwitchPreference.isChecked()) {
i = 1;
}
Settings.System.putInt(paramPreferenceScreen, "light_feedback_enabled", i);
return true;
}
if (paramPreference == this.mDataCollectionSwitch)
{
if (this.mDataCollectionSwitch.isChecked())
{
warnDataCollection();
return true;
}
Settings.System.putInt(this.mContentResolver, "meizu_data_collection", 0);
return true;
}
if (paramPreference == this.mSearchPreference)
{
paramPreferenceScreen = new Intent();
paramPreferenceScreen.setAction("com.meizu.net.search.setting");
paramPreferenceScreen.addFlags(335544320);
try
{
startActivity(paramPreferenceScreen);
return true;
}
catch (ActivityNotFoundException paramPreferenceScreen)
{
Log.e("AccessibilitySettings", "ActivityNotFoundException for search preference");
return true;
}
}
if (paramPreference == this.mHeadsetMiddleKeyWakeupPreference)
{
paramPreferenceScreen = this.mContentResolver;
i = k;
if (this.mHeadsetMiddleKeyWakeupPreference.isChecked()) {
i = 1;
}
Settings.System.putInt(paramPreferenceScreen, "headset_middle_key_wakeup", i);
return true;
}
if (paramPreference == this.mSmartVoiceWakeupPreference) {
try
{
paramPreferenceScreen = new Intent("com.mediatek.voicecommand.VOICE_CONTROL_SETTINGS");
paramPreferenceScreen.addFlags(335544320);
startActivity(paramPreferenceScreen);
return true;
}
catch (ActivityNotFoundException paramPreferenceScreen)
{
Log.e("AccessibilitySettings", "ActivityNotFoundException for MTK voice wakeup preference");
startFragment(this, "com.meizu.settings.accessibility.FlymeSmartVoiceWakeupFragment", 2131627644, -1, null);
return true;
}
}
if (this.mFlymeInnovation.onPreferenceTreeClick(paramPreferenceScreen, paramPreference)) {
return true;
}
if (paramPreference == this.mPalmRejection)
{
paramPreferenceScreen = this.mContentResolver;
i = m;
if (this.mPalmRejection.isChecked()) {
i = 1;
}
Settings.System.putInt(paramPreferenceScreen, "keyguard_palm_rejection", i);
return true;
}
if (paramPreference == this.mFastCharge) {
if (!this.mFastCharge.isChecked())
{
this.mChargeDialog = new AlertDialog.Builder(this.mContext, 5).setMessage(2131628228).setPositiveButton(17039379, this).setNegativeButton(17039369, this).show();
this.mChargeDialog.setOnDismissListener(new DialogInterface.OnDismissListener()
{
public void onDismiss(DialogInterface paramAnonymousDialogInterface)
{
boolean bool = true;
paramAnonymousDialogInterface = FlymeAccessibilitySettings.-get4(FlymeAccessibilitySettings.this);
if (Settings.Global.getInt(FlymeAccessibilitySettings.-get0(FlymeAccessibilitySettings.this), "mz_fast_charge", 1) == 1) {}
for (;;)
{
paramAnonymousDialogInterface.setChecked(bool);
return;
bool = false;
}
}
});
}
}
for (;;)
{
return super.onPreferenceTreeClick(paramPreferenceScreen, paramPreference);
Settings.Global.putInt(this.mContentResolver, "mz_fast_charge", 1);
continue;
if (paramPreference == this.mHallSwitch)
{
boolean bool = this.mHallSwitch.isChecked();
ContentResolver localContentResolver = this.mContentResolver;
if (bool) {}
for (i = j;; i = 0)
{
Settings.System.putInt(localContentResolver, "hall_switch", i);
this.mUsageStatsProxy.reportData(FlymeAccessibilitySettings.class.getSimpleName(), paramPreference.getKey(), paramPreference);
break;
}
}
if (paramPreference == this.mSafeFamilyPreference) {
startActivity(getBreakingScamIntent());
}
}
}
从上面我们可以分析出对辅助功能界面各个item的处理,不过最重要的对无障碍的处理是1
2
3
4
5
6
7
8```java
public boolean onPreferenceTreeClick(PreferenceScreen paramPreferenceScreen, Preference paramPreference)
{
if (paramPreference.getFragment() != null) {
return ((SettingsActivity)getActivity()).onPreferenceStartFragment(this, paramPreference);
}
return super.onPreferenceTreeClick(paramPreferenceScreen, paramPreference);
}
因为无障碍服务对应的是另一个Fragment,所以进入if块,调用onPreferenceStartFragment(),源码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public boolean onPreferenceStartFragment(SettingsPreferenceFragment paramSettingsPreferenceFragment, android.preference.Preference paramPreference)
{
paramSettingsPreferenceFragment = paramPreference.getTitle();
if (paramPreference.getFragment().equals(WallpaperTypeSettings.class.getName())) {
paramSettingsPreferenceFragment = getString(2131625303);
}
startPreferencePanel(paramPreference.getFragment(), paramPreference.getExtras(), -1, paramSettingsPreferenceFragment, null, 0);
return true;
}
public void startPreferencePanel(String paramString, Bundle paramBundle, int paramInt1, CharSequence paramCharSequence, Fragment paramFragment, int paramInt2)
{
String str = null;
if (paramInt1 < 0) {
if (paramCharSequence == null) {
break label39;
}
}
label39:
for (str = paramCharSequence.toString();; str = "")
{
Utils.startWithFragment(this, paramString, paramBundle, paramFragment, paramInt2, paramInt1, str, this.mIsShortcut);
return;
}
}
到这一步可以发现最终调用的是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继续往下看:
```java
public static void startWithFragment(Context paramContext, String paramString, Bundle paramBundle, Fragment paramFragment, int paramInt1, int paramInt2, CharSequence paramCharSequence, boolean paramBoolean)
{
paramString = onBuildStartFragmentIntent(paramContext, paramString, paramBundle, null, paramInt2, paramCharSequence, paramBoolean);
if (paramFragment == null)
{
paramContext.startActivity(paramString);
return;
}
paramFragment.startActivityForResult(paramString, paramInt1);
}
public static Intent onBuildStartFragmentIntent(Context paramContext, String paramString1, Bundle paramBundle, String paramString2, int paramInt, CharSequence paramCharSequence, boolean paramBoolean)
{
Intent localIntent = new Intent("android.intent.action.MAIN");
localIntent.setClass(paramContext, SubSettings.class);
localIntent.putExtra(":settings:show_fragment", paramString1);
localIntent.putExtra(":settings:show_fragment_args", paramBundle);
localIntent.putExtra(":settings:show_fragment_title_res_package_name", paramString2);
localIntent.putExtra(":settings:show_fragment_title_resid", paramInt);
localIntent.putExtra(":settings:show_fragment_title", paramCharSequence);
localIntent.putExtra(":settings:show_fragment_as_shortcut", paramBoolean);
return localIntent;
}
不难发现从调用无障碍服务的方式是startActivityForResult(),在这个之前会先调用onBuildStartFragmentInten()构造一个intent。首先我们看到方法内部所调用的Activity为SubSettings类,源码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14public class SubSettings extends SettingsActivity
{
protected boolean isValidFragment(String paramString)
{
Log.d("SubSettings", "Launching fragment " + paramString);
return true;
}
public boolean onNavigateUp()
{
finish();
return true;
}
}
SubSettings继承自SettingsActivity,我们在之前的分析已经能得出SettingsActivity加载Fragment的方式是从AndroidManifest.xml的Activity标签中获取meta-data中key为1
2
3
4
5
6
7```java
<activity android:configChanges="keyboardHidden|orientation|screenSize" android:exported="true" android:name="com.android.settings.SubSettings" android:parentActivityName="com.android.settings.Settings" android:permission="com.meizu.permission.SEARCH_SETTINGS" android:screenOrientation="portrait" android:taskAffinity="com.android.settings">
<intent-filter>
<action android:name="android.intent.action.SEARCH"/>
</intent-filter>
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/>
</activity>
并没有我们所需要的值,但在getIntent中我们可以发现intent值来自1
2
3
4
5
6代码如下:
```java
intent.setAction("android.intent.action.SEARCH");
intent.setComponent(new ComponentName("com.android.settings", "com.android.settings.SubSettings"));
intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, "com.android.settings.accessibility.AccessibilitySettings");
startActivity(intent);
因为跳转SubSettings这个Activity需要声明一个permission,所以我们必须在我们应用加上这个权限声明,但是这会引来另一个问题:Failure [INSTALL_FAILED_DUPLICATE_PERMISSION,这个错误的意思是正要安装的APP的自定义权限与手机上已有APP的自定义权限名字相同,但两个APP具有不同的签名信息导致安装失败。
前言:在使用AlertDialog时,发现即使没有调用dismiss()点击取消或者确定按钮对话框都会自动关闭,看了源码才发现,这一操作在源码已经写死了。
首先我们先看下AlertDialog的一般用法:
1 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) |
AlertDialog采用Builder进行构造参数的配置,最后通过show()方法返回一个已配置的AlertDialog实例。
从API调用我们可以知道应该先从AlertDialog.Builder入手查起,看看Builder的构造器都做了什么。1
2
3
4
5
6
7
8
9public Builder(Context context) {
this(context, resolveDialogTheme(context, 0));
}
public Builder(Context context, int theme) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, theme)));
mTheme = theme;
}
接着分析builder.setPositiveButton()方法:1
2
3
4
5public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
P.mPositiveButtonText = text;
P.mPositiveButtonListener = listener;
return this;
}
从上面的代码我们可以看出最终的操作都指向了AlertController这个类(AlertController类是Android的内部类,在com.android.internal.app包中,无法通过普通的方式访问,也无法在eclipse通过按Ctrl键跟踪进源代码),在AlertController的构造器中我们看到了一个Hanlder类:1
2
3
4
5
6
7 public AlertController(Context context, DialogInterface di, Window window) {
mContext = context;
mDialogInterface = di;
mWindow = window;
mHandler = new ButtonHandler(di);
...
}
再看下ButtonHandler的实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25private static final class ButtonHandler extends Handler {
// Button clicks have Message.what as the BUTTON{1,2,3} constant
private static final int MSG_DISMISS_DIALOG = 1;
private WeakReference<DialogInterface> mDialog;
public ButtonHandler(DialogInterface dialog) {
mDialog = new WeakReference<DialogInterface>(dialog);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DialogInterface.BUTTON_POSITIVE:
case DialogInterface.BUTTON_NEGATIVE:
case DialogInterface.BUTTON_NEUTRAL:
((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
break;
case MSG_DISMISS_DIALOG:
((DialogInterface) msg.obj).dismiss();
}
}
}
在这里我们看到了熟悉的dismiss()方法。而在这个类的开头我们就发现了OnClickListener事件的实例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19View.OnClickListener mButtonHandler = new View.OnClickListener() {
public void onClick(View v) {
Message m = null;
if (v == mButtonPositive && mButtonPositiveMessage != null) {
m = Message.obtain(mButtonPositiveMessage);
} else if (v == mButtonNegative && mButtonNegativeMessage != null) {
m = Message.obtain(mButtonNegativeMessage);
} else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
m = Message.obtain(mButtonNeutralMessage);
}
if (m != null) {
m.sendToTarget();
}
// Post a message so we dismiss after the above handlers are executed
mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
.sendToTarget();
}
};
这段代码的前几行用来触发对话框中的三个按钮,而在最后的代码就是通过mHandler来关闭对话框的。
总结
总结对话框单击事件的处理过程:在AlertController类处理对话框按钮时会为每个按钮添加一个onClick事件,而这个事件的实例就是上面的mButtonHandler,在这个实例中会通过发消息的方式调用为按钮设置的单击事件,在触发完按钮的单击事件后,会通过发消息的方式调用dismiss()来关闭对话框,这就是没有调用dismiss()点击取消或者确定按钮对话框都会自动关闭的原因。
1 | private class MyButtonHandler extends Handler{ |
上面自定义的Handler类和源代码相比只去掉了最后一个case语句。
然后在dialog调用show()方法之前为AlertController类的mHandler变量重新赋值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19try {
/**
* 通过获取字段,然后设定setAccessible为true,即可访问私有变量
*/
Field field = dialog.getClass().getDeclaredField("mAlert");
field.setAccessible(true);
Object obj = field.get(dialog);
field = obj.getClass().getDeclaredField("mHandler");
field.setAccessible(true);
field.set(obj, new MyButtonHandler(dialog));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
dialog.show();
1 | @Override |
在dismissDialog()内部会先判断对话框是否关闭(对应mShowing标识位),所以我们可以在代码中设置这个值,使系统认为对话框已经关闭。1
2
3
4
5
6
7
8
9
10
11
12try {
Field field = dialog.getClass().getSuperclass().getDeclaredField("mShowing");
field.setAccessible(true);
field.set(dialog, false);
dialog.dismiss();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
本文结构:
前言:介绍Activity的启动模式之前先介绍一下任务栈。任务栈是一个“后进先出”的栈结构,每按一下back键就有一个Activity出栈,直到栈空为止,当栈中无任何Activity的时候,系统就会回收这个任务栈。
Activity的LaunchMode目前有四种启动模式:standard、singleTop、singleTask以及singleInstance。
注意:当我们用ApplicationContext去启动standard模式的Activity的时候就会报错,log如下:
1 | ...Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag.... |
这是因为standard模式下的Activity默认会进入启动它的Activity所属的任务栈中,但是由于非Activity类型的context并没有任务栈,所以解决这个问题的办法是为待启动Activity指定FLAG_ACTIVITY_NEW_TASK标记位。这个时候待启动Activity实际上是以singleTask模式启动的。
什么是Activity所需要的任务栈?这要从一个参数说起:TaskAffinity,任务相关性。这个参数标识了一个Activity所需要的任务栈的名字,默认情况下所有Activity所需的任务栈的名字为应用的包名,但是我们可以为每个Activity都单独指定TaskAffinity属性,这个属性值不能和包名一样,否则就相当于没有指定。TaskAffinity属性主要和singleTask启动模式和allowTaskReparenting属性配对使用,在其他情况下没有意义。
通过AndroidMenifest为Activity指定启动模式。
1 | <activity |
通过在Intent设置标志位来为Activity指定启动模式
1 | Intent intent = new Intent(this, TestActivity.class); |
二者区别:首先,优先级上第二种高于第一种,当两种同时存在时,以第二种为准;其次,二者在限定范围上有所不同。
本文结构:
Java Annotation是JDK5.0引入的一种注释机制。
关于Annotation注解的概念,我们可以先看下官方的解释:
Annotations, a form of metadata;
provide data about a program that is not part of the program itself.
Annotations have no direct effect on the operation of the code they annotate
Annotation注解是Java中的一种元数据,它可以往程序中添加额外的数据,并且对注解的代码无直接的影响。注解是一种接口,可以作用于包名、类、方法、属性、参数等。它可以通过反射机制来访问annotation信息,获得所加的注解信息。
Java.lang中的注解又称为标准Annotation,即常用到的Deprecated、Override和SuppressWarnings。
(1)@Deprecated 过时:表示该方法已过时,建议使用新的一些方法代替。
(2)@Override 复写:表示复写父类的方法,如果方法上有这条注解但没有重写父类方法,则会生成一条错误信息。这个注解保证了方法一定会被复写。比如在开发过程中,手动在子类中复写父类的方法,只正确写出了方法名称,未写出正确的方法参数,则相当于方法的重载,并不是复写。所以使用@Override注解,有效地表明,此方法是复写父类的方法,不会产生手动的错误问题。
(3)@SuppressWarnings 阻止警告,阻止弹出的警告,比如加上此注解可屏蔽上述过时的警告。
元注解是指注解Annotation的注解。以Override的源代码为例:
1 | @Target(ElementType.METHOD) |
注解类的定义方法以及注解类上所加的注解。元注解有四个,位于java.lang.annotation包中:Target,Retention,Documented、Inherited。
3.1 @Target 表示注解作用的目标。
1 | @Documented |
注解作用的目标由枚举类ElementType决定。
1 | public enum ElementType{ |
注:Class,interface等都实现了java中的Type接口,因此ElementType.TYPE表示注解作用于这些“类”(并不是单纯的Class类)
3.2 @Retention 表示注解的作用时段
1 | @Documented |
由源码可见,Retention定义的是RetentionPolicy类型的数据1
2
3
4
5public enum RetentionPolicy{
SOURCE,//表示注解只在源文件中保留
CLASS,//表示注解保留到.class文件中
RUNTIME,//表示注解一直保留到内存中,类加载器把.class文件加载到内存中产生的字节码中要保留注解信息
}
3.3 @Documented 表示javadoc所生成的文档会带上注解信息。
3.4 @Inherited 表示子类可以集成加载父类上的注解,但要注意:(1)注解定义在类上面,子类可以继承该注解;(2)注解定义在方法上,子类也可继承该注解;但是如果子类复写了父类中定义了注解的方法,子类将无法继承该方法上的注解;(3)Interface的实现类无法继承接口中所定义的被@Inherited标注的注解
参数类型支持基本数据类型、Class类型、enum类型、Annotation类型以及上述所有类型的数组。
步骤:
1 | @Documented |
前言:最近做了个新项目,第一次用到了Fragment的懒加载,顺便学习了下ViewPager的预加载。
首先说明一下什么是ViewPager的预加载:默认情况下ViewPager会把当前Fragment的左右相邻页面预先初始化(俗称预加载),比如你当前在第二个Fragment的页面,其实左右第一和第三的Fragment已经初始化好了,这样做的好处是ViewPager左右滑动更流畅。我们可以写个简单例子(部分代码):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
68public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NoScrollViewPager viewPager = (NoScrollViewPager) findViewById(R.id.viewPager);
viewPager.setAdapter(new MainAdapter(getSupportFragmentManager()));
AlphaIndicator alphaIndicator = (AlphaIndicator) findViewById(R.id.alphaIndicator);
alphaIndicator.setViewPager(viewPager);
}
private class MainAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments = new ArrayList<>();
public MainAdapter(FragmentManager fm) {
super(fm);
fragments.add(new Fragment01());
fragments.add(new Fragment02());
fragments.add(new Fragment03());
fragments.add(new Fragment04());
}
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
}
}
//Fragment01.java:
public class Fragment01 extends Fragment {
private static final String TAG = "Fragment";
@Override
public void onAttach(Context context) {
super.onAttach(context);
Log.d(TAG, "Fragment01 ==> onAttach()");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "Fragment01 ==> onCreateView()");
//引用创建好的xml布局
View view = inflater.inflate(R.layout.item01,container,false);
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d(TAG, "Fragment01 ==> onDestroyView()");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "Fragment01 ==> onDestroy()");
}
}
然后我们运行并打印日志:1
2
3
4
5
6
7
8
9
10
1107-31 08:51:29.809 1987-1987/? D/Fragment: Fragment01 ==> onAttach()
07-31 08:51:29.809 1987-1987/? D/Fragment: Fragment02 ==> onAttach()
07-31 08:51:29.809 1987-1987/? D/Fragment: Fragment01 ==> onCreateView()
07-31 08:51:29.856 1987-1987/? D/Fragment: Fragment02 ==> onCreateView()
07-31 08:51:42.110 1987-1987/? D/Fragment: Fragment03 ==> onAttach()
07-31 08:51:42.110 1987-1987/? D/Fragment: Fragment03 ==> onCreateView()
[ 07-31 08:51:42.319 81: 81 D/ ]
Socket deconnection
07-31 08:55:21.653 1987-1987/? D/Fragment: Fragment04 ==> onAttach()
07-31 08:55:21.653 1987-1987/? D/Fragment: Fragment04 ==> onCreateView
从日志中我们可以看出,当位于Fragment02时,Fragment01和Fragment03是已经初始化完成了的,viewpager预加载是3页。这种特性会导致资源浪费:
懒加载指的是当有需要的时候才去加载。我们可以重写Fragment的setUserVisibleHint()。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
public abstract class BaseLazyFragment extends Fragment {
private static String TAG = null;
//第一次可见状态,第一次不可见状态
private boolean isFirstVisible = true;
private boolean isFirstInVisible = true;
private boolean isPrepared;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TAG = this.getClass().getSimpleName();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
if (getContentViewLayoutID() != 0){
return inflater.inflate(getContentViewLayoutID(), null);
}else{
return super.onCreateView(inflater, container, savedInstanceState);
}
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initView(view);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initPrepared();
}
private synchronized void initPrepared(){
if(isPrepared){
onFirstUserVisible();
}else {
isPrepared = true;
}
}
//可见状态,不可见状态
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser){
if(isFirstVisible){
isFirstVisible = false;
initPrepared();
}else {
onUserVisible();
}
}else {
if (isFirstInVisible){
isFirstInVisible = false;
onFirstUserInVisible();
}else {
onUserInVisible();
}
}
}
@Override
public void onDestroy() {
destroyViewAndThing();
super.onDestroy();
}
protected abstract int getContentViewLayoutID();
protected abstract void initView(View view);
//建议在此函数内初始化view或请求数据(only once的那种)
protected abstract void onFirstUserVisible();
protected abstract void onFirstUserInVisible();
//相当于onResume()
protected abstract void onUserVisible();
protected abstract void onUserInVisible();
protected abstract void destroyViewAndThing();
}
当然在这个基类中我们还可以封装一些通用的方法,比如页面跳转,吐司…
ps:不排除存在我们需要在界面不可见的时候去请求数据的需求,所以还是具体业务具体分析,然后再选择最合适的解决方案。
前言:本篇纯属个人学习笔记,故摘录《Android开发艺术探索》大量语句,特此声明
问题:主线程默认可以使用Handler的原因?
Android的主线程是ActivityThread(Android.app.ActivityThread类),我们可以看它的main方法: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 public static void main(String[] args) {
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
Security.addProvider(new AndroidKeyStoreProvider());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
在main方法中通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。当然ActivityThread还需要一个Handler来和消息队列进行交换,从源码我们看出sMainThreadHandler就是ActivityThread.H,它内部定义了一组消息类型,主要包含了四大组件的启动和停止等过程。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 public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
public static final int PAUSE_ACTIVITY_FINISHING= 102;
public static final int STOP_ACTIVITY_SHOW = 103;
public static final int STOP_ACTIVITY_HIDE = 104;
public static final int SHOW_WINDOW = 105;
public static final int HIDE_WINDOW = 106;
public static final int RESUME_ACTIVITY = 107;
public static final int SEND_RESULT = 108;
public static final int DESTROY_ACTIVITY = 109;
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int NEW_INTENT = 112;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int SERVICE_ARGS = 115;
public static final int STOP_SERVICE = 116;
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + msg.what);
switch (msg.what) {
...
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
...
}
public final void scheduleExit() {
queueOrSendMessage(H.EXIT_APPLICATION, null);
}
而在学习Looper.loop()时,我们提过,只有调用了Looper.quit(),loop()才会跳出循环,对于主程序的消息循环,我一直没有找到源码是在哪里调用了scheduleExit()(如果读者知道,恳请赐教…)
对于主线程的消息循环还涉及了ApplicationThread和AMS的相关知识,由于笔者学的尚浅,所以此处留空以待学完再来补充……
HandlerThread
我们知道在子线程之间通信可以使用Handler+Thread来完成(需操作Looper:调用Looper.prepare()和Looper.loop()),对于这个需求Google官方已经封装了一个类——HandlerThread
HandlerThread的源码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
...
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
...
}
可以看到HandlerThread继承了Thread,它是一种可以使用Handler的Thread,它在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop来开启消息循环。它和普通线程的区别就是多了一个Looper,这是子线程特有的Looper,用来做消息的取出和处理。由于HandlerThread的run方法是个死循环,因此当不需要使用HandlerThread时,可以通过它的quit()或quitSafely()来终止线程的执行。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
最后撸一个示例:
1 | private HandlerThread mHandlerThread; |
前言:本篇纯属个人学习笔记,故摘录《Android开发艺术探索》大量语句,特此声明
Handler是Android消息机制的上层接口,对于Handler的使用我们是熟悉的,我们往往认为Handler的作用仅仅是更新UI,但其实这只是Handler的一个特殊的使用场景。Handler的主要作用是将一个任务切换到某个指定的线程中去执行。
当然我们常用Handler来更新UI是有原因的:这是因为Android规定访问UI只能在主线程(UI线程)中进行,如果在子线程中访问UI,那么程序会抛出异常。ViewRootImpl(Android.View.ViewRootImpl).checkThread()方法对UI操作做了验证1
2
3
4
5
6void checkThread() {
if(mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
因此必须在主线程中访问UI,但又不能在主线程中进行耗时操作,否则会导致ANR(程序无响应)。这时候就是Handler起作用的时候了。
延伸:系统为什么不允许在子线程中访问UI?这是因为Android的UI控件不是线程安全的。
当多个线程访问一个类时,若不用考虑这些线程在运行时环境下的调整和交替执行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的。
所以在多线程中并发访问可能会导致UI控件处于不可预期的状态。对于多线程操作我们可以使用锁机制,但是系统不对UI控件的访问加上锁机制是有原因的:①会增加逻辑的复杂性;②会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。
首先我们看看Handler的默认构造方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
从上面我们可以看出如果当前线程没有Looper的话,就会抛出异常,这也就解释了在没有Looper的子线程中创建Handler会引发程序异常的原因了。紧接着又获取mLooper实例中保存的mQueue,这样Handler就与MessageQueue关联上了。
接下来我们啃一下我们最常用的方法——sengMessage(msg)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
31public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
辗转反侧到了enqueueMessage()方法中,首先为msg.target赋值为this,而在Looper的loop方法会取出每个msg,并交给msg.target.dispatchMessage(msg)去处理消息,也就是将当前的handler作为msg的target属性。最终调用queue的enqueueMessage的方法。简而言之,Handler发送消息的过程仅仅是向消息队列中插入一条消息。而消息由Looper交由Handler处理(dispatchMessage())1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
handler处理消息的流程如下:
首先检查Message的callback是否为null, 不为null就通过handleCallback(msg)来处理消息。handleCallback()实现如下:1
2
3private static void handleCallback(Message message) {
message.callback.run();
}
message的callback是一个Runnable对象,实际上就是Handler的post传递的Runnable参数。其次检查mCallback是否为null,不为null就调用mCallback的handleMessage()方法。
mCallback是一个接口:1
2
3
4
5
6
7
8
9
10/**
* Callback interface you can use when instantiating a Handler to avoid
* having to implement your own subclass of Handler.
*
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public interface Callback {
public boolean handleMessage(Message msg);
}
最后还是调到handleMessage()方法。这就给我们提供了另一种方式来创建Handler对象,就如源码中的注释。
handleMessage()是一个空方法,消息的最终回调是由我们控制的,我们在创建Handler的时候都是复写handlerMessage方法,然后根据msg.what去处理消息的。
当然,在使用Handler时,应注意内存泄漏问题,相关解决办法可自行google。
小结