引言
Android开发是一个充满挑战与机遇的领域。从简单的“Hello World”应用到复杂的商业级应用,开发者需要掌握从基础到进阶的全面技能。本指南将通过一系列实例分析,系统性地讲解Android开发的核心概念、常见问题及其解决方案。无论你是初学者还是有一定经验的开发者,都能从中获得实用的知识和技巧。
第一部分:Android开发基础
1.1 Android开发环境搭建
在开始编码之前,首先需要搭建开发环境。Android Studio是官方推荐的集成开发环境(IDE),它提供了代码编辑、调试、性能分析等强大功能。
步骤:
- 下载并安装Android Studio(建议从官网下载最新版本)。
- 配置SDK(Software Development Kit),包括Android SDK Platform和构建工具。
- 创建一个虚拟设备(AVD)用于测试应用。
常见问题与解决方案:
- 问题: Android Studio安装缓慢或失败。
- 解决方案: 确保网络连接稳定,使用镜像源加速下载。如果遇到Gradle同步问题,可以尝试离线模式或手动下载Gradle版本。
- 问题: AVD启动失败。
- 解决方案: 检查BIOS设置中是否启用了虚拟化技术(VT-x/AMD-V),并确保系统资源充足。
1.2 第一个Android应用:Hello World
创建一个简单的应用,理解Android应用的基本结构。
代码示例:
// MainActivity.java
package com.example.helloworld;
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 textView = findViewById(R.id.textView);
textView.setText("Hello, Android!");
}
}
布局文件:
<!-- activity_main.xml -->
<?xml version="1.1" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="24sp" />
</LinearLayout>
解析:
MainActivity是应用的入口,继承自AppCompatActivity。onCreate方法在Activity创建时调用,setContentView设置布局。TextView是一个UI组件,用于显示文本。
1.3 Android应用的基本组件
Android应用由四大组件构成:Activity、Service、BroadcastReceiver和ContentProvider。
1. Activity: 用户交互的界面。 2. Service: 后台运行的服务,不提供用户界面。 3. BroadcastReceiver: 接收和响应系统或应用广播。 4. ContentProvider: 管理共享数据,如联系人、媒体等。
实例:使用Service播放音乐
代码示例:
// MusicService.java
package com.example.musicplayer;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import androidx.annotation.Nullable;
public class MusicService extends Service {
private MediaPlayer mediaPlayer;
@Override
public void onCreate() {
super.onCreate();
mediaPlayer = MediaPlayer.create(this, R.raw.background_music);
mediaPlayer.setLooping(true);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mediaPlayer.start();
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
在Activity中启动Service:
// MainActivity.java
Intent serviceIntent = new Intent(this, MusicService.class);
startService(serviceIntent);
常见问题与解决方案:
- 问题: Service在后台被系统杀死。
- 解决方案: 使用前台服务(Foreground Service)并添加通知,提高优先级。
- 问题: MediaPlayer资源未释放导致内存泄漏。
- 解决方案: 在Service的
onDestroy方法中释放MediaPlayer资源。
- 解决方案: 在Service的
第二部分:UI设计与交互
2.1 布局管理器
Android提供多种布局管理器,如LinearLayout、RelativeLayout、ConstraintLayout等。
ConstraintLayout示例:
<!-- activity_constraint.xml -->
<?xml version="1.1" 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/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_margin="16dp" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Me"
app:layout_constraintTop_toBottomOf="@id/textView1"
app:layout_constraintStart_toStartOf="parent"
android:layout_margin="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
解析:
- ConstraintLayout允许通过约束关系定位视图,减少嵌套,提高性能。
app:layout_constraintTop_toTopOf等属性定义视图之间的相对位置。
2.2 RecyclerView的使用
RecyclerView是用于显示大量数据的高效组件,支持列表和网格布局。
代码示例:
// MainActivity.java
package com.example.recyclerviewexample;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private MyAdapter adapter;
private List<String> data;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
data = new ArrayList<>();
for (int i = 1; i <= 50; i++) {
data.add("Item " + i);
}
adapter = new MyAdapter(data);
recyclerView.setAdapter(adapter);
}
}
适配器类:
// MyAdapter.java
package com.example.recyclerviewexample;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private List<String> data;
public MyAdapter(List<String> data) {
this.data = data;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_layout, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.textView.setText(data.get(position));
}
@Override
public int getItemCount() {
return data.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textView);
}
}
}
布局文件:
<!-- item_layout.xml -->
<?xml version="1.1" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp" />
</LinearLayout>
常见问题与解决方案:
- 问题: RecyclerView滑动卡顿。
- 解决方案: 使用
setHasFixedSize(true)固定大小,优化布局层级,避免在onBindViewHolder中执行耗时操作。
- 解决方案: 使用
- 问题: 数据更新后UI不刷新。
- 解决方案: 使用
adapter.notifyDataSetChanged()或更高效的notifyItemInserted()等方法。
- 解决方案: 使用
2.3 Fragment的使用
Fragment是Activity的一部分,可以复用UI组件,适用于多屏幕适配。
代码示例:
// MyFragment.java
package com.example.fragmentexample;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
public class MyFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_my, container, false);
TextView textView = view.findViewById(R.id.textView);
textView.setText("This is a Fragment");
return view;
}
}
在Activity中添加Fragment:
// MainActivity.java
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, new MyFragment())
.commit();
常见问题与解决方案:
- 问题: Fragment生命周期管理复杂。
- 解决方案: 使用
ViewModel和LiveData管理数据,避免在Fragment中持有Activity引用。
- 解决方案: 使用
- 问题: Fragment通信困难。
- 解决方案: 使用
setTargetFragment或通过Activity作为中介,或使用事件总线(如LiveData)。
- 解决方案: 使用
第三部分:数据存储与网络通信
3.1 SharedPreferences存储
SharedPreferences用于存储简单的键值对数据。
代码示例:
// 存储数据
SharedPreferences sharedPreferences = getSharedPreferences("MyPrefs", MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("username", "JohnDoe");
editor.putInt("age", 30);
editor.apply();
// 读取数据
String username = sharedPreferences.getString("username", "Default");
int age = sharedPreferences.getInt("age", 0);
常见问题与解决方案:
- 问题: SharedPreferences存储大量数据导致性能问题。
- 解决方案: 仅用于存储少量简单数据,复杂数据使用数据库。
- 问题: 多进程访问冲突。
- 解决方案: 使用
MODE_MULTI_PROCESS或考虑使用其他存储方案。
- 解决方案: 使用
3.2 Room数据库
Room是Google推荐的SQLite抽象层,提供编译时验证和简化数据库操作。
实体类:
// User.java
package com.example.roomexample;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "users")
public class User {
@PrimaryKey(autoGenerate = true)
public int id;
public String name;
public String email;
}
DAO接口:
// UserDao.java
package com.example.roomexample;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface UserDao {
@Insert
void insert(User user);
@Query("SELECT * FROM users")
List<User> getAllUsers();
}
数据库类:
// AppDatabase.java
package com.example.roomexample;
import androidx.room.Database;
import androidx.room.RoomDatabase;
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
使用:
// 在Activity中
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name").build();
// 插入数据
new Thread(() -> {
User user = new User();
user.name = "Alice";
user.email = "alice@example.com";
db.userDao().insert(user);
}).start();
// 查询数据
new Thread(() -> {
List<User> users = db.userDao().getAllUsers();
// 更新UI需使用runOnUiThread
runOnUiThread(() -> {
// 更新UI
});
}).start();
常见问题与解决方案:
- 问题: Room数据库操作在主线程执行导致ANR。
- 解决方案: 所有数据库操作必须在后台线程执行,使用
AsyncTask、Thread或Coroutine。
- 解决方案: 所有数据库操作必须在后台线程执行,使用
- 问题: 数据库版本升级问题。
- 解决方案: 使用Room的迁移策略,编写
Migration类。
- 解决方案: 使用Room的迁移策略,编写
3.3 Retrofit网络请求
Retrofit是用于RESTful API调用的流行库。
接口定义:
// ApiService.java
package com.example.retrofitexample;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
public interface ApiService {
@GET("users/{id}")
Call<User> getUser(@Path("id") int id);
}
使用:
// MainActivity.java
package com.example.retrofitexample;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<User> call = apiService.getUser(1);
call.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful()) {
User user = response.body();
Log.d("API", "User: " + user.name);
}
}
@Override
public void onFailure(Call<User> call, Throwable t) {
Log.e("API", "Error: " + t.getMessage());
}
});
}
}
常见问题与解决方案:
- 问题: 网络请求在主线程执行。
- 解决方案: Retrofit默认在后台线程执行,但回调在主线程,确保UI更新在主线程。
- 问题: 网络权限未添加。
- 解决方案: 在
AndroidManifest.xml中添加<uses-permission android:name="android.permission.INTERNET"/>。
- 解决方案: 在
第四部分:进阶主题
4.1 Jetpack组件
Jetpack是Google提供的一套组件库,帮助开发者构建高质量应用。
ViewModel示例:
// MyViewModel.java
package com.example.viewmodelexample;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class MyViewModel extends ViewModel {
private MutableLiveData<String> data = new MutableLiveData<>();
public LiveData<String> getData() {
return data;
}
public void setData(String newData) {
data.setValue(newData);
}
}
在Activity中使用:
// MainActivity.java
MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
viewModel.getData().observe(this, newData -> {
// 更新UI
textView.setText(newData);
});
常见问题与解决方案:
- 问题: ViewModel生命周期管理不当。
- 解决方案: ViewModel的生命周期与Activity/Fragment绑定,避免在ViewModel中持有Context引用。
- 问题: LiveData更新频繁导致UI卡顿。
- 解决方案: 使用
postValue()在后台线程更新,或使用DistinctUntilChanged过滤重复值。
- 解决方案: 使用
4.2 Kotlin协程
Kotlin协程简化了异步编程,避免了回调地狱。
代码示例:
// MainActivity.kt
package com.example.coroutineexample
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 启动协程
CoroutineScope(Dispatchers.Main).launch {
val result = fetchData()
Log.d("Coroutine", "Result: $result")
}
}
private suspend fun fetchData(): String {
return withContext(Dispatchers.IO) {
// 模拟网络请求
delay(2000)
"Data from network"
}
}
}
常见问题与解决方案:
- 问题: 协程泄漏(协程未取消)。
- 解决方案: 使用
viewModelScope或lifecycleScope,它们会自动取消协程。
- 解决方案: 使用
- 问题: 线程切换错误。
- 解决方案: 使用
Dispatchers.IO进行IO操作,Dispatchers.Main更新UI。
- 解决方案: 使用
4.3 性能优化
1. 布局优化:
- 使用
ConstraintLayout减少嵌套。 - 使用
<include>和<merge>标签复用布局。 - 避免在
onDraw中执行耗时操作。
2. 内存优化:
- 使用
LeakCanary检测内存泄漏。 - 避免在静态变量中持有Context。
- 及时释放资源(如Bitmap、数据库连接)。
3. 网络优化:
- 使用缓存(如Retrofit的缓存机制)。
- 压缩数据(如Gzip)。
- 使用HTTP/2或HTTP/3。
代码示例:使用LeakCanary检测内存泄漏
// app/build.gradle
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
}
常见问题与解决方案:
- 问题: 应用启动缓慢。
- 解决方案: 使用
App Startup库延迟初始化,优化Application类。
- 解决方案: 使用
- 问题: 内存泄漏。
- 解决方案: 使用
WeakReference或ViewModel管理生命周期。
- 解决方案: 使用
第五部分:常见问题与解决方案
5.1 运行时权限处理
从Android 6.0开始,需要动态请求权限。
代码示例:
// MainActivity.java
package com.example.permissionexample;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CODE = 100;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA}, REQUEST_CODE);
} else {
// 权限已授予,执行操作
useCamera();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
useCamera();
} else {
Toast.makeText(this, "权限被拒绝", Toast.LENGTH_SHORT).show();
}
}
}
private void useCamera() {
// 使用相机功能
}
}
常见问题与解决方案:
- 问题: 权限请求被拒绝后无法再次请求。
- 解决方案: 向用户解释为什么需要该权限,或引导用户到设置页面手动开启。
- 问题: 权限请求在Android 11及以上版本行为变化。
- 解决方案: 使用
ActivityCompat.requestPermissions,并处理shouldShowRequestPermissionRationale。
- 解决方案: 使用
5.2 屏幕适配
1. 使用dp和sp:
dp(密度无关像素)用于布局尺寸。sp(缩放无关像素)用于字体大小。
2. 使用ConstraintLayout:
- 通过约束关系适应不同屏幕。
3. 使用尺寸限定符:
- 创建
values-sw600dp、values-sw720dp等文件夹,为不同屏幕提供不同尺寸。
代码示例:
<!-- values/dimens.xml -->
<dimen name="padding_medium">16dp</dimen>
<!-- values-sw600dp/dimens.xml -->
<dimen name="padding_medium">24dp</dimen>
常见问题与解决方案:
- 问题: 布局在平板上显示异常。
- 解决方案: 使用
values-sw600dp等文件夹提供不同布局,或使用ConstraintLayout的百分比约束。
- 解决方案: 使用
- 问题: 字体大小在不同设备上不一致。
- 解决方案: 使用
sp单位,并考虑用户系统字体大小设置。
- 解决方案: 使用
5.3 电池优化
1. 使用JobScheduler或WorkManager:
- 延迟执行任务,减少唤醒次数。
2. 避免频繁唤醒:
- 使用
AlarmManager的setExactAndAllowWhileIdle时需谨慎。
3. 使用Doze模式:
- 在Android 6.0+,系统会进入Doze模式,限制后台任务。
代码示例:使用WorkManager
// MyWorker.java
package com.example.workmanagerexample;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
public class MyWorker extends Worker {
public MyWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
// 执行后台任务
return Result.success();
}
}
调度任务:
// MainActivity.java
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MyWorker.class)
.setConstraints(new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.build();
WorkManager.getInstance(this).enqueue(workRequest);
常见问题与解决方案:
- 问题: 后台任务在Doze模式下被延迟。
- 解决方案: 使用WorkManager,它会自动处理Doze模式。
- 问题: 电池消耗过快。
- 解决方案: 使用
Battery Historian分析电池使用情况,优化代码。
- 解决方案: 使用
第六部分:实战项目分析
6.1 项目结构
一个典型的Android项目结构如下:
app/
├── src/
│ ├── main/
│ │ ├── java/com/example/myapp/
│ │ │ ├── ui/ # UI相关类
│ │ │ ├── data/ # 数据层
│ │ │ ├── repository/ # 仓库层
│ │ │ ├── viewmodel/ # ViewModel
│ │ │ └── di/ # 依赖注入
│ │ ├── res/ # 资源文件
│ │ └── AndroidManifest.xml
│ └── test/ # 单元测试
└── build.gradle # 构建配置
6.2 架构模式:MVVM
Model-View-ViewModel (MVVM) 是推荐的架构模式。
代码示例:
// User.java (Model)
package com.example.mvvmexample;
public class User {
private String name;
private String email;
// 构造函数、getter和setter
}
// UserRepository.java (Repository)
package com.example.mvvmexample;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
public class UserRepository {
private MutableLiveData<User> userLiveData = new MutableLiveData<>();
public LiveData<User> getUser() {
// 从网络或数据库获取数据
User user = new User();
user.setName("John");
user.setEmail("john@example.com");
userLiveData.setValue(user);
return userLiveData;
}
}
// UserViewModel.java (ViewModel)
package com.example.mvvmexample;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
public class UserViewModel extends ViewModel {
private UserRepository repository;
private LiveData<User> userLiveData;
public UserViewModel() {
repository = new UserRepository();
userLiveData = repository.getUser();
}
public LiveData<User> getUser() {
return userLiveData;
}
}
// MainActivity.java (View)
package com.example.mvvmexample;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private UserViewModel viewModel;
private TextView nameTextView, emailTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nameTextView = findViewById(R.id.nameTextView);
emailTextView = findViewById(R.id.emailTextView);
viewModel = new ViewModelProvider(this).get(UserViewModel.class);
viewModel.getUser().observe(this, user -> {
if (user != null) {
nameTextView.setText(user.getName());
emailTextView.setText(user.getEmail());
}
});
}
}
常见问题与解决方案:
- 问题: ViewModel中持有Context导致内存泄漏。
- 解决方案: ViewModel不应持有Context,如需使用,使用
AndroidViewModel。
- 解决方案: ViewModel不应持有Context,如需使用,使用
- 问题: Repository层职责不清晰。
- 解决方案: Repository负责数据源的协调(网络、数据库、本地缓存),不应包含UI逻辑。
6.3 依赖注入(Dagger Hilt)
Dagger Hilt是Google推荐的依赖注入框架。
步骤:
- 添加依赖:
// app/build.gradle
dependencies {
implementation 'com.google.dagger:hilt-android:2.48'
kapt 'com.google.dagger:hilt-compiler:2.48'
}
- 创建Application类:
// MyApplication.java
package com.example.hiltexample;
import android.app.Application;
import dagger.hilt.android.HiltAndroidApp;
@HiltAndroidApp
public class MyApplication extends Application {
}
- 创建模块:
// AppModule.java
package com.example.hiltexample;
import dagger.Module;
import dagger.Provides;
import dagger.hilt.InstallIn;
import dagger.hilt.components.SingletonComponent;
import javax.inject.Singleton;
@Module
@InstallIn(SingletonComponent.class)
public class AppModule {
@Provides
@Singleton
public ApiService provideApiService() {
return new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService.class);
}
}
- 在Activity中使用:
// MainActivity.java
package com.example.hiltexample;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import dagger.hilt.android.AndroidEntryPoint;
import javax.inject.Inject;
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
@Inject
ApiService apiService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 使用apiService
}
}
常见问题与解决方案:
- 问题: 依赖注入配置错误导致编译失败。
- 解决方案: 检查
@Module、@Provides和@Inject注解是否正确,确保所有依赖都有提供者。
- 解决方案: 检查
- 问题: 循环依赖。
- 解决方案: 重构代码,避免循环依赖,或使用
@Lazy延迟注入。
- 解决方案: 重构代码,避免循环依赖,或使用
第七部分:测试
7.1 单元测试
使用JUnit和Mockito进行单元测试。
代码示例:
// UserRepositoryTest.java
package com.example.mvvmexample;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.when;
public class UserRepositoryTest {
@Mock
private ApiService apiService;
private UserRepository userRepository;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
userRepository = new UserRepository(apiService);
}
@Test
public void testGetUser() {
// 模拟API响应
User mockUser = new User();
mockUser.setName("Test");
when(apiService.getUser(1)).thenReturn(mockUser);
User user = userRepository.getUser(1);
assert user.getName().equals("Test");
}
}
7.2 UI测试
使用Espresso进行UI测试。
代码示例:
// MainActivityTest.java
package com.example.espressoexample;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
@Rule
public ActivityScenarioRule<MainActivity> activityRule =
new ActivityScenarioRule<>(MainActivity.class);
@Test
public void testTextView() {
onView(withId(R.id.textView))
.check(matches(withText("Hello, Android!")));
}
}
常见问题与解决方案:
- 问题: 测试运行缓慢。
- 解决方案: 使用
@Before和@After管理测试资源,避免在测试中执行耗时操作。
- 解决方案: 使用
- 问题: UI测试不稳定。
- 解决方案: 使用
IdlingResource等待异步操作完成,或使用Espresso的IdlingPolicies。
- 解决方案: 使用
第八部分:发布与维护
8.1 构建变体
使用构建变体管理不同环境(如开发、测试、生产)。
配置:
// app/build.gradle
android {
buildTypes {
debug {
applicationIdSuffix ".debug"
buildConfigField "String", "API_BASE_URL", "\"https://dev.api.example.com/\""
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField "String", "API_BASE_URL", "\"https://api.example.com/\""
}
}
}
使用:
// 在代码中
String baseUrl = BuildConfig.API_BASE_URL;
8.2 混淆与优化
ProGuard/R8配置:
# proguard-rules.pro
-keep class com.example.myapp.** { *; }
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
常见问题与解决方案:
- 问题: 混淆后应用崩溃。
- 解决方案: 检查ProGuard规则,确保必要的类和方法不被混淆。
- 问题: 构建时间过长。
- 解决方案: 使用增量构建,优化Gradle配置,使用
buildCache。
- 解决方案: 使用增量构建,优化Gradle配置,使用
8.3 监控与崩溃报告
集成Firebase Crashlytics或Sentry。
代码示例:
// app/build.gradle
dependencies {
implementation 'com.google.firebase:firebase-crashlytics:18.6.1'
}
初始化:
// MyApplication.java
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true);
记录自定义异常:
try {
// 可能抛出异常的代码
} catch (Exception e) {
FirebaseCrashlytics.getInstance().recordException(e);
}
常见问题与解决方案:
- 问题: 崩溃报告不准确。
- 解决方案: 确保在ProGuard中保留必要的符号,使用
mappingFile上传映射文件。
- 解决方案: 确保在ProGuard中保留必要的符号,使用
- 问题: 隐私问题。
- 解决方案: 遵循GDPR等法规,获取用户同意,提供关闭崩溃报告的选项。
总结
本指南从Android开发基础到进阶实战,涵盖了UI设计、数据存储、网络通信、架构模式、测试和发布等关键主题。通过实例分析和常见问题解决方案,帮助开发者构建高质量的应用。记住,Android开发是一个持续学习的过程,不断实践和优化是提升技能的关键。希望本指南能为你的Android开发之旅提供有力的支持。
