安装
下载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文件里可以有TextView
、Button
等各种控件,各个控件有自己的属性比如text
、id
等,在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))
|
从当前页面回到上一个页面,相当于关闭当前页面

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
还需要传入Uri
和Category
,Uri
数据可以通过构造函数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)