引言

Android开发是一个充满挑战与机遇的领域。从简单的“Hello World”应用到复杂的商业级应用,开发者需要掌握从基础到进阶的全面技能。本指南将通过一系列实例分析,系统性地讲解Android开发的核心概念、常见问题及其解决方案。无论你是初学者还是有一定经验的开发者,都能从中获得实用的知识和技巧。

第一部分:Android开发基础

1.1 Android开发环境搭建

在开始编码之前,首先需要搭建开发环境。Android Studio是官方推荐的集成开发环境(IDE),它提供了代码编辑、调试、性能分析等强大功能。

步骤:

  1. 下载并安装Android Studio(建议从官网下载最新版本)。
  2. 配置SDK(Software Development Kit),包括Android SDK Platform和构建工具。
  3. 创建一个虚拟设备(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资源。

第二部分: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生命周期管理复杂。
    • 解决方案: 使用ViewModelLiveData管理数据,避免在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。
    • 解决方案: 所有数据库操作必须在后台线程执行,使用AsyncTaskThreadCoroutine
  • 问题: 数据库版本升级问题。
    • 解决方案: 使用Room的迁移策略,编写Migration类。

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"
        }
    }
}

常见问题与解决方案:

  • 问题: 协程泄漏(协程未取消)。
    • 解决方案: 使用viewModelScopelifecycleScope,它们会自动取消协程。
  • 问题: 线程切换错误。
    • 解决方案: 使用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类。
  • 问题: 内存泄漏。
    • 解决方案: 使用WeakReferenceViewModel管理生命周期。

第五部分:常见问题与解决方案

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-sw600dpvalues-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. 避免频繁唤醒:

  • 使用AlarmManagersetExactAndAllowWhileIdle时需谨慎。

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
  • 问题: Repository层职责不清晰。
    • 解决方案: Repository负责数据源的协调(网络、数据库、本地缓存),不应包含UI逻辑。

6.3 依赖注入(Dagger Hilt)

Dagger Hilt是Google推荐的依赖注入框架。

步骤:

  1. 添加依赖:
// app/build.gradle
dependencies {
    implementation 'com.google.dagger:hilt-android:2.48'
    kapt 'com.google.dagger:hilt-compiler:2.48'
}
  1. 创建Application类:
// MyApplication.java
package com.example.hiltexample;

import android.app.Application;
import dagger.hilt.android.HiltAndroidApp;

@HiltAndroidApp
public class MyApplication extends Application {
}
  1. 创建模块:
// 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);
    }
}
  1. 在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等待异步操作完成,或使用EspressoIdlingPolicies

第八部分:发布与维护

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

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上传映射文件。
  • 问题: 隐私问题。
    • 解决方案: 遵循GDPR等法规,获取用户同意,提供关闭崩溃报告的选项。

总结

本指南从Android开发基础到进阶实战,涵盖了UI设计、数据存储、网络通信、架构模式、测试和发布等关键主题。通过实例分析和常见问题解决方案,帮助开发者构建高质量的应用。记住,Android开发是一个持续学习的过程,不断实践和优化是提升技能的关键。希望本指南能为你的Android开发之旅提供有力的支持。