安装

下载Android Studio,然后在安装目录的上级目录下新建Project和SDK目录

安装的时候选custom。在SDK安装路径的时候指定为上述的目录

setting里找到SDK,SDK tools,下载NDK Cmake

开启投屏,Windows: File > Settings > Device Mirroring

gradle是一个项目组动画构建工具,帮开发者做了依赖、打包、部署、发布、各种渠道的差异管理工作

Android项目结构

介绍

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
.gradle		开发工具AS自动生成的,删掉都不影响,重新编译会生成
.ideaa 开发工具AS自动生成的,删掉都不影响,重新编译会生成
app 存放开发代码的
gradle 里面的gradle-wrapper.properties存放gradle的版本
.gitignore git忽略文件
build.gradle 项目级的配置文件(和app下的build.gradle不一样),指定了项目的总体编译规则
gradle.propertites 一些配置信息,一般无需改动
gradlew gradlew.bat 不用管
local.properties 配置SDK的路径,需要的话也会有NDK
settings.gradle 配置文件,包含项目模块信息等,初始为include 'app'也就是编译app模块


app目录
build 项目编译生成的临时文件
libs 第三方jar包
src 存放开发代码的
build.gradle 项目配置文件,制定了当前模块的编译规则
proguard-rules.pto 配置Java代码的混淆的规则,打包的时候用到,因为不混淆的话apk可以直接解压出class文件

src目录
AndroidTest 测试代码目录,测Android代码的,可以跑在Android手机上
Test 测试代码目录,跑Java代码的,跑在开发环境中
main 正式代码目录

main目录
java java代码
res 资源目录,图标、布局、图片、字符串等等
AndroidManifest.xml 清单文件,配置四大组件的


另一种介绍:
安卓工程分为两个层次,一个层次是项目,一个层次是模块
模块依附于项目,每个项目至少一个模块
一般所说的编译运行app,指的是运行某个模块而不是运行某个项目,因为模块才对应实际的app
安卓项目下面两个分类:app(代表app模块)、gradle scripts
app模块: manifest java res
gradle scripts:build.gradle groguard-rules.pro gradle.properties settings.gradle local.properties

app的res子目录:
drawable目录存放图形描述文件与图片文件
layout目录存放app页面的布局文件
mipmap目录存放app的启动图标
values目录存放一些常量定义文件,例如字符串常量strings.xml,像素常量dimens.xml,颜色常量colors.xml,样式风格定义styles.xml

每个模块都有自己模块级的build.gradle

项目配置文件build.gradle

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
plugins {
alias(libs.plugins.android.application)
}

android {
namespace 'com.example.demo'
compileSdk 34

defaultConfig {
// 指定该模块的应用编号也就是app的包名
applicationId "com.example.demo"
// 指定app适合运行的最小SDK版本号
minSdk 24
// 指定目标设备的SDK版本号,标识app最希望在哪个版本的Android上运行
targetSdk 34
// 指定app的应用版本号
versionCode 1
// 指定app的应用版本名称
versionName "1.0"

// 单元测试用的
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
// 指定混淆规则
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
buildFeatures {
viewBinding true
}
}

// 指定app编译的依赖信息
dependencies {

implementation libs.appcompat
implementation libs.material
implementation libs.constraintlayout
implementation libs.navigation.fragment
implementation libs.navigation.ui
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
}

gradle工具的版本配置在gradle\wrapper\gradle-wrapper.properties,也可以依次选择菜单File->ProjectStructure->Project中修改gradle版本,注意每个版本的AS都有对应的gradle版本,只有二者的版本对应才能成功编译

清单文件AndroidManifest.xml

指定了app的运行配置信息,是一个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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application
android:allowBackup="true" // 是否允许应用备份
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher" // 指定app在手机屏幕上显示的图标
android:label="@string/app_name" // 指定app在手机屏幕上显示的名称
android:roundIcon="@mipmap/ic_launcher_round" // 指定app的圆角图标
android:supportsRtl="true" // 是否支持阿拉伯语从右往左的文字排列
android:theme="@style/Theme.Demo" // 指定app的显示风格
tools:targetApi="31">
<activity
android:name=".MainActivity" // 指定了了第一个运行的Activity
android:exported="true"
android:theme="@style/Theme.Demo">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
// 如果还有别的activity需要在这里注册,语法就是<activity android:name="./NewActivity"/>
</application>

</manifest>

什么是activity,是一个应用程序组件,提供一个屏幕,用户可以用来交互为了完成某项任务,从A界面跳到B界面,相当于从A这个Activity跳到了B这个Activity,Activity相当于一个舞台,各种控件都在这个舞台上运行

Android开发语法

界面显示与处理逻辑

利用xml标记描绘应用界面(界面的显示交给xml文件),Java代码处理程序逻辑交互,这样的好处是把展示效果和处理逻辑分开,比如两个页面长得一样但是处理逻辑不一样,这样的话xml文件可以拿去直接用,两个页面的处理逻辑一样的话,java代码可以拿去直接用

页面的显示与跳转

创建新的app页面,完整的页面创建包括以下三个步骤

1
2
3
在layout目录下创建xml文件
创建与xml文件对应的java代码
在AndroidManifest.xml中注册页面配置

xml文件里可以有TextViewButton等各种控件,各个控件有自己的属性比如textid等,在xml对应的activity.java中可以调用比如findViewById来找到对应的控件,实现点击事件等操作

简单控件

文本控件

文本控件,TextView在xml中声明,有android:id android:text等属性,在activity中可以通过findViewById来找到控件,然后调用setText来设置文本内容

按钮控件

按钮控件,Button实际上是继承于TextView的,Button主要新增了onclick属性,指定了触发点击操作之后执行什么事件(这种写法已经过时了)

1
2
<Button
android:onClick="doClick"/>

然后在activity.java中实现doClick方法

demo

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
activity_main.xml
<TextView
android:id="@+id/view_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TimeShow"
android:textColor="@color/black"
android:textSize="17sp"
android:onClick="doClick"
tools:ignore="MissingConstraints" />

----------------------------------------------------------------------------------------------------------------

MainActivity.java

public class MainActivity extends AppCompatActivity {

private TextView tv_result;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_result = findViewById(R.id.view_time);

}

public void doClick(View view) {
String desc = String.format("%s clicked!", DateUtil.getTime());
tv_result.setText(desc);
}
}



----------------------------------------------------------------------------------------------------------------
DateUtil.java

public class DateUtil {
public static String getTime() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
return sdf.format(new Date());
}
}



PS:具体工具类在util包里实现
app\src\main\java\com\example\buttonclick\util
然后在MainActivity里导入import com.example.buttonclick.util.DateUtil;

使用setOnClickListener来进行事件响应

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
acvitity_main.xml
<TextView
android:id="@+id/view_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_click"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TimeShow"
android:textColor="@color/black"
android:textSize="17sp"
tools:ignore="MissingConstraints" />




-------------------------------------------------------------------------------
MainActivity.java
public class MainActivity extends AppCompatActivity {

private TextView tv_result;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_result = findViewById(R.id.view_time);
Button btn_click = findViewById(R.id.btn_click);
btn_click.setOnClickListener(new MyOnClickListener(tv_result));
}

static class MyOnClickListener implements View.OnClickListener {
private final TextView tv_result;

public MyOnClickListener(TextView tv_result) {
this.tv_result = tv_result;
}
public void onClick(View v) {
String desc = String.format("%s clicked!", DateUtil.getTime());
tv_result.setText(desc);
}
}
}

Activity组件

Activity的启动和结束

从当前页面跳转到新页面

1
startActivity(new Intent(源页面.this,目标页面.class))

从当前页面回到上一个页面,相当于关闭当前页面

1
finish()

and_1

activity有启动模式,默认就是用栈的那一套,先入后出,这个可以在AndroidManifest.xml里设置

显示Itent和隐式Intent

Intent是各个组件之间信息沟通的桥梁,用于Android各组件之间的通信,主要完成以下工作

1
2
3
4
5
表明本次通信请求从哪来到哪去

发起方携带本次通信需要的数据内容

发起方若想判断接收方的处理结果,Intent就要入则让接收方传回应答的数据内容

显式Intent

直接指定来源Activity与目标Activity,属于精确匹配,有三种构建方式,第一个参数表示跳转的来源页面,即SourceActivity.this,第二个参数表示待跳转的页面,即DestActivity.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1.在Intent的构造函数中指定
Intent intent = new Intent(this, ActNextActivity.class)
startActivity(intent);

2.调用意图对象的setClass方法指定
Intent intent = new Intent();
intent.setClass(this, ActNextActivity.class)
startActivity(intent);

3.调用意图对象的setComponent方法指定
Intent intent = new Intent();
ComponentName component = new ComponentName(this, ActNextActivity.class);
intent.setComponent(component);
startActivity(intent);
也可以不传class类的参数,可以直接传包名

隐式Intent

没有明确指定要跳转的目标活动,只给出一个动作字符让系统匹配

常见系统动作的取值说明

Intent类的系统动作常量名 系统动作的常量值 说明
ACTION_MAIN android.intent.action.MAIN App启动时的入口
ACTION_VIEW android.intent.action.VIEW 向用户显示数据
ACTION_SEND android.intent.action.SEND 分享内容
ACTION_CALL android.intent.action.CALL 直接拨号
ACTION_DIAL android.intent.action.DIAL 准备拨号
ACTION_SENDTO android.intent.action.SENDTO 发送短信
ACTION_ANSWER android.intent.action.ANSWER 接听电话

动作名称既可以通过setAction方法指定,也可以通过构造函数Intent(String action)直接生成Intent对象,但是不能只指定action还需要传入UriCategoryUri数据可以通过构造函数Intent(String action,Uri uri)在生成对象时一起指定,也可以通过setData方法指定;Category可以通过addCategory方法指定

1
2
3
4
5
6
String phoneNumber = "12345";
Intent intent = new Intent();
intent.setAction(Intent.ACTION_DIAL);
Uri uri = Uri.parse("tel:" + phoneNumber);
intent.setData(uri);
startActivity(intent);

如果不想启动系统应用而是启动自己写的另一个app的话,需要在目标app的AndroidManifest.xml中对主Activity加上一条intent-filter属性

1
2
3
4
<intent-filter>
<action android:name="android.intent.action.Demo" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>

然后调用

1
2
3
4
Intent intent = new Intent();
intent.setAction("android.intent.action.Demo");
intent.addCategory(Intent.CATEGORY.DEFAULT");
startActivity(intent);

不同的activity之间数据传递

把数据打包成Bundle对象

向下一个Activity传递数据

sourceActivity

1
2
3
4
5
6
7
8
public void onClick(View v) {
Intent intent = new Intent(this,ActReceiveActivity.class);
Bundle bundle = new Bundle();
bundle.putString("name","Jack");
intent.putExtras(bundle);
startActivity(intent);
//
}

接收方

1
2
Bundle bundle = getIntent().getExtras();
String name = bundle.getString("name");

向上一个Activity返回数据

处理下一个下面的应答数据

1
2
3
4
5
6
7
上一个页面打包好请求数据,调用startActivityForResult方法执行跳转动作(这种方法已经过时了)

下一个页面接收并解析请求数据,进行相应处理

下一个页面在返回上一个页面时,打包应答数据并调用setResult方法返回数据

上一个页面重写方法onActivityResult,解析获得下一个页面的返回数据

demo

requestActivity

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
private static final String msg = "hello";
private ActivityResultLauncher<Intent> intentActivityResultLauncher;

protected void onCreate(Bundle savedInstanceState) {
...
TextView tv_request = findViewById(R.id.tv_request);
tv_request.setText(msg);
findViewById(R.id.btn_request).setOnClickListener(this);
intentActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
@Override
// 这个回调函数是处理有数据返回时调用的
public void onActivityResult(ActivityResult o) {

}
});
//上面的写法一般用lamda表达式
intentActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result != null) {
Intent intent = result.getData();
if (intent != null && result.getResultCode() == Activity.RESULT_OK) {
Bundle bundle = intent.getExtras();
...
}
}
});
}

public void onCLick(View v) {
Intent intent = new Intent(this,responseActivity.class);
Bundle bundle = new Bundle();
bundle.putString("msg",msg);
intent.putExtras(bundle);
intentActivityResultLauncher.launch(intent);
}

responseActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
TextView tv_request = findViewById(R.id.tv_resuest);
// 获取数据
Bundle bundle = getIntent().getExtras();
...
findViewById(R.id.btn_response).setOnClickListener(this);
...

public void onClick(View v) {
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString("resp","How are you");
// 返回上一个页面
setResult(Activity.RESULT_OK, intent);
intent.putExtras(bundle);
finish();
}

Content Provider

三个作用:在不同的应用之间共享数据,使用内容组件获取通讯信息,在应用之间共享文件

ContentProvider为App存取内部数据提供统一的外部接口,让不同的应用之间得以共享数据,可以理解为一个接口,不同应用之间的数据传输得通过这个接口来实现

提供数据方的Content Provider可以定义哪些数据是可以被读取的,哪些数据是不能被读取的

使用ContentProvider只实现服务端App的数据封装,如果客户端App想访问对方内部的数据,需要通过内容解析器ContentResol

广播

广播是一种数据传输方式
发送一条广播之后,可以被不同的广播接收者接收,接收者接收到广播之后再进行逻辑处理(BroadcastReceiver)