一、APP 运行环境
- APP 是在手机上运行的一类应用软件,而应用软件依附于操作系统,无论电脑还是手机,开机后都会显示桌面,而这个桌面便是操作系统的工作台
-
电脑的操作系统主要有微软的 Windows 和苹果的 Mac OS
-
手机的操作系统主要安卓手机的 Android 和苹果手机的 iOS
- 本次讲述的 APP 开发为 Android 上的应用开发,Android 系统基于 Linux 内核,但不等同于 Linux 系统,故而 APP 无法在 Linux 系统上运行
二、APP 开发技术路线
- 基于安卓系统的 APP 开发主要有两大技术路线,分别是原生开发和混合开发
-
原生开发指的是在移动平台上利用官方提供的编程语言(Java、Kotlin 等)、开发工具包(SDK)、开发环境(Android Studio)进行 APP 开发
-
混合开发指的是结合原生与 H5 技术开发混合应用,也就是将部分 APP 页面改成内嵌的网页,这样无须升级 APP,只要覆盖服务器上的网页,即可动态更新 APP页面
- 不管是原生开发还是混合开发,都要求掌握 Android Studio 的开发技能,就原生开发而言,涉及多种编程语言,包括 Java、Kotlin、C / C++、XML 等
三、APP 内置数据库
-
在 Java 编程时,可以通过连接数据库进行对数据的增删改查,这个数据库可以是 MySQL、Oracle、SQL Server,然而手机应用不能直接操作这几种数据库,因为数据库软件也需要像应用软件那样安装到操作系统上,MySQL、Oracle 数据库无法在手机上安装
-
Android 内置了专门的数据库 SQLite,它遵循关系型数据库的设计理念,语法类似于 MySQL,SQLite 无须单独安装,它内嵌在应用进程当中(嵌入式数据库),APP 无须配置连接即可直接对其进行操作
四、工程目录结构
1、基本介绍
-
APP 工程分为两个层次,第一个层次是项目,第二个层次是模块
-
模块依附于项目,每个项目至少有一个模块,也能拥有多个模块
-
一般所言的编译运行 APP,指的是运行某个模块,而非运行某个项目,因为模块才对应着实际的 APP
2、项目目录说明
- 项目目录有两个分类,分别是 app(模块) 和 Gradle Scripts
(1)app
- app 下有三个子目录
目录 | 说明 |
---|---|
manifests | 下面只有一个 XML 文件,AndroidManifest.xml,它是 APP 的运行配置文件 |
java | 下面有 3 个包(com.my.helloworld) 其中第一个包存放当前模块 Java 代码,其余两个包存放测试用 Java 代码 |
res | 存放当前模块的资源文件 |
- res 目录下有 4 个子目录
目录 | 说明 |
---|---|
drawable | 存放图形描述文件与图片文件 |
layout | 存放 APP 页面的布局文件 |
mipmap | 存放 APP 的启动图标 |
values | 存放一些常量定义文件 例如字符串常量 strings.xml、像素常量 dimens.xml、颜色常量 colors.xml、样式风格定义 styles.xml 等 |
(2)Gradle Scripts
- Gradle Scripts 下主要是工程的编译配置文件
文件 | 说明 |
---|---|
build.gradle | 该文件分为项目级与模块级两种,用于描述 APP 工程的编译规则 |
proguard-rules.pro | 该文件用于描述 Java 代码的混淆规则 |
gradle.properties | 该文件用于配置编译工程的命令行参数,一般无须改动 |
settings.gradle | 该文件配置了需要编译哪些模块,初始内容为 include ‘:app’,表示只编译 app 模块 |
local.properties | 项目的本地配置文件,它在工程编译时自动生成 用于描述开发者电脑的环境配置,包括 SDK 的本地路径、NDK 的本地路径等 |
五、编译配置文件 build.gradle
1、项目级
- 指定当前项目的总体编译规则
// Top-level build file where you can add configuration options common to all sub-projects/modules.
// 插件
plugins {
id 'com.android.application' version '7.2.2' apply false
id 'com.android.library' version '7.2.2' apply false
}
// 任务(Groovy 语言)
task clean(type: Delete) {
delete rootProject.buildDir
}
2、模块级
- 对应于具体模块,每个模块都有自己的 build.gradle,它指定当前模块的详细编译规则
// 插件
plugins {
id 'com.android.application'
}
android {
// 指定编译用的 SDK 版本,比如 30 表示使用 Android 11.0 编译
compileSdk 32
defaultConfig {
// 指定该模块的应用编号,也就是 App 的包名
applicationId "com.my.helloworld"
// 指定 App 适合运行的最小 SDK 版本,比如 19 表示至少要在Android 4.4 上运行
minSdk 21
// 指定目标设备的 SDK 版本,表示 App 最希望在哪个版本的 Android 上运行
targetSdk 32
// 指定 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_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
// 指定 App 编译的依赖信息
dependencies {
// 指定编译 Android 的高版本支持库,如 AppCompatActivity 必须指定编译 appcompat 库
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
六、清单文件 AndroidManifest.xml
- AndroidManifest.xml 指定了 App 的运行配置信息
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.my.helloworld">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.HelloWorld"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
- AndroidManifest.xml 的根节点为 manifest ,它的 package 属性指定了该 APP 的包名,manifest 下有 application 节点,它的各属性说明如下
属性 | 说明 |
---|---|
allowBackup | 是否允许应用备份 允许用户备份系统应用和第三方应用的 apk 安装包和应用数据,以便在刷机或者数据丢失后恢复应用 用户可通过 adb backup 和 adb restore 进行数据的备份和恢复,true 表示允许,false 表示不允许 |
icon | 指定 APP 在手机屏幕上显示的图标 |
label | 指定 APP 在手机屏幕上显示的名称 |
roundIcon | 指定 APP 的圆角图标 |
supportsRtl | 是否支持阿拉伯语 / 波斯语这种从右往左的文字排列顺序,true 表示支持,false 表示不支持 |
theme | 指定 APP 的显示风格 |
- application 下还有个 activity 节点,它是活动页面的注册声明,只有在 AndroidManifest.xml 中正确配置了 activity 节点,才能在运行时访问对应的活动页面,初始配置的 MainActivity 正是 APP 的默认主页,之所以说该页面是 APP 主页,是因为它的 activity 节点内部还配置了以下的过滤信息
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
-
action 节点设置的 android.intent.action.MAIN 表示该页面是 APP 的入口页面,启动 APP 时会最先打开该页面
-
category 节点设置的 android.intent.category.LAUNCHER 决定了是否在手机屏幕上显示 APP 图标,如果同时有两个 activity 节点内部都设置了android.intent.category.LAUNCHER,那么桌面就会显示两个 APP 图标,以上的两种节点规则可能一开始不太好理解,只需记住默认主页必须同时配置这两种过滤规则即可
七、APP 设计规范
1、基本介绍
-
APP 将看得见的界面设计与看不见的代码逻辑区分开,利用 XML 文件描绘应用界面,使用 Java 代码书写程序逻辑
-
这样形成 APP 前后端分离的设计规约,有利于提高 APP 集成的灵活性
2、界面设计与代码逻辑
- 手机的功能越来越强大,某种意义上相当于微型电脑,比如打开一个电商 APP,仿佛是在电脑上浏览网站,网站分为用户看得到的网页,以及用户看不到的后台,APP 也分为用户看得到的界面,以及用户看不到的后台,虽然 Android 允许使用 Java 代码描绘界面,但不提倡这么做,推荐的做法是将界面设计从 Java 代码剥离出来,通过单独的 XML 文件定义界面布局,就像网站使用 HTML 文件定义网页那样,把界面设计与代码逻辑分开,参考了网站的前后端分离,还有下列几点好处
-
使用 XML 文件描述 APP 界面,可以很方便地在 Android Studio 上预览界面效果,如果 XML 文件修改了内容,立刻就能在预览区域观看最新界面,倘若使用 Java 代码描绘界面,那么必须运行 APP 才能看到界面,无疑费时许多
-
一个界面布局可以被多处代码复用,反过来,一段 Java 代码也可能适配多个界面布局
八、APP 设计初体验
1、界面设计
- activity_main.xml 文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World"/>
</LinearLayout>
2、代码逻辑
- MainActivity 类
package com.my.androiddevelop;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.hello);
tv.setText("你好,世界!");
}
}
九、APP 页面快速入门
1、APP 活动页面
(1)编写 XML 文件
- 在 layout 目录下创建 activity_my.xml 文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/my_text" />
</LinearLayout>
- 在 activity_main.xml 文件中创建跳转按钮
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World"/>
<Button
android:id="@+id/but"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/but_text" />
</LinearLayout>
- 在 res/values/strings.xml 中定义字符串常量
<resources>
<string name="app_name">AndroidDevelop</string>
<string name="my_text">My Activity</string>
<string name="but_text">跳转</string>
</resources>
(2)编写 Java 代码
- MyActivity 类
package com.my.helloworld;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public class MyActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_main);
}
}
- MainActivity 类,实现跳转
package com.my.androiddevelop;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.hello);
tv.setText("你好,世界!");
// 实现跳转
Button button = findViewById(R.id.but);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent();
intent.setClass(MainActivity.this, MyActivity.class);
startActivity(intent);
}
});
}
}
(3)注册页面
- AndroidManifest.xml 文件
<activity android:name=".MyActivity" />
2、快速生成页面
- 右击包名 -> 【Activity】 -> 【Empty Activity】 -> 填写相关信息 -> 【Finish】
(1)XML 文件
- activity_new.xml 文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".NewActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
(2)Java 代码
- NewActivity
package com.my.androiddevelop;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class NewActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_new);
}
}
(3)页面注册
- AndroidManifest.xml 文件
<activity android:name=".NewActivity" android:exported="false" />
十、消息框
1、基本介绍
- 消息框可以在一个小型弹出式窗口中提供与操作有关的简单反馈,它只会填充消息所需的空间大小,超时后,消息框会自动消失,它有如下常用方法
-
Toast.makeText(Context context, @StringRes int resId, @Duration int duration)
:该方法用于实例化 Toast 对象,它接收以下参数:activity Context、应向用户显示的文本 / 字符串资源的资源 ID、消息框应在屏幕上停留的时长 -
show()
:该方法用于显示消息框 -
setGravity(int gravity, int xOffset, int yOffset)
:该方法用于设置消息框在屏幕上的垂直和水平对齐方式,它接收以下参数:对齐方式、水平方向上的偏移量(向左,以像素为单位)、在垂直方向上的偏移量(向上,以像素为单位)
- 消息框基本使用为默认位置显示文本内容,高级使用为自定义显示位置,自定义显示内容
2、基本使用
(1)XML 文件
- activity_toast_test.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/btn_toast1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TOAST 1"/>
<Button
android:id="@+id/btn_toast2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TOAST 2"/>
<Button
android:id="@+id/btn_toast3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TOAST 3"/>
</LinearLayout>
(2)Java 代码
- ToastTestActivity.java
package com.my.androiddevelop;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.my.androiddevelop.utils.ToastUtils;
public class ToastTestActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn_toast1;
private Button btn_toast2;
private Button btn_toast3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_toast_test);
btn_toast1 = findViewById(R.id.btn_toast1);
btn_toast2 = findViewById(R.id.btn_toast2);
btn_toast3 = findViewById(R.id.btn_toast3);
btn_toast1.setOnClickListener(this);
btn_toast2.setOnClickListener(this);
btn_toast3.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_toast1) {
Toast toast = Toast.makeText(this, "Hello Toast LONG", Toast.LENGTH_LONG);
toast.show();
return;
}
if (v.getId() == R.id.btn_toast2) {
// Toast.makeText(this, "Hello Toast SHORT", Toast.LENGTH_SHORT).show();
ToastUtils.toast(this, R.string.toast_str);
return;
}
if (v.getId() == R.id.btn_toast3) {
Toast toast = Toast.makeText(this, "Hello Toast LONG", Toast.LENGTH_LONG);
toast.setGravity(Gravity.BOTTOM | Gravity.CENTER, 150, 300);
toast.show();
return;
}
}
}
3、高级使用
(1)XML 文件
- activity_toast_content.xml,自定义显示内容
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/toast_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher_background"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="自定义 ToastView"/>
</LinearLayout>
(2)Java 代码
- CustomToastActivity.java
package com.my.androiddevelop;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.my.androiddevelop.utils.ToastUtils;
public class CustomToastActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn_show_toast1;
private Button btn_show_toast2;
private Context context;
private Toast toast;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_toast);
initView();
}
private void initView() {
btn_show_toast1 = findViewById(R.id.btn_show_toast1);
btn_show_toast2 = findViewById(R.id.btn_show_toast2);
context = getApplicationContext();
LayoutInflater layoutInflater = getLayoutInflater();
// 使用布局解析器来解析一个布局
View view = layoutInflater.inflate(R.layout.activity_toast_content, null);
toast = new Toast(context);
toast.setGravity(Gravity.TOP, 0, 800);
toast.setDuration(Toast.LENGTH_SHORT);
toast.setView(view);
btn_show_toast1.setOnClickListener(this);
btn_show_toast2.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_show_toast1:
ToastUtils.toast(context, "Hello Toast");
break;
case R.id.btn_show_toast2:
toast.show();
break;
}
}
}
4、实例实操
(1)基本介绍
-
对 Android 原生的 Toast 类进行简单封装实现 Toast 工具类
-
对 Android 原生的 Toast 类进行自定义封装实现自定义 Toast 工具类
(2)具体实现
- ToastUtils.java
package com.my.androiddevelop.utils;
import android.content.Context;
import android.widget.Toast;
/**
* Toast 工具类
* @author my
*/
public class ToastUtils {
/**
* 显示 Toast
* @param context 上下文对象
* @param text 文本
*/
public static void toast(Context context, CharSequence text) {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
}
/**
* 显示 Toast
* @param context 上下文对象
* @param resId 字符串资源的资源 ID
*/
public static void toast(Context context, int resId) {
Toast.makeText(context, resId, Toast.LENGTH_SHORT).show();
}
/**
* 长时间显示 Toast
* @param context 上下文对象
* @param text 文本
*/
public static void toastLong(Context context, CharSequence text) {
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
}
/**
* 长时间显示 Toast
* @param context 上下文对象
* @param resId 字符串资源的资源 ID
*/
public static void toastLong(Context context, int resId) {
Toast.makeText(context, resId, Toast.LENGTH_LONG).show();
}
}
- CustomToastUtils.java
package com.my.androiddevelop.utils;
import android.content.Context;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.my.androiddevelop.R;
import java.util.Timer;
import java.util.TimerTask;
public class CustomToastUtils {
// 定时器
public static void controlToastTime(Toast toast, int duration) {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
toast.cancel();
}
}, duration);
}
// 自定义位置弹出消息
public static void show(Context context, String s) {
View view = LayoutInflater.from(context).inflate(R.layout.activity_toast_content, null);
TextView tv_msg = view.findViewById(R.id.tv_toast_text);
tv_msg.setText(s);
Toast toast = new Toast(context);
controlToastTime(toast, 500);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.setView(view);
toast.show();
}
}