[Android] How to learn Android

learn Android from scratch,and get started quickly

Posted by xiuyuantech on 2020-06-20

如何学Android呢,这个话题也有不少人回答过了。
每个人有每个人的学习方法,学习的效率也不一样。
Android 发展这么多年了,变得越来越好了,同时需要大家
学习的东西就比较多。
从目前来看就有多种语言可以开发Android,例如 官方推荐的 JAVA、Kotlin、Flutter
本文主要从 JAVA、Kotlin、Flutter 三个方面来介绍如何学习
学好一门语言再学其他的语言就会快些,不同语言的原理都差不多。所以本文主要讲JAVA相关的, Kotlin 和 Flutter 只作简单介绍,后续补充

如下是Android官方系统架构图,从架构图中我们可以了解大概流程以便今后学习增加理解
Android架构图

如果你是刚刚入门,建议先打好基础,千万不要一上看源码,一来看得费劲,二来你可能在代码间来回跳转,可能会迷失在某一个环节,更可能是理解错误,记住一定要循序渐进。

基础的东西,本文不作详细介绍,不常用的或者特殊的例外

JAVA

基础

JDK 和 JRE 区别

JDK 是 Java Development Kit,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。

JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。

数据类型

基本类型 位数 字节 默认值
int 32 4 0
short 16 2 0
long 64 8 0L
byte 8 1 0
char 16 2 ‘u0000’
float 32 4 0f
double 64 8 0d
boolean 1 false

对于boolean,官方文档未明确定义,它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1位
一个字节等于8位 1byte = 8bit

自动装箱与拆箱
装箱:将基本类型用它们对应的引用类型包装起来;
拆箱:将包装类型转换为基本数据类型;
例如:
int i = 128;Integer j = 128;但是 i!=j

== 和 equals 区别

== : 它的作用是判断两个对象的地址是不是相等。即判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)

因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

equals() : 它的作用也是判断两个对象是否相等,它不能用于比较基本数据类型的变量。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类。

重写 equals 时必须重写 hashCode 方法

访问修饰符

default:默认只能在本包内访问

Float 和 Double 精度

1
2
3
4
5

double d1 = 1.4;
double d2 = 0.5;

double ch = d1-d2; //0.8999999999999999

作运算的时候会丢失精度,建议使用BigDecimal进行运算,尽量使用参数类型为String的构造函数。

BigDecimal 注意事项 同上

1
2
3
4
5

BigDecimal b1 = new BigDecimal(1.21);
BigDecimal b2 = new BigDecimal("1.21");

boolean b1.toString() == b2.toString() //false

注意:商业计算使用BigDecimal,为了防止精度丢失,推荐使用它的 BigDecimal(String) 构造方法来创建对象。

数组

Arrays.asList() 创建的列表不可以进行add、remove,不然会报异常。

Collection.toArray() 使用此方法推荐使用带参数的方法 toArray(T[] a)。

remove 操作 推荐使用迭代器方式,使用遍历会将抛出一个ConcurrentModificationException。

面向对象

构造器

Constructor 不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。

三大特征

  1. 封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性

  2. 继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。
    特点:
    1)子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
    2)子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
    3)子类可以用自己的方式实现父类的方法

  3. 多态,顾名思义,表示一个对象具有多种的状态。具体表现为父类的引用指向子类的实例。
    特点:
    1)对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
    2)对象类型不可变,引用类型可变;
    3)方法具有多态性,属性不具有多态性;
    4)引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
    5)多态不能调用“只在子类存在但在父类不存在”的方法;
    6)如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。

重载 和 重写

1、重载是一个编译期概念、重写是一个运行期间概念。

2、重载遵循所谓“编译期绑定”,即在编译时根据参数变量的类型判断应该调用哪个方法。

3、重写遵循所谓“运行期绑定”,即在运行的时候,根据引用变量所指向的实际对象的类型来调用方法

4、因为在编译期已经确定调用哪个方法,所以重载并不是多态。而重写是多态。重载只是一种语言特性,是一种语法规则,与多态无关,与面向对象也无关。(注:严格来说,重载是编译时多态,即静态多态。但是,Java中提到的多态,在不特别说明的情况下都指动态多态)

基本原则

单一职责原则(Single-Responsibility Principle)

开放封闭原则(Open-Closed principle)

Liskov替换原则(Liskov-Substitution Principle)

依赖倒置原则(Dependecy-Inversion Principle)

接口隔离原则(Interface-Segregation Principle)

高级

多线程 关键字

volatile,synchronized,ThreadLocal,ThreadPoolExecutor,Semaphore(信号量-允许多个线程同时访问),CountDownLatch(倒计时器-允许多个线程阻塞在一个地方,只能使用一次),CyclicBarrier(循环栅栏-与CountDownLatch类似,但是它可以重复使用),Lock …

并发 和 并行 区别

并发当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。
并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

区别:并发和并行是即相似又有区别的两个概念,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。

volatile 和 synchronized 区别

一、volatile是变量修饰符,而synchronized则作用于一段代码或方法。

二、volatile只是在线程内存和“主”内存间同步某个变量的值;而synchronized通过锁定和解锁某个监视器同步所有变量的值。显然synchronized要比volatile消耗更多资源。

三、volatile 只有可见性,没有原子性,而 synchronized 都有。

1
2
3
4
volatile int i = 0;
i = 2; //可见性

i++;//原子性,不安全

死锁的四个必要条件

(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

并发容器

  • ConcurrentHashMap: 线程安全的 HashMap
  • CopyOnWriteArrayList: 线程安全的 List,在读多写少的场合性能非常好,远远好于 Vector.
  • ConcurrentLinkedQueue: 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。
  • BlockingQueue: 这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。
  • ConcurrentSkipListMap: 跳表的实现。这是一个 Map,使用跳表的数据结构进行快速查找。

代理

  1. 静态代理

接口:IUser.java

1
2
3
 public interface IUser{
void save();
}

目标对象:User.java

1
2
3
4
5
public class User implements IUser {
public void save() {
System.out.println("----已经保存数据!----");
}
}

代理对象:UserProxy.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserProxy implements IUser{
//接收保存目标对象
private IUser target;
public UserProxy(IUser target){
this.target=target;
}

public void save() {
System.out.println("开始事务...");
target.save();//执行目标对象的方法
System.out.println("提交事务...");
}
}

测试类:App.java

1
2
3
4
5
6
7
8
9
10
11
public class App {
public static void main(String[] args) {
//目标对象
User target = new User();

//代理对象,把目标对象传给代理对象,建立代理关系
UserProxy proxy = new UserProxy(target);

proxy.save();//执行的是代理的方法
}
}

+可以做到在不修改目标对象的功能前提下,对目标功能扩展.
+缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.

  1. 动态代理
    接口类IUser.java以及接口实现类,目标对象User是一样的,没有做修改.在这个基础上,增加一个代理工厂类(ProxyFactory.java),将代理类写在这个地方,然后在测试类(需要使用到代理的代码)中先建立目标对象和代理对象的联系,然后代用代理对象的中同名方法

代理工厂类:ProxyFactory.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
public class ProxyFactory{

//维护一个目标对象
private Object target;
public ProxyFactory(Object target){
this.target=target;
}

//给目标对象生成代理对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务2");
//执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务2");
return returnValue;
}
}
);
}

}

测试类:App.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class App {
public static void main(String[] args) {
// 目标对象
IUser target = new User();
// 【原始的类型 class User】
System.out.println(target.getClass());

// 给目标对象,创建代理对象
IUser proxy = (IUser) new ProxyFactory(target).getProxyInstance();
// class $Proxy0 内存中动态生成的代理对象
System.out.println(proxy.getClass());

// 执行方法 【代理对象】
proxy.save();
}
}

1).代理对象,不需要实现接口
2).代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3).动态代理也叫做:JDK代理,接口代理

代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理

  1. Cglib代理
    静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理

Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

目标对象类:User.java

1
2
3
4
5
6
public class User {

public void save() {
System.out.println("----已经保存数据!----");
}
}

Cglib代理工厂:ProxyFactory.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
public class ProxyFactory implements MethodInterceptor{
//维护目标对象
private Object target;

public ProxyFactory(Object target) {
this.target = target;
}

//给目标对象创建一个代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();

}

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始事务...");

//执行目标对象的方法
Object returnValue = method.invoke(target, args);

System.out.println("提交事务...");

return returnValue;
}
}

测试类:App.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class App {

@Test
public void test(){
//目标对象
User target = new User();

//代理对象
User proxy = (User)new ProxyFactory(target).getProxyInstance();

//执行代理对象的方法
proxy.save();
}
}

代理的类不能为final,否则报错 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

设计模式

  • 工厂模式
  • 适配器模式
  • 策略模式
  • 模板方法模式
  • 观察者模式
  • 外观模式
  • 代理模式
  • 单例模式
  • 责任链模式

总结

JAVA需要学习的东西还有很多,大家要循序渐进,慢慢来。上面只是JAVA中的一部分,而这部分是大家易出问题,接触比较少的地方。其他部分请自行学习,下面为大家推荐一些资源和书。
《Java 从入门到精通》、《疯狂JAVA讲义》、《深入理解 Java 虚拟机》、《Effective java 》、《Java 编程思想》
github推荐:27天成为Java大神Java工程师成神之路Java学习+面试指南进阶知识完全扫盲
JavaFamily面试+学习指南Java资源大全中文版

JAVA开发工程师发展线路:初级JAVA工程师->中级JAVA工程师->中高级JAVA工程师->高级JAVA工程师->资深JAVA工程师->JAVA架构师->JAVA专家

Kotlin

简介:Kotlin(中文官网)是一种在 Java 虚拟机上运行的静态类型编程语言,被称之为 Android 世界的Swift,由 JetBrains 设计开发并开源。
Kotlin 可以编译成Java字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。
在Google I/O 2017中,Google 宣布 Kotlin 成为 Android 官方开发语言。

基础

@JvmField 和 @JvmStatic

@JvmField消除了变量的getter与setter方法
@JvmField修饰的变量不能是private属性的
@JvmStatic只能在object类或者伴生对象companion object中使用,而@JvmField没有这些限制
@JvmStatic一般用于修饰方法,使方法变成真正的静态方法;如果修饰变量不会消除变量的getter与setter方法,但会使getter与setter方法和变量都变成静态

懒加载

1
2
3
companion object {
val a by lazy { A() }
}

本质上是调用了 public actual fun lazy(initializer: () -> T): Lazy = SynchronizedLazyImpl(initializer)
by lazy是线程安全的

run、let、also、apply、with的用法和区别

  • run 函数
    第一种:直接使用run函数返回其结果(最后一行的结果)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun runTest() {
val a = run {
"abc"
1
}
val b = run {
1
2
"abc"
}
println(a)
println(b)
}
结果:
1
abc

第二种:调用某个对象(该对象作为接收者)的run函数并返回结果

1
2
3
4
5
6
7
8
fun runTestWithT() {
val a = 1.run {
"$this 和 abc"
}
println(a)
}
结果:
1 和 abc
  • let 函数
    调用某个对象(该对象作为函数的参数)的let的函数并返回结果
1
2
3
4
5
6
7
8
9
10
11
fun letTest() {
val let = "abc".let {
println(it)
1
}
println(let)
}
结果:
abc

1
  • also 函数
    调用某一对象的also 函数(该对象作为函数参数)并返回改对象
1
2
3
4
5
6
7
8
9
fun alsoTest() {
val also = "abc".also {
println(it)
}
println(also)
}
结果:
abc
abc
  • apply 函数
    调用对象(该对象作为接收者)的apply函数并返回该对象
1
2
3
4
5
6
7
8
9
fun applyTest(){
val apply ="abc".apply {
println(this)
}
println(apply)
}
结果:
abc
abc
  • with 函数
    调用对象(该对象作为接收者)的apply函数并返回该对象
1
2
3
4
5
6
7
8
9
10
fun withTest() {
val with = with("abc") {
println(this)
1111
}
println(with)
}
结果:
abc
1111

从返回结果不同来看
返回其他结果 :run ,let,with

返回自身结果 :also ,apply

从对象调用的作用来看
调用者作为参数 :let,also

调用者作为接收者:run,with,apply

协程

协程的三种启动方式
1.runBlocking:T
runBlocking启动的协程任务会阻断当前线程,直到该协程执行结束。
2.launch:Job
最常用的用于启动协程的方式,它最终返回一个Job类型的对象,这个Job类型的对象实际上是一个接口,它包涵了许多我们常用的方法。例如join()启动一个协程、cancel() 取消一个协程
该方式启动的协程任务是不会阻塞线程的
3.async/await:Deferred
async和await是两个函数,这两个函数在我们使用过程中一般都是成对出现的。
async用于启动一个异步的协程任务,await用于去得到协程任务结束时返回的结果,结果是通过一个Deferred对象返回的。
协程是可以被取消的和超时控制,可以组合被挂起的函数,协程中运行环境的指定,也就是线程的切换,协程可以说是线程封装优化的版本。

Flutter

Flutter 是 Google 开源的 UI 工具包,帮助开发者通过一套代码库高效构建多平台精美应用,支持移动、Web、桌面和嵌入式平台。

基础

Flutter体系结构

Flutter框架:框架层(Framework)和引擎层(Engine)。
框架层
框架层是由Dart所编写的,包含:Material(Material Design风格的组件)、Cupertino(针对iOS风格的组件)、Widgets(组件)、Rendering(渲染)、Animation(动画)、Painting(绘制)、Gestures(手势)、Foundation(基础库)。通常开发者使用Framework层所提供的API去开发自己的项目。
引擎层(Engine)是由C++编写而成,里面包含Skia、Dart、Text三个部分。
kia:图形渲染引擎库,也是最核心的部分。
Dart:指的是Dart VM虚拟机,用于编译和运行Dart代码。
Text:文本渲染,不同平台采用的渲染方式不一样。

Flutter开发者帮助

Flutter 中文网
Flutter-Go
awesome-flutter
Flutter实战
Flutter实战源码
Flutter完整开源项目

总结

如果想成为高级App开发工程师,那么阅读Android系统源码算是选修课,阅读一些优秀的开源框架库算是必修课。

Android之路很长,建议先打好基础,千万不要一上来就看源码,一看得费劲,二你可能在代码间来回跳转,可能会迷失,理解错误,记住一定要循序渐进。

遇到问题,一定要先尝试自己解决,实在解决不了再请教他人。可以有多种途径自行尝试解决:

百度一下,不要过分依赖google,完全抛弃百度,毕竟中文资料对大多数人来说理解起来更快;
Google搜索,建议先用中文关键词google一下;stackoverflow.com、知乎、掘金、简书等技术问答网站内直接搜索;

如果有源码,尝试直接看源码;
多逛逛github,多看看Google官方文档;

推荐资源:
《Android 从入门到精通》、《疯狂Android讲义》、《第一行代码》、《深入理解Android》、
《Android群英传》、《Android开发艺术探索》、《Android编程权威指南》、《Android进阶解密》…

AndroidDevTools.cnAndroid培训文档android-arsenal.com
codota.com Android主页androidweekly.cn

当然还有很多优秀的博客和网站值得推荐,这里就不一一介绍。

Android开发工程师发展线路:初级Android开发工程师->中级Android开发工程师->中高级Android开发工程师->高级Android开发工程师->资深Android开发工程师->Android架构师->Android专家