一、HelloWorld:你的第一个Android应用
1.1 为什么Android值得学
去年我带一个大学生做毕设,他一开始问我:”老师,现在写Android还有前途吗?”我笑着跟他说,你看每年Google I/O大会,Android新版还是有很多让人兴奋的东西——Material You设计、Compose声明式UI、Jetpack Compose动画系统,这些都在进步。关键是,移动端开发是理解整个软件工程的绝佳入口,你从Android学起,会把数据结构、算法、网络、数据库、设计模式全过一遍。
1.2 环境搭建(别被劝退)
先说实话:2023年前后Android开发环境确实有点坑,但现在已经好多了。我用的是:
JDK 17(Android Studio要求Java 17+)
Android Studio Flamingo或更新版本
Android SDK 34(对应Android 14)
最小SDK版本:21(Android 5.0,覆盖99%以上设备)
装好之后,File → New → New Project → Empty Activity,项目名 HelloWorld,Language选Kotlin(Java也行,但Kotlin是官方推荐)。
1.3 核心文件结构
app/src/main/
├── AndroidManifest.xml ← 应用清单文件
├── java/com/example/helloworld/
│ └── MainActivity.kt ← 主Activity
└── res/
├── layout/activity_main.xml ← 布局文件
├── values/strings.xml ← 字符串资源
└── mipmap-*/ ← 图标资源
重点:AndroidManifest.xml是必须的,没有它应用根本无法安装。它是整个应用的”身份证”。
1.4 第一个HelloWorld
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.helloworld">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/Theme.HelloWorld">
<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>
activity_main.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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/helloText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world"
android:textSize="32sp"
android:textColor="#000000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt:
package com.example.helloworld
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.helloworld.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
// ViewBinding:强烈推荐,比findViewById干净太多
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 用ViewBinding代替setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 点击文字弹出Toast
binding.helloText.setOnClickListener {
Toast.makeText(this, "Hello Android!", Toast.LENGTH_SHORT).show()
}
}
}
strings.xml:
<resources>
<string name="app_name">HelloWorld</string>
<string name="hello_world">Hello Android!</string>
</resources>
1.5 运行方式
有三种方式:
- 连真机:USB调试开启,Android Studio右上角选你的设备,点Run(Shift+F10)
- 模拟器:Tools → Device Manager → Create Virtual Device,选Pixel 6,系统镜像选Android 14
- AVD Manager里管理多个设备
二、Activity与生命周期:Android的基石
2.1 什么是Activity
Activity是Android应用的”窗口”,每个屏幕通常对应一个Activity。你可以把Activity想象成Windows里的窗口——有打开、关闭、最小化、最大化这些状态。
2.2 生命周期方法(必背)
onCreate() ← Activity创建时调用
onStart() ← Activity变为可见
onResume() ← Activity获得焦点(可交互)
onPause() ← Activity失去焦点
onStop() ← Activity完全不可见
onDestroy() ← Activity被销毁
onRestart() ← Activity从stop状态重新启动
我用一个生活例子帮你理解:
想象你去图书馆看书:
onCreate()= 你走到图书馆,找座位坐下,打开书(初始化)onStart()= 你开始能看到书的内容了(可见)onResume()= 你开始认真看书了(可交互)onPause()= 有人拍你肩膀,你暂时分心了(失去焦点)onStop()= 图书馆关门了,你看不见书了(不可见)onDestroy()= 你离开图书馆了(销毁)
2.3 实际代码
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate called")
// 这里放初始化代码
// 注意:savedInstanceState不为空说明是恢复状态,不是新建
}
override fun onStart() {
super.onStart()
Log.d(TAG, "onStart called")
}
override fun onResume() {
super.onResume()
Log.d(TAG, "onResume called")
}
override fun onPause() {
super.onPause()
Log.d(TAG, "onPause called")
// 保存数据的好地方,比如用户输入
}
override fun onStop() {
super.onStop()
Log.d(TAG, "onStop called")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy called")
// 清理资源,比如关闭数据库连接
}
}
2.4 屏幕旋转时的坑
问题:横竖屏切换会重新创建Activity,数据丢失。
解决方案:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
// 恢复数据
val count = savedInstanceState.getInt("count")
Log.d(TAG, "Restored count: $count")
} else {
// 首次创建
Log.d(TAG, "First creation")
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// 保存数据
outState.putInt("count", 42)
}
}
或者在AndroidManifest.xml里禁止旋转重建:
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize" />
三、Jetpack Compose:现代Android UI
3.1 为什么学Compose
说实话:传统的XML布局方式很繁琐,每次改个样式要打开布局文件、找ID、写代码,来回切换。Compose用声明式UI,类似React的写法,代码量减少一半,性能还更好。
3.2 第一个Compose例子
@Composable
fun HelloWorld() {
// 状态:点击次数
var count by remember { mutableStateOf(0) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "Hello Compose!",
fontSize = 32.sp,
color = Color.Black
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = "Count: $count",
fontSize = 24.sp
)
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = { count++ },
modifier = Modifier.fillMaxWidth()
) {
Text("Increment")
}
}
}
3.3 MainActivity使用Compose
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
HelloWorld()
}
}
}
注意:setContent 是Compose的入口,必须在Activity的onCreate里调用。
四、常见报错与处理
4.1 AndroidManifest.xml相关
错误:Activity not declared in AndroidManifest.xml
Caused by: java.lang.RuntimeException:
Unable to instantiate activity ComponentInfo{...}
原因:AndroidManifest.xml里没注册Activity 解决:添加activity标签
错误:No activity found to handle intent
原因:跳转的Activity没注册或intent-filter配置错误
解决:检查AndroidManifest.xml
4.2 资源相关
错误:Resource not found
java.lang.ExceptionInInitializerError
Caused by: java.lang.IllegalStateException:
Need to specify a ViewModelProvider
原因:布局文件引用了不存在的资源ID 解决:检查R文件,确认资源ID存在
错误:Out of memory
java.lang.OutOfMemoryError: Failed to allocate...
原因:加载大图没做压缩 解决:
// 使用Glide或Coil加载图片,自动压缩
Glide.with(context)
.load(imageUrl)
.override(200, 200) // 限制尺寸
.into(imageView)
4.3 生命周期相关
错误:CalledFromWrongThreadException
Only the original thread that created a view hierarchy
can touch its views.
原因:在子线程更新UI 解决:
// 方式1:主线程
runOnUiThread {
textView.text = "Updated"
}
// 方式2:View.post
textView.post {
textView.text = "Updated"
}
// 方式3:Coroutine(推荐)
lifecycleScope.launch(Dispatchers.Main) {
val data = withContext(Dispatchers.IO) { fetchFromNetwork() }
textView.text = data
}
错误:Fragment not attached to Activity
原因:Fragment生命周期处理不当
解决:在Fragment里检查isAdded
if (isAdded) {
activity?.let { updateUI(it) }
}
4.4 网络相关
错误:Cleartext HTTP traffic not permitted
原因:Android 9+默认禁止明文HTTP
解决:android:usesCleartextTraffic=“true”
<application
android:usesCleartextTraffic="true"
...>
或者使用HTTPS。
错误:NetworkOnMainThreadException
原因:在主线程做网络请求
解决:用协程或RxJava
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
makeNetworkRequest()
}
// 更新UI
}
五、性能优化详解
5.1 布局优化
问题1:嵌套过深
<!-- 反例:嵌套3层 -->
<LinearLayout>
<LinearLayout>
<LinearLayout>
<TextView />
</LinearLayout>
</LinearLayout>
</LinearLayout>
解决:用ConstraintLayout或LinearLayout+weight
<!-- 正例:ConstraintLayout单层 -->
<androidx.constraintlayout.widget.ConstraintLayout>
<TextView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
问题2:不必要的View
<!-- 反例:用了FrameLayout但只需要TextView -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView ... />
</FrameLayout>
解决:直接用TextView
<TextView ... />
5.2 图片优化
使用WebP格式
PNG: 100KB
JPEG: 80KB
WebP: 60KB(质量相同)
图片加载库
// Glide(Google官方推荐)
Glide.with(context)
.load(url)
.placeholder(R.drawable.loading) // 加载中显示
.error(R.drawable.error) // 加载失败显示
.into(imageView)
// Coil(Kotlin友好,推荐新项目使用)
imageLoader.load(url) {
placeholder(R.drawable.loading)
error(R.drawable.error)
}
5.3 内存优化
避免内存泄漏
// 反例:静态引用Activity导致泄漏
class MyHelper {
companion object {
private var activity: Activity? = null
fun setActivity(act: Activity) {
activity = act // 泄漏!
}
}
}
// 正例:用弱引用
class MyHelper {
companion object {
private var activityRef: WeakReference<Activity>? = null
fun setActivity(act: Activity) {
activityRef = WeakReference(act)
}
fun getActivity(): Activity? {
return activityRef?.get()
}
}
}
合理使用单例
// 反例:全局单例持有Context
object NetworkManager {
var context: Context? = null
fun init(ctx: Context) {
context = ctx // 可能泄漏
}
}
// 正例:单例使用Application Context
object NetworkManager {
private var appContext: Context? = null
fun init(ctx: Context) {
appContext = ctx.applicationContext // 安全
}
}
5.4 列表优化(RecyclerView)
ViewHolder复用
class MyAdapter(
private val items: List<String>
) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
// ViewHolder类(必须)
class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.text)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_layout, parent, false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
// 复用ViewHolder,避免频繁创建
holder.textView.text = items[position]
}
override fun getItemCount() = items.size
}
DiffUtil优化刷新
class MyDiffCallback(
private val oldList: List<String>,
private val newList: List<String>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldPos: Int, newPos: Int) =
oldList[oldPos] == newList[newPos]
override fun areContentsTheSame(oldPos: Int, newPos: Int) =
oldList[oldPos] == newList[newPos]
}
// 使用
val diffCallback = MyDiffCallback(oldList, newList)
val diffResult = DiffUtil.calculateDiff(diffCallback)
diffResult.dispatchUpdatesTo(adapter)
5.5 启动速度优化
Application初始化
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 异步初始化第三方SDK
doAsyncInit()
}
private fun doAsyncInit() {
Thread {
// 初始化逻辑
}.start()
}
}
冷启动优化
// 在AndroidManifest.xml设置application主题
<application
android:theme="@style/SplashTheme">
<!-- res/values/styles.xml -->
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@drawable/splash_bg</item>
</style>
六、代码规范指南
6.1 命名规范
Activity命名 “` Login
