2025年4月12日 星期六 乙巳(蛇)年 正月十三 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > 安卓(android)开发

从零开始学习 Android 笔记

时间:12-14来源:作者:点击数:4

工作需要,开始写原生安卓项目;本文记录一个前端学习安卓相关知识的历程

一、前置 Java 知识

https://www.cdsy.xyz/computer/programme/java/20210305/cd16149327239109.html 可用于快速补充一些 Java 的基础知识,作为一个基本的工具手册。Java 是一门强类型的面向对象的解释型语言,通过JVM可以在多平台运行。

1. 基础类型

8 种内置类型,六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型:

  • byte:8位,有符号整数,-128(-2^7)-- 127(2^7-1);
  • short:16位,有符号整数,-32768(-2^15)-- 32767(2^15 - 1);
  • int:32位,有符号整数,-2,147,483,648(-2^31)-- 2,147,483,647(2^31 - 1);
  • long:64位,有符号整数,-9,223,372,036,854,775,808(-2^63)-- 9,223,372,036,854,775,807(2^63 -1);
  • float:32位、单精度、符合IEEE 754标准的浮点数,不能表示精确的值,如货币;
  • double:64位、双精度、符合IEEE 754标准的浮点数,不能表示精确的值,如货币;
  • boolean:1位,只有两个取值:true和false;
  • char:16位,表示Unicode字符,最小值是’\u0000’(即为0),最大值是’\uffff’(即为65,535)。

引用类型

对象、数组都是引用数据类型,所有引用类型的默认值都是null。

对前端而言,需要额外注意两点:

  • 变量一旦声明,则类型确定,且不能更改(不能赋值其它类型)。
  • char 和 String 的区别:char是基本类型,对应一个字符;String 是引用类型,对应0或多个字符。
    char a = 'a'; String x = "hi!";

2. 基本语法

  • 大小写敏感:Java是大小写敏感的。
  • 类名:对于所有的类来说,类名的首字母应该大写。如果类名由若干单词组成,那么每个单词的首字母应该大写,例如 MyFirstJavaClass
  • 方法名:所有的方法名都应该以小写字母开头。如果方法名含有若干单词,则后面的每个单词首字母大写。
  • 源文件名:源文件名必须和类名相同。文件名的后缀为.java,如果文件名和类名不相同则会导致编译错误。
  • 主方法入口:Java 程序由**public static void main(String args[])**方法开始执行。

总的来说,Java 基本语法和一般程序语言的语法一致。相比JS,我们可能要注意 修饰符接口等概念。

循环

和 JS 基本一致;不过可以注意下增强的 for 循环:

  • String [] names ={"James", "Larry", "Tom", "Lacy"};
  • for( String name : names ) {
  • System.out.print( name );
  • System.out.print(",");
  • }

for(声明语句 : 表达式) 中表达式为数组。

分支

与 JS 一致。

3. 类和对象

对象是类的一个实例,有状态和行为。类可以看成是创建Java对象的模板。一个类可以包含以下类型变量:

  • 局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
  • 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
  • 类变量:类变量也声明在类中,方法体之外,但必须声明为static类型。

每个类都有构造方法。如果没有显式为类定义构造方法,Java编译器将会为该类提供一个默认构造方法。

源文件声明规则
  • 一个源文件中只能有一个public类。
  • 一个源文件可以有多个非public类。
  • 源文件的名称应该和public类的类名保持一致。
  • 如果一个类定义在某个包中,那么package语句应该在源文件的首行。
  • 如果源文件包含import语句,那么应该放在package语句和类定义之间。如果没有package语句,那么import语句应该在源文件中最前面。
  • import语句和package语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。
Java包

包主要用来对类和接口进行分类。我们用package pkgName 来声明包,用import java.io.* 来引入包。

4. 修饰符(访问控制及其它)

Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java支持4种不同的访问权限。

  • 默认的,也称为default,在同一包内可见,不使用任何修饰符。
    接口里的变量都隐式声明为public static final,而接口里的方法默认情况下访问权限为public。
  • 私有的,以private修饰符指定,在同一类内可见。
    私有访问修饰符是最严格的访问级别,所以被声明为private的方法、变量和构造方法只能被所属类访问,并且类和接口不能声明为private。
  • 公有的,以public修饰符指定,对所有类可见。
    被声明为public的类、方法、构造方法和接口能够被任何其他类访问。
  • 受保护的,以protected修饰符指定,对同一包内的类和所有子类可见。
    被声明为protected的变量、方法和构造器能被同一个包中的任何其他类访问,也能够被不同包中的子类访问。protected访问修饰符不能修饰类和接口,方法和成员变量能够声明为protected,但是接口的成员变量和成员方法不能声明为protected。子类能访问protected修饰符声明的方法和变量。
访问控制和继承
  • 父类中声明为public的方法在子类中也必须为public。
  • 父类中声明为protected的方法在子类中要么声明为protected,要么声明为public。不能声明为private。
  • 父类中默认修饰符声明的方法,能够在子类中声明为private。
  • 父类中声明为private的方法,不能够被继承。
非访问修饰符

为了实现一些其他的功能,Java也提供了许多非访问修饰符。

  • static修饰符,用来创建类方法和类变量。
  • final修饰符,用来修饰类、方法和变量,final修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
  • abstract修饰符,用来创建抽象类和抽象方法。
    • 抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。
    • 抽象方法是一种没有任何实现的方法,该方法的的具体实现由子类提供。抽象方法不能被声明成final和static。任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。
  • synchronized和volatile修饰符,主要用于线程的编程。

5. Java 的继承与接口

Java是完全面向对象的语言,继承是最重要的topic。Java只有单继承(相比多继承,减少复杂度和潜在的一些问题(比如函数重写)),但通过接口来保留多继承的一些优点。

典型的继承语法:

  • class Pet {
  • public String name;
  • private int age = 0;
  • public Pet() {}
  • public String getName() {
  • System.out.println("yes, in Pet!");
  • return name;
  • }
  • public int getAge() {
  • return age;
  • }
  • }
  • interface Animal {
  • public void eat(String food);
  • }
  • class Dog extends Pet implements Animal {
  • int age = 10;
  • @Override
  • public int getAge() {
  • System.out.println("hey!" + age);
  • return age;
  • }
  • public void eat(String food) {
  • System.out.println("dog eat " + food);
  • }
  • }
  • public class Test {
  • public static void main(String[] args) {
  • Pet a = new Pet();
  • Pet b = new Pet();
  • Dog d = new Dog();
  • System.out.println(a.getName());
  • System.out.println(d.getName());
  • System.out.println("--------------");
  • d.eat("gouliang");
  • }
  • }

子类可以从父类继承所有的 protected/public 属性和方法。

我们知道,JS 中,基于原型链的继承机制,所有实例的方法调用的方法最终都指向原型(链)的某个方法,即方法在内存中只有一份,那在Java中一样吗?

答案是:是。

Java中,类在加载时,类的信息(类信息,常量,静态变量,类方法)被存储到方法区。

类信息除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量、符号引用,文字字符串、final变量值、类名和方法名常量,这部分内容将在类加载后存放到方法区的运行时常量池中。它们以数组形式访问,是调用方法、与类联系及类的对象化的桥梁。

其中 方法信息,包括方法名、返回值类型、参数类型、修饰符、异常、方法的字节码

在Java中 new 一个对象时,为类的成员(包括(继承的)父类的成员)分配了内存空间,然后执行构造函数,初始化这些属性的值。

但对象并不会为方法分配内存,当调用对象的方法时,实质上是去方法区查找到对应的方法执行。

静态方法和私有方法在解析阶段确定唯一的调用版本,而其它实例方法,会去动态查找(沿继承链)。

参考:

6. Java 泛型

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

泛型类

泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。

泛型类的定义:

  • class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
  • private 泛型标识 /*(成员变量类型)*/ var;
  • .....
  • }
  • }

例子:

  • class Demo <T> {
  • private T key;
  • public Demo(T key) {
  • this.key = key;
  • }
  • public T getKey() {
  • return key;
  • }
  • }
  • // 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
  • // 传入的实参类型需与泛型的类型参数类型相同,即为Integer.
  • Demo<Integer> demoInt = new Demo<Integer>(10);

泛型的类型参数只能代表引用型类型,不能是原始类型(像int,double,char等)

泛型接口
  • //定义一个泛型接口
  • public interface Generator<T> {
  • public T next();
  • }
  • public class FruitGenerator implements Generator<String> {
  • private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
  • @Override
  • public String next() {
  • Random rand = new Random();
  • return fruits[rand.nextInt(3)];
  • }
  • }
泛型方法

泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的)。
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
  • 泛型方法方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char等)。
  • public class GenericMethodTest
  • {
  • // 泛型方法 printArray
  • public static < E > void printArray( E[] inputArray )
  • {
  • // 输出数组元素
  • for ( E element : inputArray ){
  • System.out.printf( "%s ", element );
  • }
  • System.out.println();
  • }
  • public static void main( String args[] )
  • {
  • // 创建不同类型数组: Integer, Double 和 Character
  • Integer[] intArray = { 1, 2, 3, 4, 5 };
  • Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
  • Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
  • System.out.println( "Array integerArray contains:" );
  • printArray( intArray ); // 传递一个整型数组
  • System.out.println( "\nArray doubleArray contains:" );
  • printArray( doubleArray ); // 传递一个双精度型数组
  • System.out.println( "\nArray characterArray contains:" );
  • printArray( charArray ); // 传递一个字符型型数组
  • }
  • }

有界的类型参数:

可能有时候,你会想限制那些被允许传递到一个类型参数的类型种类范围。例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例。这就是有界类型参数的目的。

要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界。

  • // 比较三个值并返回最大值
  • // T 必须继承自 Comparable 接口
  • public static <T extends Comparable<T>> T maximum(T x, T y, T z)
  • {
  • T max = x; // 假设x是初始最大值
  • if ( y.compareTo( max ) > 0 ){
  • max = y; //y 更大
  • }
  • if ( z.compareTo( max ) > 0 ){
  • max = z; // 现在 z 更大
  • }
  • return max; // 返回最大对象
  • }

7. Java 多线程编程

8. Java 内存模型

源于同事的一次分享,查阅资料了解了Java虚拟机和内存管理相关知识。利于深入了解Java,对比JS可能有更大收获。

Run-Time Data Areas

  1. 堆区:存放所有类实例(对象)和数组,虚拟机启动时创建。由GC自动管理。
  2. 方法区(Method area and runtime constant pool):存放类的结构信息,虚拟机启动时创建。类似于传统语言中存放编译后代码的地方,它存放类的 (1)run-time constant pool(类似传统语言的符号表+其它),(2)成员和方法信息,(3)静态变量,(4)方法的代码等等。

    It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors.

    虽然逻辑上来说,方法区也是堆的一部分,不过方法区一般不被GC管理(取决于JVM的具体实现)。

  3. JVM Stack:每个Java线程创建时都会创建一个私有的JVM Stack,存储局部变量和部分结果,参与函数调用和返回。Stack本身只负责存储(push/pop)frames,frame 对应方法,负责存数据和部分结果,执行动态链接,返回方法的值或者dispatch异常。
  4. Native Method Stacks:线程私有,但不是所有JVM都实现了。类似JVM Stack,用于执行 Native 方法服务。frame通常包含:
    • 局部变量表(Current Variable Table)
    • 操作数栈(Operand Stack):字节码指令从操作数栈弹出数据,执行计算,再把结果入操作数栈。
    • 动态链接(Dynamic Linking):指向运行时常量池中frame所属方法的引用。
    • 返回地址(Return Address):正常返回,PC register的值作为返回地址保存;异常返回,通过异常处理表获得返回地址。
  5. PC register:线程私有。每个JVM线程都有自己的 pc register,在任意时刻,每个线程都是在执行一个方法(记作 current method)。如果这个方法不是native的,那么pc register存着当前执行的JVM指令的地址;否则 pc register 的值是 undefined。

二、Android 编程

1. IDE 和创建第一个项目

下载 AndroidStudio,创建一个项目并编译,了解基本的流程。

2. Layout 与 Res 文件夹

3. Gradle

Gradle 是一个基于 JVM 的构建工具,基于 groovy,有强大的依赖管理,支持多工程构建。

Gradle 构建基础

projects 和 tasks是 Gradle 中最重要的两个概念。

  • 任何一个 Gradle 构建都是由一个或多个 projects 组成。每个project一般是一个jar包或者一个web应用等等。
  • 每个 project 都由多个 tasks 组成。每个 task 都代表了构建执行过程中的一个原子性操作。如编译,打包,生成 javadoc,发布到某个仓库等操作。

1、创建任务

1.1、创建一个最简单的task(build.gradle文件):

  • task hello {
  • doLast {
  • println 'Hello world!'
  • }
  • }

我们可以执行gradle -q hello查看输出:

  • $ gradle -q hello
  • Hello world!

1.2、快速定义task:

  • task hello << {
  • println 'Hello world!'
  • }
  • // << 等同于 doLast

1.3、使用groovy

  • task upper << {
  • String someString = 'mY_nAmE'
  • println "Original: " + someString
  • println "Upper case: " + someString.toUpperCase()
  • }
  • $ gradle -q upper
  • Original: mY_nAmE
  • Upper case: MY_NAME

2、任务依赖

2.1、 在两个任务之间指明依赖关系

  • task hello << {
  • println 'Hello world!'
  • }
  • task intro(dependsOn: hello) << {
  • println "I'm Gradle"
  • }
  • $ gradle -q intro
  • Hello world!
  • I'm Gradle

2.2、延迟依赖

taskX 是可以在 taskY 之前定义的。

  • task taskX(dependsOn: 'taskY') << {
  • println 'taskX'
  • }
  • task taskY << {
  • println 'taskY'
  • }

3、动态任务

  • 4.times { counter ->
  • task "task$counter" << {
  • println "I'm task number $counter"
  • }
  • }
  • // gradle -q task1

4、任务操纵

4.1、通过 API 进行任务之间的通信 - 增加依赖

  • 4.times { counter ->
  • task "task$counter" << {
  • println "I'm task number $counter"
  • }
  • }
  • task0.dependsOn task2, task3

4.2、通过 API 进行任务之间的通信 - 增加任务行为

  • task hello << {
  • println 'Hello Earth'
  • }
  • hello.doFirst {
  • println 'Hello Venus'
  • }
  • hello.doLast {
  • println 'Hello Mars'
  • }
  • hello << {
  • println 'Hello Jupiter'
  • }

doFirst 和 doLast 可以进行多次调用。他们分别被添加在任务的开头和结尾。当任务开始执行时这些动作会按照既定顺序进行。

5、短标记法

每个任务都是一个脚本的属性,你可以访问它。

以属性的方式访问任务:

  • task hello << {
  • println 'Hello world!'
  • }
  • hello.doLast {
  • println "Greetings from the $hello.name task."
  • }
  • // $hello.name 就是 “hello”

6、增加自定义属性

  • task myTask {
  • ext.myProperty = "myValue"
  • }
  • task printTaskProperties << {
  • println myTask.myProperty
  • }

7、调用 Ant 任务

Ant 任务是 Gradle 中的一等公民。Gradle 自带了一个 AntBuilder,可以通过它来调用一个 Ant 任务以及与 Ant 中的属性进行通信。

  • task loadfile << {
  • def files = file('../antLoadfileResources').listFiles().sort()
  • files.each { File file ->
  • if (file.isFile()) {
  • ant.loadfile(srcFile: file, property: file.name)
  • println " *** $file.name ***"
  • println "${ant.properties[file.name]}"
  • }
  • }
  • }

8、方法抽取

  • task checksum << {
  • fileList('../antLoadfileResources').each {File file ->
  • ant.checksum(file: file, property: "cs_$file.name")
  • println "$file.name Checksum: ${ant.properties["cs_$file.name"]}"
  • }
  • }
  • task loadfile << {
  • fileList('../antLoadfileResources').each {File file ->
  • ant.loadfile(srcFile: file, property: file.name)
  • println "I'm fond of $file.name"
  • }
  • }
  • File[] fileList(String dir) {
  • file(dir).listFiles({file -> file.isFile() } as FileFilter).sort()
  • }

9、定义默认任务

  • defaultTasks 'clean', 'run'
  • task clean << {
  • println 'Default Cleaning!'
  • }
  • task run << {
  • println 'Default Running!'
  • }
  • task other << {
  • println "I'm not a default task!"
  • }
  • $ gradle -q
  • Default Cleaning!
  • Default Running!

以上是一些 gradle 的基础小知识,下面以 android 项目为例,了解 gradle 实际是怎么工作的。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐