尾声
如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
架构篇
《Jetpack全家桶打造全新Google标准架构模式》
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
}
可以简写成:
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
这种箭头通常称作胖箭头(fat arrow)。
区分 expression 和 statement 有时还是挺重要的,不包含 if
等控制流语句的程序元素都可以称之为表达式,包括操作符表达式,return
子句,throw
子句,print
等函数的调用。
可选参数
可选参数有两种,命名可选参数和有序可选参数,可选参数必须放在所有必须参数的后面。命名可选参数的列表使用 {param1, param2, …}
声明,使用 paramName: value
调用。有序可选参数的列表使用 [param1, param2, …]
声明:
void enableFlags(String styleName, {bool bold, bool hidden}) {
}
…
enableFlags(“NORMAL”, hidden: false);
String say(String from, String msg, [String device]) {
}
…
say(‘Bob’, ‘Howdy’);
say(‘Bob’, ‘Howdy’, ‘smoke signal’);
}
可选参数可以通过 =
指定默认参数值,这个默认参数值必须是编译时常量,如果不指定默认参数值,那么默认参数值为 null
,所以不要显示地设置默认值为 null
:
void enableFlags({bool bold = false, bool hidden = false}) {
}
…
enableFlags(bold: true);
main() 函数
每个 app 都必须有一个顶层 main()
函数作为 app 的入口, main()
函数的参数列表是可选的 List<String>
,返回值是 void
。
匿名函数
没有名字的函数有时会称为 匿名函数(anonymous function)、lambda 表达式(lambda) 或 闭包(closure),匿名函数的声明与普通函数类似:
([[Type] param1[, …]]) {
codeBlock;
};
如:
var functionList = [];
functionList.add((int element) {
print(“element:” + element.toString());
});
作用域
Dart 是一个 lexically scoped 语言,即 词法作用域/静态作用域 语言。也就是说,变量的作用域是根据它所处代码的布局位置静态决定的,你可以根据大括号去判断变量的作用域。
词法闭包
闭包(closure)就是一个函数对象可以访问它词法作用域内的变量,即使这个函数在原始范围外被使用。例如,下面的例子,makeAdder()
会捕获 addBy
. 无论他返回的函数在哪,他都会记住 addBy
:
/// 返回一个匿名函数,匿名函数的返回值是
/// 匿名函数的参数加上 [addBy].
Function makeAdder(num addBy) {
return (num i) => addBy + i;
}
void main() {
// 创建一个返回值是参数值加2的函数
var add2 = makeAdder(2);
// 创建一个返回值是参数值加4的函数
var add4 = makeAdder(4);
assert(add2(3) == 5);
assert(add4(3) == 7);
}
返回值
所有函数都有返回值,如果不指定返回值,默认会隐式添加 return null;
到函数体中。
操作符
5 / 2 的值是浮点数 2.5,而5 ~/ 2 的值才是整型的 2。
判断对象类型用 is
或 is!
操作符,使用 as
操作符进行类型转换:
if (emp is Person) {
emp.firstName = ‘Bob’;
}
// 可以简写成下面的语句,但是如果emp 不是 Person 类型的
// 那么下面的语句会抛出异常
(emp as Person).firstName = ‘Bob’;
使用 ??=
操作符可以给一个值为 null
的变量赋值。使用 ?.
可以避免左边表达式的值为空导致的异常,如 foo?.bar
在 foo
不为空时返回 bar
属性,在 foo
为空时返回 null
。
级联符号(…)
级联符号 ..
可以让你连续操作相同的对象,不单可以连续地调用函数,还可以连续地访问方法,这样做可以避免创建临时变量,从而写出更流畅的代码,流式编程更符合现代编程习惯和编程风格:
final addressBook = (new AddressBookBuilder()
…name = ‘jenny’
…email = ‘jenny@example.com’
…phone = (new PhoneNumberBuilder()
…number = ‘415-555-0100’
…label = ‘home’)
.build())
.build();
严格意义上说,级联符号不是操作符,而是 Dart 语法的一部分。
循环
如果循环(迭代)的对象是 Iterable
对象,可以利用它的 forEach()
方法对集合中的元素进行操作,forEach()
方法的参数是一个函数,它会按序应用集合中的每个元素给这个函数:
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
list.forEach(printElement);
像 List
和 Set
这样的 Iterable 类还支持 for-in
循环:
var collection = [0, 1, 2];
for (var x in collection) {
print(x); // 0 1 2
}
异常处理
抛出的异常可以是任意对象,但最好是 Error
或 Exception
对象:
throw new FormatException(‘Expected at least 1 section’);
…
throw ‘Out of llamas!’;
捕获异常可以使用 on
或 catch
或者同时使用:
try {
breedMoreLlamas();
} on OutOfLlamasException {
// 捕获 OutOfLlamasException 类型的异常,不需要使用异常对象
buyMoreLlamas();
} on Exception catch (e) {
// 捕获 Exception 异常,打印异常对象
print(‘Unknown exception: $e’);
} catch (e) {
// 捕获并处理任何类型的 thrown 对象
print(‘Something really unknown: $e’);
}
catch()
可以有两个参数,第一个是抛出的异常对象,第二个是堆栈跟踪对象。如果捕获异常并处理后还允许该异常继续传播,可以使用 rethrow
关键字:
final foo = ‘’;
void misbehave() {
try {
foo = “You can’t change a final variable’s value.”;
} catch (e) {
print(‘misbehave() partially handled ${e.runtimeType}.’);
rethrow; // Allow callers to see the exception.
}
}
void main() {
try {
misbehave();
} catch (e) {
print(‘main() finished handling ${e.runtimeType}.’);
}
}
finally
子句会在 catch
子句执行完执行。如果没有 catch
子句,那么会在finally
子句运行完才开始传播异常。
类与继承
Dart 的继承是基于 mixin 的继承,也就是说,虽然每个类(除了 Object
类)有且只有一个超类,但类体仍然可以在多个类层级上重用。
构造器的名字可以是 ClassName
(无名构造器),也可以是 ClassName.identifier
(命名构造器),实例化时的 new
关键字可以省略。
所有的实例变量都会自动生成隐式的 getter 方法,非 final 的实例变量也会自动生成隐式的 setter 方法,使用 get
和 set
关键词可以创建额外的属性和其 getter/setter 方法的实现。
Dart 有个语法糖可以方便地将构造器参数赋值给实例变量(在构造器体执行之前):
class Point {
num x, y;
Point(this.x, this.y);
}
如果不声明构造器,会隐式包含一个无名无参构造器,并调用父类的无名无参构造器。
子类不能继承父类的构造器,所以,如果如果子类想利用父类的命名构造器创建实例,就必须实现父类的命名构造器。如果父类没有无名无参构造器,那么你就必须手动调用父类的一个构造器:
class Person {
String firstName;
Person.fromJson(Map data) {
print(‘in Person’);
}
}
class Employee extends Person {
Employee.fromJson(Map data) : super.fromJson(data) {
print(‘in Employee’);
}
}
初始化的顺序为: 初始化列表 → 父类无参构造器 → 主类无参构造器
可以在构造器函数体执行前初始化实例变量,初始化列表使用逗号隔开,初始化列表特别适合初始化 final 字段:
class Point {
final num x;
final num y;
final num distanceFromOrigin;
-
Point(x, y)
-
x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
重定向到同类其他构造器:
class Point {
num x, y;
// The main constructor for this class.
Point(this.x, this.y);
// Delegates to the main constructor.
Point.alongXAxis(num x) : this(x, 0);
}
如果类的对象永远不会改变,就可以让这个对象成为编译时常量,只需要定义 const
构造器并确保所有的实例变量是 final
的:
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final num x, y;
const ImmutablePoint(this.x, this.y);
}
可以使用 factory
关键词声明工厂构造器,工厂构造器不一定要创建一个新的实例,可以直接返回缓存的实例,也可以返回子类型的实例,如:
class Logger {
final String name;
bool mute = false;
// _cache is library-private, thanks to
// the _ in front of its name.
static final Map<String, Logger> _cache =
<String, Logger>{};
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = new Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
可以使用 abstract
修饰符声明一个不能直接实例化的抽象类,抽象类可以包含抽象方法,抽象方法没有方法体,以分号结尾。
每个类都隐式地定义了一个接口,该接口包含该类的所有实例成员及其实现的所有接口。可以通过 implements
子句实现一或多个接口,以逗号隔开。
使用 extends
创建子类,使用 super
引用父类,使用 @override
注解有意地重载成员。
在面向对象的语言中,多继承是个非常糟糕的设计,因为多继承会产生一系列问题,如:
多继承会使类之间的关系更加复杂。如果一个类有多个超类,超类又可能有多个超类,那么类之间的关系将会变得尤为复杂,你很难理清一个类的超类到底有哪些,继承关系又是怎样的。
多继承可能会出现菱形继承的情况,这种情况下会出现二义性,虽然 C++ 使用虚继承的方式可以暂时避免这种二义性,但会使程序可读性和可调试性变差。
所以现代的面向对象语言都不会允许多继承这种情况存在,其实继承的目的无非是为了重用并拓展类的功能,而通过继承来重用类体并不是一个好的方式,首先继承会“白箱复用”,会将超类细节暴露给子类,不管子类愿不愿意。如果超类更改,其所有子类也不得不改变。而且继承在运行时的行为不易改变。
所以尽可能少用继承,多用组合。继承是 is-a 的关系,而组合是 has-a 的关系。
Mixin 的概念源于 LISP 社区,在那里是指被设计用来与各个超类一起工作的类。对 Mixin 介绍比较详细的是 Mixins in Strongtalk 这篇论文。
一个类会隐式地定义一个 mixin, mixin 被类体定义,与类和它的超类构成函数关系,类实际上就是 mixin 的应用,是将其隐式定义的 mixin 应用于其超类的结果。Mixin应用 与 函数应用 类似,从数学上来说,一个 mixin 可以看成是超类到子类的函数,也就是说,将 作用于超类 ,结果是新的 子类,在研究资料里通常写作 。因此对于 mixin 的组合来说: 。 一个类隐式定义的 mixin 通常只会在给定超类定义处应用一次,所以为了让 mixin 能够应用不同的超类,我们需要独立于任何特定超类来声明 mixin,或者将类隐式定义的 mixin 剥离出来并在原始声明之外重用它。
Mixin 可以通过普通类定义的方式隐式定义,但要想提取 mixin 就必须要保证类不能声明构造器,这也避免了由于在继承链上传递构造器参数引起的各种并发症。
abstract class Collection {
Collection newInstance();
Collection map((f) {
var result = newInstance();
forEach((E e) { result.add(f(e)); });
return result;
});
void forEach(void f(E element)) {
// …
}
void add(E element) {
// …
}
}
abstract class DOMElementList = DOMList with Collection;
abstract class DOMElementSet = DOMSet with Collection;
在这里,Collection
就是一个隐式声明了 mixin 的普通类,它没有构造器。而 DOMElementList
和 DOMElementSet
类就是 mixin 的应用,它们被特殊的类声明格式定义,也就是通过 with
子句声明 mixin 到 父类的应用。这两个类也是抽象的,因为他们没有实现 Collection
类的抽象方法 newInstance()
。
也就是说,DOMElementList
就是 Collection mixin ▷ DOMList,而 DOMElementSet
就是 Collection mixin ▷ DOMSet。
这样的好处是,Collection
类的代码可以在多个类层次上共享。我们这里列举了两个类层次,一个是以 DOMList
为根的,一个是以 DOMSet
为根的。我们不需要复制粘贴Collection
类的代码,而 Collection
的任何更改都会传播到这两个类层次中,这样也极大地解放了代码的维护。
上面的例子只是简单说明了一种形式的 mixin 应用,应用的结果是一个有名字的类。而另一种形式,with
子句后跟上以逗号分隔的标识符列表,所有的标识符必须表示一个类,这种形式下,多个 mixin 会被组合并应用到 extends
子句的超类中,从而产生一个匿名超类,即:
class DOMElementList extends DOMList with Collection {
DOMElementList newInstance() => new DOMElementList();
}
class DOMElementSet extends DOMSet with Collection {
DOMElementSet newInstance() => new DOMElementSet();
}
这里的 DOMElementList
不再是 Collection mixin ▷ DOMList 的应用,而是一个新类,它的超类是这个应用。
如果 DOMList
有非平凡构造器,那么构造将会是这样:
class DOMElementList extends DOMList with Collection {
DOMElementList newInstance() => new DOMElementList(0);
DOMElementList(size): super(size);
}
每个 mixin 都有属于自己的被独立调用的构造器,超类也是如此。由于 mixin 的构造器不能被声明,对它的调用可以用 ;
语法省略,在底层实现时,这个调用会被放在初始化列表的开始处。
为了让多个 mixin 应用到一个类是而又不引入多个中间声明,可以使用这种方便的语法糖:
class Person {
String name;
Person(this.name);
}
class Maestro extends Person with Musical, Aggressive, Demented {
Maestro(name):super(name);
}
在这里,Maestro
的超类就是这样的 mixin 应用:
Demented mixin ▷ Aggressive mixin ▷ Musical mixin ▷ Person
mixin 与普通继承一样,隐私性取决于其声明的位置。mixin 的应用的静态元素同样不能被继承使用,Dart 中静态元素是不能被继承的。
那 mixin 应用后的实例类型到底是什么类型呢?毫无疑问,它必须是命名 mixin 类的子类型,也必须是原来类的子类型。原来的类有它自己的超类,为了确保指定的 mixin 应用 兼容原来的类,Dart 会给 with
子句的类额外的要求。
如果类 A 定义时的 with
子句是 mixin M,而 M 又是继承的 类 K,那么类 A 就必须支持类 K 的接口。
class S {
twice(int x) => 2 * x;
}
abstract class I {
twice(x);
}
abstract class J {
thrice(x);
}
class K extends S implements I, J {
int thrice(x) => 3* x;
}
class B {
twice(x) => x + x;
}
class A = B with K;
A 必须支持 K 的超类 S 的隐式接口,以确保 A 确实是 M 的子类型。K 必须实现 twice()
以满足 I 的需要,同时必须实现 thrice()
以满足 J 的需要,显然 K 已经满足了这些需要。
现在我们声明了 A,我们从 K 的 mixin 中 拿到了 thrice()
的实现,但这个 mixin 没有提供 twice()
的实现,幸运的是,B 有这个实现,所以总的来说,A 确实满足了 I,J 和 S 的需要。
相反,对于这样的类 D:
class D {
double(x) => x+x;
}
class E = D with K;
会收到一个警告,因为 E 没有 twice()
方法,因此不满足 I 和 S,也就不能使用预期的 K 了。
如果一个类有泛型参数,那么它的 mixin 也必须有相同类型的参数。
在 1.13 版本之后,Mixin 可以继承普通类而不要求一定是 Object,Mixin 可以使用 super.method()
。
学习分享
①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
`。
学习分享
①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包
[外链图片转存中…(img-43dBJO3p-1714832276109)]
[外链图片转存中…(img-aV0oeI1f-1714832276109)]
[外链图片转存中…(img-003DBxDN-1714832276109)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!