项目结构
MyAndroidProject/
├── build.gradle
├── settings.gradle
├── gradle/
├── app/
│ ├── build.gradle
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/ 存放 Java 或 Kotlin 源代码,按包名结构组织。
│ │ │ ├── res/ 存放资源文件
│ │ │ ├── AndroidManifest.xml 应用的清单文件,声明应用的组件和权限。
│ │ ├── test/
│ │ └── androidTest/ 存放 Android 仿真器和设备上运行的测试代码。
└── gradle/wrapper/
- build.gradle:项目级的 Gradle 构建文件,用于配置整个项目的构建设置。
- settings.gradle:用于包含多个模块的设置。
- gradle:存放 Gradle Wrapper 的脚本和配置文件。
简单使用
这里直接通过分析示例代码来讲述如何简单编写一个安卓程序。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
HelloWorldTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
} } }
}
onCreate() 函数是此应用的入口点,可以简单理解为C语言的main函数。其中的setContent() 函数用于通过可组合函数定义布局。任何标有 @Composable 注解的函数都可通过 setContent() 函数或其他可组合函数进行调用。
注意:
- @Composable 函数名称采用首字母大写形式。
- 需在该函数前面添加 @Composable 注解。
- @Composable 函数无法返回任何内容。
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
HelloWorldTheme {
Greeting("Android")
}
}
日志使用
为什么使用日志
很多人会非常喜欢使用System.out.println()方法来打印日志,在Kotlin中使用与之对应的是println()方法,在真正的项目开发中,是极度不建议使用System.out.println()或println()方法的,如果你在公司的项目中经常使用这两个方法来打印日志的话,就很有可能要挨骂了。那缺点在哪儿了呢?这个就太多了,比如日志开关不可控制、不能添加日志标签、日志没有级别区分等。
简单使用
Android中的日志工具类是Log(android.util.Log),这个类中提供了如下5个方法来供我们打印日志:
- Log.v()。用于打印那些最为琐碎的、意义最小的日志信息。对应级别verbose,是Android日志里面级别最低的一种。
- Log.d()。用于打印一些调试信息,这些信息对你调试程序和分析问题应该是有帮助的。对应级别debug,比verbose高一级。Log.i()。用于打印一些比较重要的数据,这些数据应该是你非常想看到的、可以帮你分析用户行为的数据。对应级别info,比debug高一级。
- Log.w()。用于打印一些警告信息,提示程序在这个地方可能会有潜在的风险,最好去修复一下这些出现警告的地方。对应级别warn,比info高一级。
- Log.e()。用于打印程序中的错误信息,比如程序进入了catch语句中。当有错误信息打印出来的时候,一般代表你的程序出现严重问题了,必须尽快修复。对应级别error,比warn高一级。
四大核心组件
在 Android 应用开发中,有四大核心组件,它们分别是:
- Activity:
代表用户界面的单一屏幕,负责与用户交互。
可以启动其他 Activity 以创建多屏幕应用。 - Service:
在后台运行的组件,不提供用户界面。
用于执行长时间运行的操作或处理后台任务,例如播放音乐或下载文件。 - Broadcast Receiver:
用于接收和处理广播消息(如系统或应用程序的事件)。
允许应用在特定事件发生时响应,例如网络状态变化或电池电量变化。 - Content Provider:
提供应用间的数据共享机制。
允许一个应用访问另一个应用的数据(如联系人、媒体等),并管理对这些数据的访问。
Activity使用
Activity是一个单一的界面,用户可以在该界面与应用进行交互。每个Activity通常代表应用中的一个屏幕。
Activity有一组生命周期方法,帮助开发者管理其状态和行为。常见的生命周期方法包括:
- onCreate(): 初始化Activity时调用,设置布局等。
- onStart(): Activity即将变为可见时调用。
- onResume(): Activity已可见并与用户交互时调用。
- onPause(): Activity即将进入不可见状态时调用。
- onStop(): Activity已不可见时调用。
- onDestroy(): Activity即将被销毁时调用,清理资源
代码示例
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.first_layout)
// 初始化界面元素
val textView: TextView = findViewById(R.id.textView)
textView.text = "欢迎来到我的应用!"
}
override fun onStart() {
super.onStart()
// Activity即将可见
}
override fun onResume() {
super.onResume()
// Activity已可见并与用户交互
}
override fun onPause() {
super.onPause()
// Activity即将不可见
}
override fun onStop() {
super.onStop()
// Activity已不可见
}
override fun onDestroy() {
super.onDestroy()
// 清理资源
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp"
android:layout_centerInParent="true" />
</RelativeLayout>
Service使用
Service是Android中实现程序后台运行的解决方案,它非常适合执行那些不需要和用户交互而
且还要求长期运行的任务,例如:
-
下载文件
-
上传数据
-
播放音乐
-
处理网络请求
在Android的Service类中,有几个重要的方法用于管理服务的生命周期和行为: -
onCreate():当服务第一次被创建时调用。可以在此方法中进行一次性初始化操作,如创建线程或初始化资源。
-
onStartCommand():每次调用startService()时都会调用此方法。可以在这里处理启动服务时传入的Intent,并执行实际的后台任务。
-
onBind():当其他组件通过bindService()方法绑定到服务时调用。如果服务不支持绑定,可以返回null。
-
onUnbind():当所有客户端都解除绑定时调用,可以在此方法中执行清理工作。
-
onRebind():当新的客户端绑定到已经解绑的服务时调用。
-
onDestroy():当服务被销毁时调用。可以在此方法中释放资源或停止后台任务。
代码示例
class MyService : Service() {
override fun onBind(intent: Intent): IBinder {
TODO("Return the communication channel to the service.")
}
override fun onCreate() {
super.onCreate()
Log.d("MyService", "onCreate executed")
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.d("MyService", "onStartCommand executed")
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
super.onDestroy()
Log.d("MyService", "onDestroy executed")
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="@+id/startServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Service" />
<Button android:id="@+id/stopServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Stop Service" />
</LinearLayout>
class MainActivity : AppCompatActivity() {
private lateinit var startServiceBtn: Button
private lateinit var stopServiceBtn: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startServiceBtn = findViewById(R.id.startServiceBtn)
stopServiceBtn = findViewById(R.id.stopServiceBtn)
startServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
startService(intent) // 启动Service
}
stopServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
stopService(intent) // 停止Service
}
}
}
Broadcast Receiver使用
为Android中的每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会收到自己所关心的广播内容,这些广播可能是来自于系统的,也可能是来自于其他应用程序的。
- 隐式广播: 不指定发送广播的组件,只指定意图(Intent)中的动作。适用于大多数系统广播。系统或其他应用发出,接收者不明确指定。
- 显式广播: 指定特定组件(如某个Activity或Service)来接收广播。程序发出,明确指定接收者。
代码示例
动态注册
动态注册是在代码中注册BroadcastReceiver,通常在Activity或Service的生命周期方法中进行。
class TimeChangeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val currentTime = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date())
Toast.makeText(context, "Time has changed $currentTime", Toast.LENGTH_SHORT).show()
}
}
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var startServiceBtn: Button
private lateinit var stopServiceBtn: Button
private lateinit var timeChangeReceiver: TimeChangeReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startServiceBtn = findViewById(R.id.startServiceBtn)
stopServiceBtn = findViewById(R.id.stopServiceBtn)
startServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
startService(intent) // 启动Service
}
stopServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
stopService(intent) // 停止Service
}
timeChangeReceiver = TimeChangeReceiver()
}
override fun onStart() {
super.onStart()
// 注册BroadcastReceiver
val filter = IntentFilter(Intent.ACTION_TIME_TICK)
registerReceiver(timeChangeReceiver, filter)
}
override fun onStop() {
super.onStop()
// 注销BroadcastReceiver
unregisterReceiver(timeChangeReceiver)
}
}
动态注册的BroadcastReceiver可以自由地控制注册与注销,在灵活性方面有很大的优势。但
是它存在着一个缺点,即必须在程序启动之后才能接收广播,因为注册的逻辑是写在
onCreate()方法中的
静态注册
编写对应的一个逻辑:
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "received in MyBroadcastReceiver",
Toast.LENGTH_SHORT).show()
}
}
注意这里需要将其注册进行AndroidManifest.xml文件中
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter> <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter></receiver>
这里的
"com.example.broadcasttest.MY_BROADCAST"
即表明他要接收的类型。
class MainActivity : AppCompatActivity() {
private lateinit var button: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button=findViewById(R.id.button)
button.setOnClickListener {
val intent = Intent("com.example.broadcasttest.MY_BROADCAST")
intent.setPackage(packageName)
sendBroadcast(intent)
}
}
}
这样即可实现
ContentProvider使用
Content Provider是Android应用中的一种组件,用于管理和共享应用数据。它为其他应用提供了一种安全的、标准化的方式来访问数据。、
在业务中承担的角色:
- 数据共享: 允许一个应用访问另一个应用的数据,这对于多应用之间的数据交互非常重要。
- 抽象数据存储: 提供统一的接口来访问不同类型的数据源,如SQLite数据库、文件系统或网络。
- 安全性: 通过权限控制,确保只有授权的应用才能访问特定的数据。
ContentProvider的用法一般有两种:一种是使用现有的ContentProvider读取和操作相应程
序中的数据;另一种是创建自己的ContentProvider,给程序的数据提供外部访问接口。
权限处理
在我们读取其他数据之前,我们首先要处理的是权限的问题。比如我们读取照片和电话信息时,app都会跳出对应的弹窗要求我们授予对应的权限。
流程可以大概分为:
- 在Manifest中声明权限。
- 检查是否已获得权限。
- 如果未获得权限,申请权限。
- 处理用户的授权结果。
声明权限
我们首先要声明权限,即安装时就要求拥有的权限.
<uses-permission android:name="android.permission.READ_CONTACTS" />
检查权限
这里要使用的函数为checkSelfPermission函数
public static int checkSelfPermission(@NonNull Context context, @NonNull String permission)
参数
- context: 当前的上下文,通常是 Activity 或 Application。
- permission: 要检查的权限字符串,例如 Manifest.permission.CAMERA。
返回值
- 返回 PackageManager.PERMISSION_GRANTED (值为 0) 表示权限已获得。
- 返回 PackageManager.PERMISSION_DENIED (值为 -1) 表示权限未获得。
申请权限
public void requestPermissions(@NonNull String[] permissions, int requestCode)
参数
- permissions: 一个字符串数组,包含要请求的权限。
- requestCode: 一个整型值,用于标识这个请求,以便在回调中处理。
处理授权结果
当用户做出权限选择后,系统会调用这个方法。
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
- requestCode: 之前传递的请求代码。
- permissions: 请求的权限数组。
- grantResults: 权限请求的结果数组,其中每个元素对应于 permissions 数组的元素,值为 PackageManager.PERMISSION_GRANTED 或 PackageManager.PERMISSION_DENIED。
完整代码示例
package com.example.permissionexample
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
class MainActivity : AppCompatActivity() {
private val REQUEST_PHONE_PERMISSION = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
checkAndRequestPhonePermission()
}
private fun checkAndRequestPhonePermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
// 权限已获得
Toast.makeText(this, "权限已获得", Toast.LENGTH_SHORT).show()
} else {
// 请求权限
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), REQUEST_PHONE_PERMISSION)
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode == REQUEST_PHONE_PERMISSION) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 用户已同意权限
Toast.makeText(this, "用户同意权限", Toast.LENGTH_SHORT).show()
} else {
// 用户拒绝权限
Toast.makeText(this, "用户不同意权限", Toast.LENGTH_SHORT).show()
}
}
}
}
读取数据
基本方法
这里我们使用的ContentResolver,来进行对应的CRUD:
- 查询数据: 使用 query() 方法从 ContentProvider 获取数据。
- 插入数据: 使用 insert() 方法向 ContentProvider 添加新数据。
- 更新数据: 使用 update() 方法修改 ContentProvider 中的现有数据。
- 删除数据: 使用 delete() 方法从 ContentProvider 移除数据。
contentResolver.query(uri, null, null, null, null)
参数:
- uri: 要查询的内容 URI。
- projection: 要返回的列名数组(可为 null,表示返回所有列)。
- selection: 选择条件(WHERE 子句,或 null)。
- selectionArgs: 选择条件的参数数组(或 null)。
- sortOrder: 排序顺序(或 null)。
对于结果的处理,我们这里使用Cursor,来实现结果的梳理:
- 遍历结果: 使用 moveToNext() 方法移动到下一行。
- 获取列值: 使用 getString()、getInt() 等方法获取特定列的数据。
- 获取列索引: 使用 getColumnIndex() 方法根据列名获取列的索引。
- 资源管理: 提供 close() 方法以释放相关资源。
代码示例
val contentResolver: ContentResolver = contentResolver //创建ContentValues 实例
val uri: Uri = ContactsContract.Contacts.CONTENT_URI //
val cursor: Cursor? = contentResolver.query(uri, null, null, null, null)
cursor?.use {
val nameIndex = it.getColumnIndex(ContactsContract.Contacts.CONTACT_STATUS)
while (it.moveToNext()) { //将光标移动到下一行。如果还有行可供访问,返回 true,否则返回 false。
val name = it.getString(nameIndex)
// 显示联系人名称
Toast.makeText(this, "联系人: $name", Toast.LENGTH_SHORT).show()
}
}
这里使用了getColumnIndex方法,使用列名ContactsContract.Contacts.CONTACT_STATUS来获取对应的一个索引。