引言

Android开发是一个充满挑战但回报丰厚的领域。对于初学者来说,从零开始学习Android编程可能会感到不知所措。本文旨在通过详细的实例分析和实战指南,帮助你从零开始掌握Android开发的核心技能,并提供常见问题的解决方案。我们将通过一个完整的项目实例,逐步讲解Android开发的各个方面,包括环境搭建、UI设计、数据处理、网络请求、性能优化等。

第一部分:环境搭建与项目创建

1.1 安装Android Studio

Android Studio是Google官方推荐的Android开发IDE。以下是安装步骤:

  1. 下载Android Studio:访问Android Studio官网下载最新版本。
  2. 安装:运行安装程序,按照向导完成安装。确保勾选”Android SDK”和”Android Virtual Device”。
  3. 配置SDK:首次启动时,Android Studio会引导你配置Android SDK。选择默认路径或自定义路径,建议使用默认路径。
  4. 验证安装:创建一个新的空白项目,运行模拟器或连接真机测试。

1.2 创建第一个Android项目

  1. 打开Android Studio,点击”New Project”。
  2. 选择”Empty Activity”模板。
  3. 配置项目信息:
    • Name: HelloWorld
    • Package name: com.example.helloworld
    • Save location: 选择项目保存路径
    • Language: Java或Kotlin(推荐Kotlin)
    • Minimum SDK: 选择API 21(Android 5.0)或更高
  4. 点击”Finish”,等待项目构建完成。

1.3 项目结构解析

一个典型的Android项目包含以下主要目录:

app/
├── src/
│   ├── main/
│   │   ├── java/          # Java/Kotlin源代码
│   │   ├── res/           # 资源文件
│   │   │   ├── layout/    # XML布局文件
│   │   │   ├── values/    # 字符串、颜色等资源
│   │   │   └── drawable/  # 图片资源
│   │   └── AndroidManifest.xml  # 应用清单文件
│   └── test/              # 单元测试
├── build.gradle           # 模块级构建配置
└── gradle.properties      # Gradle属性配置

第二部分:UI设计与布局

2.1 布局基础

Android提供了多种布局方式,最常用的是ConstraintLayout和LinearLayout。

示例:使用ConstraintLayout创建登录界面

activity_login.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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".LoginActivity">

    <EditText
        android:id="@+id/etUsername"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="用户名"
        android:inputType="text"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="100dp"
        android:layout_marginHorizontal="32dp" />

    <EditText
        android:id="@+id/etPassword"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="密码"
        android:inputType="textPassword"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/etUsername"
        android:layout_marginTop="16dp"
        android:layout_marginHorizontal="32dp" />

    <Button
        android:id="@+id/btnLogin"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="登录"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/etPassword"
        android:layout_marginTop="32dp"
        android:layout_marginHorizontal="32dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

2.2 自定义View

当系统提供的控件无法满足需求时,可以创建自定义View。

示例:创建一个简单的圆形进度条

CircularProgressBar.java:

package com.example.helloworld;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;

public class CircularProgressBar extends View {
    private Paint backgroundPaint;
    private Paint progressPaint;
    private RectF arcRect;
    private float progress = 0;
    private float maxProgress = 100;

    public CircularProgressBar(Context context) {
        super(context);
        init();
    }

    public CircularProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CircularProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        backgroundPaint = new Paint();
        backgroundPaint.setColor(Color.LTGRAY);
        backgroundPaint.setStyle(Paint.Style.STROKE);
        backgroundPaint.setStrokeWidth(20);
        backgroundPaint.setAntiAlias(true);

        progressPaint = new Paint();
        progressPaint.setColor(Color.BLUE);
        progressPaint.setStyle(Paint.Style.STROKE);
        progressPaint.setStrokeWidth(20);
        progressPaint.setAntiAlias(true);
        progressPaint.setStrokeCap(Paint.Cap.ROUND);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        int padding = 20;
        arcRect = new RectF(padding, padding, w - padding, h - padding);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 绘制背景圆环
        canvas.drawArc(arcRect, 0, 360, false, backgroundPaint);
        // 绘制进度圆环
        float sweepAngle = (progress / maxProgress) * 360;
        canvas.drawArc(arcRect, -90, sweepAngle, false, progressPaint);
    }

    public void setProgress(float progress) {
        this.progress = progress;
        invalidate(); // 重绘View
    }

    public void setMaxProgress(float maxProgress) {
        this.maxProgress = maxProgress;
    }
}

在布局中使用自定义View:

<com.example.helloworld.CircularProgressBar
    android:id="@+id/circularProgressBar"
    android:layout_width="200dp"
    android:layout_height="200dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

第三部分:Activity与Fragment

3.1 Activity生命周期

Activity的生命周期方法包括:

  • onCreate(): Activity被创建时调用
  • onStart(): Activity变为可见时调用
  • onResume(): Activity获得焦点时调用
  • onPause(): Activity失去焦点时调用
  • onStop(): Activity不再可见时调用
  • onDestroy(): Activity被销毁时调用

示例:生命周期方法日志记录

MainActivity.java:

package com.example.helloworld;

import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Lifecycle";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG, "onCreate: Activity创建");
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "onStart: Activity开始可见");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume: Activity获得焦点");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause: Activity失去焦点");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop: Activity不再可见");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: Activity被销毁");
    }
}

3.2 Fragment的使用

Fragment是可复用的UI模块,可以在多个Activity中使用。

示例:创建一个简单的Fragment

BlankFragment.java:

package com.example.helloworld;

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 BlankFragment extends Fragment {
    private static final String ARG_PARAM1 = "param1";
    private String mParam1;

    public BlankFragment() {
        // Required empty public constructor
    }

    public static BlankFragment newInstance(String param1) {
        BlankFragment fragment = new BlankFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_blank, container, false);
        TextView textView = view.findViewById(R.id.textView);
        textView.setText(mParam1);
        return view;
    }
}

fragment_blank.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".BlankFragment">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Fragment内容"
        android:textSize="18sp" />

</LinearLayout>

在Activity中使用Fragment:

// 在MainActivity的onCreate方法中
getSupportFragmentManager().beginTransaction()
    .replace(R.id.fragment_container, BlankFragment.newInstance("Hello Fragment"))
    .commit();

第四部分:数据存储

4.1 SharedPreferences

SharedPreferences是Android中最简单的数据存储方式,适合存储少量键值对数据。

示例:使用SharedPreferences保存用户偏好设置

SettingsManager.java:

package com.example.helloworld;

import android.content.Context;
import android.content.SharedPreferences;

public class SettingsManager {
    private static final String PREF_NAME = "app_settings";
    private static final String KEY_USERNAME = "username";
    private static final String KEY_THEME = "theme";
    private static final String KEY_NOTIFICATIONS = "notifications";

    private SharedPreferences sharedPreferences;

    public SettingsManager(Context context) {
        sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
    }

    public void saveUsername(String username) {
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString(KEY_USERNAME, username);
        editor.apply(); // 异步提交
    }

    public String getUsername() {
        return sharedPreferences.getString(KEY_USERNAME, "Guest");
    }

    public void saveTheme(String theme) {
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString(KEY_THEME, theme);
        editor.apply();
    }

    public String getTheme() {
        return sharedPreferences.getString(KEY_THEME, "Light");
    }

    public void saveNotificationsEnabled(boolean enabled) {
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putBoolean(KEY_NOTIFICATIONS, enabled);
        editor.apply();
    }

    public boolean areNotificationsEnabled() {
        return sharedPreferences.getBoolean(KEY_NOTIFICATIONS, true);
    }
}

4.2 SQLite数据库

对于复杂数据,SQLite是Android内置的关系型数据库。

示例:创建一个简单的用户数据库

UserDatabaseHelper.java:

package com.example.helloworld;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.ArrayList;
import java.util.List;

public class UserDatabaseHelper extends SQLiteOpenHelper {
    private static final String DATABASE_NAME = "users.db";
    private static final int DATABASE_VERSION = 1;
    private static final String TABLE_USERS = "users";
    private static final String COLUMN_ID = "id";
    private static final String COLUMN_NAME = "name";
    private static final String COLUMN_EMAIL = "email";
    private static final String COLUMN_AGE = "age";

    public UserDatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String CREATE_USERS_TABLE = "CREATE TABLE " + TABLE_USERS + "("
                + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
                + COLUMN_NAME + " TEXT,"
                + COLUMN_EMAIL + " TEXT,"
                + COLUMN_AGE + " INTEGER" + ")";
        db.execSQL(CREATE_USERS_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_USERS);
        onCreate(db);
    }

    public void addUser(User user) {
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(COLUMN_NAME, user.getName());
        values.put(COLUMN_EMAIL, user.getEmail());
        values.put(COLUMN_AGE, user.getAge());
        db.insert(TABLE_USERS, null, values);
        db.close();
    }

    public User getUser(int id) {
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.query(TABLE_USERS, 
                new String[]{COLUMN_ID, COLUMN_NAME, COLUMN_EMAIL, COLUMN_AGE},
                COLUMN_ID + "=?",
                new String[]{String.valueOf(id)},
                null, null, null);
        
        if (cursor != null) {
            cursor.moveToFirst();
            User user = new User(
                    cursor.getInt(0),
                    cursor.getString(1),
                    cursor.getString(2),
                    cursor.getInt(3)
            );
            cursor.close();
            return user;
        }
        return null;
    }

    public List<User> getAllUsers() {
        List<User> userList = new ArrayList<>();
        String selectQuery = "SELECT * FROM " + TABLE_USERS;
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery(selectQuery, null);
        
        if (cursor.moveToFirst()) {
            do {
                User user = new User(
                        cursor.getInt(0),
                        cursor.getString(1),
                        cursor.getString(2),
                        cursor.getInt(3)
                );
                userList.add(user);
            } while (cursor.moveToNext());
        }
        cursor.close();
        return userList;
    }

    public int updateUser(User user) {
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(COLUMN_NAME, user.getName());
        values.put(COLUMN_EMAIL, user.getEmail());
        values.put(COLUMN_AGE, user.getAge());
        return db.update(TABLE_USERS, values, COLUMN_ID + "=?",
                new String[]{String.valueOf(user.getId())});
    }

    public void deleteUser(int id) {
        SQLiteDatabase db = this.getWritableDatabase();
        db.delete(TABLE_USERS, COLUMN_ID + "=?",
                new String[]{String.valueOf(id)});
        db.close();
    }
}

User.java (实体类):

package com.example.helloworld;

public class User {
    private int id;
    private String name;
    private String email;
    private int age;

    public User() {}

    public User(int id, String name, String email, int age) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.age = age;
    }

    // Getters and Setters
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

4.3 Room数据库(推荐)

Room是Google推荐的SQLite抽象层,提供了更简洁的API和编译时检查。

示例:使用Room实现用户数据库

User.java (实体类):

package com.example.helloworld;

import androidx.room.Entity;
import androidx.room.PrimaryKey;

@Entity(tableName = "users")
public class User {
    @PrimaryKey(autoGenerate = true)
    private int id;
    
    private String name;
    private String email;
    private int age;

    // Getters and Setters
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

UserDao.java (数据访问对象):

package com.example.helloworld;

import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;

@Dao
public interface UserDao {
    @Insert
    void insert(User user);
    
    @Update
    void update(User user);
    
    @Delete
    void delete(User user);
    
    @Query("SELECT * FROM users")
    List<User> getAllUsers();
    
    @Query("SELECT * FROM users WHERE id = :id")
    User getUserById(int id);
    
    @Query("DELETE FROM users")
    void deleteAll();
}

AppDatabase.java (数据库类):

package com.example.helloworld;

import androidx.room.Database;
import androidx.room.RoomDatabase;

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

在Application类中初始化数据库:

package com.example.helloworld;

import android.app.Application;
import androidx.room.Room;

public class MyApp extends Application {
    private static AppDatabase database;

    @Override
    public void onCreate() {
        super.onCreate();
        database = Room.databaseBuilder(getApplicationContext(),
                AppDatabase.class, "user-database").build();
    }

    public static AppDatabase getDatabase() {
        return database;
    }
}

第五部分:网络请求

5.1 使用Retrofit进行网络请求

Retrofit是Square公司开发的HTTP客户端,是Android开发中最流行的网络库之一。

示例:使用Retrofit获取GitHub用户信息

添加依赖 (build.gradle):

dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1'
}

创建数据模型:

package com.example.helloworld;

import com.google.gson.annotations.SerializedName;

public class GitHubUser {
    @SerializedName("login")
    private String login;
    
    @SerializedName("id")
    private int id;
    
    @SerializedName("avatar_url")
    private String avatarUrl;
    
    @SerializedName("name")
    private String name;
    
    @SerializedName("public_repos")
    private int publicRepos;

    // Getters and Setters
    public String getLogin() { return login; }
    public void setLogin(String login) { this.login = login; }
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getAvatarUrl() { return avatarUrl; }
    public void setAvatarUrl(String avatarUrl) { this.avatarUrl = avatarUrl; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getPublicRepos() { return publicRepos; }
    public void setPublicRepos(int publicRepos) { this.publicRepos = publicRepos; }
}

创建API接口:

package com.example.helloworld;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;

public interface GitHubApi {
    @GET("users/{username}")
    Call<GitHubUser> getUser(@Path("username") String username);
}

创建Retrofit实例:

package com.example.helloworld;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitClient {
    private static final String BASE_URL = "https://api.github.com/";
    private static Retrofit retrofit = null;

    public static Retrofit getClient() {
        if (retrofit == null) {
            // 添加日志拦截器
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            
            OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .addInterceptor(loggingInterceptor)
                    .build();
            
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .client(okHttpClient)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

在Activity中使用:

package com.example.helloworld;

import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView);
        
        fetchGitHubUser("octocat");
    }

    private void fetchGitHubUser(String username) {
        GitHubApi api = RetrofitClient.getClient().create(GitHubApi.class);
        Call<GitHubUser> call = api.getUser(username);
        
        call.enqueue(new Callback<GitHubUser>() {
            @Override
            public void onResponse(Call<GitHubUser> call, Response<GitHubUser> response) {
                if (response.isSuccessful() && response.body() != null) {
                    GitHubUser user = response.body();
                    String result = String.format(
                        "用户名: %s\nID: %d\n姓名: %s\n公开仓库数: %d",
                        user.getLogin(), user.getId(), user.getName(), user.getPublicRepos()
                    );
                    textView.setText(result);
                    Log.d(TAG, "用户信息: " + result);
                } else {
                    Log.e(TAG, "请求失败: " + response.code());
                    textView.setText("请求失败: " + response.code());
                }
            }

            @Override
            public void onFailure(Call<GitHubUser> call, Throwable t) {
                Log.e(TAG, "网络错误: " + t.getMessage());
                textView.setText("网络错误: " + t.getMessage());
            }
        });
    }
}

5.2 处理网络错误和超时

示例:添加超时和错误处理

修改RetrofitClient:

package com.example.helloworld;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import java.util.concurrent.TimeUnit;

public class RetrofitClient {
    private static final String BASE_URL = "https://api.github.com/";
    private static Retrofit retrofit = null;

    public static Retrofit getClient() {
        if (retrofit == null) {
            // 添加日志拦截器
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            
            // 配置超时和重试
            OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .addInterceptor(loggingInterceptor)
                    .connectTimeout(30, TimeUnit.SECONDS)  // 连接超时
                    .readTimeout(30, TimeUnit.SECONDS)     // 读取超时
                    .writeTimeout(30, TimeUnit.SECONDS)    // 写入超时
                    .retryOnConnectionFailure(true)        // 连接失败重试
                    .build();
            
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .client(okHttpClient)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

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

6.1 内存泄漏

问题描述:Activity被销毁后,由于某些引用未被释放,导致内存无法回收。

解决方案

  1. 避免静态引用Context: “`java // 错误示例 private static Context context; // 可能导致内存泄漏

// 正确示例 private Context context; // 使用非静态引用


2. **及时取消异步任务**:
   ```java
   @Override
   protected void onDestroy() {
       super.onDestroy();
       // 取消所有网络请求
       if (call != null && !call.isCanceled()) {
           call.cancel();
       }
       // 取消所有动画
       if (animator != null) {
           animator.cancel();
       }
   }
  1. 使用弱引用

    private static class MyHandler extends Handler {
       private final WeakReference<MainActivity> weakActivity;
    
    
       public MyHandler(MainActivity activity) {
           weakActivity = new WeakReference<>(activity);
       }
    
    
       @Override
       public void handleMessage(Message msg) {
           MainActivity activity = weakActivity.get();
           if (activity != null) {
               // 处理消息
           }
       }
    }
    

6.2 ANR(应用无响应)

问题描述:应用在主线程执行耗时操作,导致界面卡顿或无响应。

解决方案

  1. 使用异步任务

    // 使用AsyncTask(已废弃,但原理相同)
    private class DownloadTask extends AsyncTask<String, Integer, String> {
       @Override
       protected String doInBackground(String... urls) {
           // 在后台线程执行耗时操作
           return downloadData(urls[0]);
       }
    
    
       @Override
       protected void onPostExecute(String result) {
           // 在主线程更新UI
           updateUI(result);
       }
    }
    
  2. 使用协程(Kotlin)

    // 在Kotlin中使用协程
    private fun fetchData() {
       lifecycleScope.launch {
           try {
               val data = withContext(Dispatchers.IO) {
                   // 在IO线程执行网络请求
                   apiService.getData()
               }
               // 在主线程更新UI
               updateUI(data)
           } catch (e: Exception) {
               // 处理错误
           }
       }
    }
    
  3. 使用RxJava

    // 使用RxJava进行异步处理
    Observable.fromCallable(() -> {
       // 耗时操作
       return fetchData();
    })
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(data -> {
       // 更新UI
       updateUI(data);
    }, error -> {
       // 处理错误
    });
    

6.3 网络请求失败

问题描述:网络请求失败,可能由于网络问题、服务器错误或配置错误。

解决方案

  1. 检查网络权限

    <!-- AndroidManifest.xml -->
    <uses-permission android:name="android.permission.INTERNET" />
    
  2. 添加网络状态检查

    public boolean isNetworkAvailable(Context context) {
       ConnectivityManager cm = (ConnectivityManager) context
               .getSystemService(Context.CONNECTIVITY_SERVICE);
       if (cm != null) {
           NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
           return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
       }
       return false;
    }
    
  3. 使用OkHttp的拦截器处理错误

    public class ErrorInterceptor implements Interceptor {
       @Override
       public Response intercept(Chain chain) throws IOException {
           Request request = chain.request();
           Response response = chain.proceed(request);
    
    
           if (!response.isSuccessful()) {
               // 处理错误响应
               String errorBody = response.body().string();
               throw new IOException("HTTP " + response.code() + ": " + errorBody);
           }
           return response;
       }
    }
    

6.4 布局性能问题

问题描述:布局嵌套过深导致渲染性能下降。

解决方案

  1. 使用ConstraintLayout减少嵌套: “`xml

   <TextView
       app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintStart_toStartOf="parent" />


2. **使用ViewStub延迟加载**:
   ```xml
   <ViewStub
       android:id="@+id/stub"
       android:layout="@layout/complex_layout"
       android:inflatedId="@+id/inflated_view" />
  1. 使用RecyclerView替代ListView
    
    // RecyclerView更高效,支持局部刷新
    RecyclerView recyclerView = findViewById(R.id.recyclerView);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    recyclerView.setAdapter(new MyAdapter(dataList));
    

6.5 权限处理问题

问题描述:Android 6.0+需要动态请求权限。

解决方案

  1. 检查和请求权限

    private void checkPermissions() {
       String[] permissions = {
               Manifest.permission.CAMERA,
               Manifest.permission.WRITE_EXTERNAL_STORAGE
       };
    
    
       List<String> permissionsToRequest = new ArrayList<>();
       for (String permission : permissions) {
           if (ContextCompat.checkSelfPermission(this, permission) 
                   != PackageManager.PERMISSION_GRANTED) {
               permissionsToRequest.add(permission);
           }
       }
    
    
       if (!permissionsToRequest.isEmpty()) {
           ActivityCompat.requestPermissions(this, 
                   permissionsToRequest.toArray(new String[0]), 
                   REQUEST_CODE_PERMISSIONS);
       }
    }
    
  2. 处理权限请求结果

    @Override
    public void onRequestPermissionsResult(int requestCode, 
           String[] permissions, int[] grantResults) {
       super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    
    
       if (requestCode == REQUEST_CODE_PERMISSIONS) {
           boolean allGranted = true;
           for (int result : grantResults) {
               if (result != PackageManager.PERMISSION_GRANTED) {
                   allGranted = false;
                   break;
               }
           }
    
    
           if (allGranted) {
               // 权限已授予,执行相关操作
               startCamera();
           } else {
               // 权限被拒绝,显示提示
               showPermissionDeniedDialog();
           }
       }
    }
    

第七部分:性能优化

7.1 内存优化

  1. 使用LeakCanary检测内存泄漏

    // build.gradle
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
    
  2. 优化图片加载

    // 使用Glide加载图片
    Glide.with(context)
       .load(imageUrl)
       .override(200, 200) // 限制尺寸
       .centerCrop()
       .into(imageView);
    

7.2 网络优化

  1. 使用缓存

    // 添加缓存拦截器
    public class CacheInterceptor implements Interceptor {
       @Override
       public Response intercept(Chain chain) throws IOException {
           Request request = chain.request();
           Response response = chain.proceed(request);
    
    
           // 缓存策略
           return response.newBuilder()
                   .header("Cache-Control", "public, max-age=60")
                   .removeHeader("Pragma")
                   .build();
       }
    }
    
  2. 使用HTTP/2

    // OkHttp默认支持HTTP/2,确保使用最新版本
    implementation 'com.squareup.okhttp3:okhttp:4.9.1'
    

7.3 UI优化

  1. 使用ViewHolder模式

    public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
       private List<String> dataList;
    
    
       public static class ViewHolder extends RecyclerView.ViewHolder {
           TextView textView;
    
    
           public ViewHolder(View itemView) {
               super(itemView);
               textView = itemView.findViewById(R.id.textView);
           }
       }
    
    
       @Override
       public void onBindViewHolder(ViewHolder holder, int position) {
           holder.textView.setText(dataList.get(position));
       }
    }
    
  2. 使用DiffUtil优化列表更新

    // 在Adapter中使用DiffUtil
    public void updateData(List<String> newData) {
       DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
           new MyDiffCallback(dataList, newData));
       dataList = newData;
       diffResult.dispatchUpdatesTo(this);
    }
    

第八部分:实战项目:天气应用

8.1 项目概述

我们将创建一个简单的天气应用,展示从网络获取的天气数据。

8.2 项目结构

WeatherApp/
├── app/
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/com/example/weatherapp/
│   │   │   │   ├── MainActivity.java
│   │   │   │   ├── WeatherAdapter.java
│   │   │   │   ├── WeatherData.java
│   │   │   │   ├── WeatherApi.java
│   │   │   │   └── RetrofitClient.java
│   │   │   ├── res/
│   │   │   │   ├── layout/
│   │   │   │   │   ├── activity_main.xml
│   │   │   │   │   └── item_weather.xml
│   │   │   │   └── values/
│   │   │   │       └── strings.xml
│   │   │   └── AndroidManifest.xml
│   │   └── test/
│   └── build.gradle
└── build.gradle

8.3 实现步骤

8.3.1 添加依赖

build.gradle (Module: app):

dependencies {
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
    
    // Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    
    // RecyclerView
    implementation 'androidx.recyclerview:recyclerview:1.2.1'
    
    // Glide for image loading
    implementation 'com.github.bumptech.glide:glide:4.12.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
}

8.3.2 创建数据模型

WeatherData.java:

package com.example.weatherapp;

import com.google.gson.annotations.SerializedName;
import java.util.List;

public class WeatherData {
    @SerializedName("main")
    private Main main;
    
    @SerializedName("weather")
    private List<Weather> weather;
    
    @SerializedName("name")
    private String name;
    
    @SerializedName("dt")
    private long dt;

    public Main getMain() { return main; }
    public List<Weather> getWeather() { return weather; }
    public String getName() { return name; }
    public long getDt() { return dt; }

    public static class Main {
        @SerializedName("temp")
        private double temp;
        
        @SerializedName("humidity")
        private int humidity;
        
        @SerializedName("pressure")
        private int pressure;

        public double getTemp() { return temp; }
        public int getHumidity() { return humidity; }
        public int getPressure() { return pressure; }
    }

    public static class Weather {
        @SerializedName("description")
        private String description;
        
        @SerializedName("icon")
        private String icon;

        public String getDescription() { return description; }
        public String getIcon() { return icon; }
    }
}

8.3.3 创建API接口

WeatherApi.java:

package com.example.weatherapp;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

public interface WeatherApi {
    @GET("weather")
    Call<WeatherData> getCurrentWeather(
            @Query("q") String city,
            @Query("appid") String apiKey,
            @Query("units") String units
    );
}

8.3.4 创建Retrofit客户端

RetrofitClient.java:

package com.example.weatherapp;

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitClient {
    private static final String BASE_URL = "https://api.openweathermap.org/data/2.5/";
    private static Retrofit retrofit = null;

    public static Retrofit getClient() {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

8.3.5 创建适配器

WeatherAdapter.java:

package com.example.weatherapp;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import java.util.List;

public class WeatherAdapter extends RecyclerView.Adapter<WeatherAdapter.ViewHolder> {
    private List<WeatherData> weatherDataList;

    public WeatherAdapter(List<WeatherData> weatherDataList) {
        this.weatherDataList = weatherDataList;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_weather, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        WeatherData data = weatherDataList.get(position);
        
        holder.cityName.setText(data.getName());
        holder.temperature.setText(String.format("%.1f°C", data.getMain().getTemp()));
        holder.description.setText(data.getWeather().get(0).getDescription());
        
        // 加载天气图标
        String iconUrl = "https://openweathermap.org/img/wn/" + 
                data.getWeather().get(0).getIcon() + "@2x.png";
        Glide.with(holder.itemView.getContext())
                .load(iconUrl)
                .into(holder.weatherIcon);
    }

    @Override
    public int getItemCount() {
        return weatherDataList.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        TextView cityName;
        TextView temperature;
        TextView description;
        ImageView weatherIcon;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            cityName = itemView.findViewById(R.id.cityName);
            temperature = itemView.findViewById(R.id.temperature);
            description = itemView.findViewById(R.id.description);
            weatherIcon = itemView.findViewById(R.id.weatherIcon);
        }
    }
}

8.3.6 创建布局文件

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/etCity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="输入城市名称"
        android:padding="16dp"
        android:layout_margin="16dp" />

    <Button
        android:id="@+id/btnSearch"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="查询天气"
        android:layout_marginHorizontal="16dp" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_marginTop="16dp" />

</LinearLayout>

item_weather.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 
    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="wrap_content"
    android:layout_margin="8dp"
    app:cardCornerRadius="8dp"
    app:cardElevation="4dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="16dp">

        <ImageView
            android:id="@+id/weatherIcon"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:src="@mipmap/ic_launcher" />

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical"
            android:layout_marginStart="16dp">

            <TextView
                android:id="@+id/cityName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="城市名称"
                android:textSize="18sp"
                android:textStyle="bold" />

            <TextView
                android:id="@+id/temperature"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="25°C"
                android:textSize="16sp" />

            <TextView
                android:id="@+id/description"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="晴天"
                android:textSize="14sp" />
        </LinearLayout>
    </LinearLayout>
</androidx.cardview.widget.CardView>

8.3.7 实现MainActivity

MainActivity.java:

package com.example.weatherapp;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity {
    private EditText etCity;
    private Button btnSearch;
    private RecyclerView recyclerView;
    private WeatherAdapter adapter;
    private List<WeatherData> weatherDataList;
    private static final String API_KEY = "YOUR_API_KEY"; // 替换为你的API密钥

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        initViews();
        setupRecyclerView();
        setupListeners();
    }

    private void initViews() {
        etCity = findViewById(R.id.etCity);
        btnSearch = findViewById(R.id.btnSearch);
        recyclerView = findViewById(R.id.recyclerView);
    }

    private void setupRecyclerView() {
        weatherDataList = new ArrayList<>();
        adapter = new WeatherAdapter(weatherDataList);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(adapter);
    }

    private void setupListeners() {
        btnSearch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String city = etCity.getText().toString().trim();
                if (city.isEmpty()) {
                    Toast.makeText(MainActivity.this, "请输入城市名称", Toast.LENGTH_SHORT).show();
                    return;
                }
                fetchWeatherData(city);
            }
        });
    }

    private void fetchWeatherData(String city) {
        WeatherApi api = RetrofitClient.getClient().create(WeatherApi.class);
        Call<WeatherData> call = api.getCurrentWeather(city, API_KEY, "metric");
        
        call.enqueue(new Callback<WeatherData>() {
            @Override
            public void onResponse(Call<WeatherData> call, Response<WeatherData> response) {
                if (response.isSuccessful() && response.body() != null) {
                    WeatherData data = response.body();
                    weatherDataList.add(data);
                    adapter.notifyItemInserted(weatherDataList.size() - 1);
                    recyclerView.scrollToPosition(weatherDataList.size() - 1);
                } else {
                    Toast.makeText(MainActivity.this, 
                            "查询失败: " + response.code(), Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onFailure(Call<WeatherData> call, Throwable t) {
                Toast.makeText(MainActivity.this, 
                        "网络错误: " + t.getMessage(), Toast.LENGTH_SHORT).show();
            }
        });
    }
}

8.3.8 添加网络权限

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.weatherapp">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
        
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

8.4 项目优化

8.4.1 添加加载状态

修改MainActivity:

private void fetchWeatherData(String city) {
    // 显示加载状态
    showLoading();
    
    WeatherApi api = RetrofitClient.getClient().create(WeatherApi.class);
    Call<WeatherData> call = api.getCurrentWeather(city, API_KEY, "metric");
    
    call.enqueue(new Callback<WeatherData>() {
        @Override
        public void onResponse(Call<WeatherData> call, Response<WeatherData> response) {
            hideLoading();
            if (response.isSuccessful() && response.body() != null) {
                WeatherData data = response.body();
                weatherDataList.add(data);
                adapter.notifyItemInserted(weatherDataList.size() - 1);
                recyclerView.scrollToPosition(weatherDataList.size() - 1);
            } else {
                Toast.makeText(MainActivity.this, 
                        "查询失败: " + response.code(), Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public void onFailure(Call<WeatherData> call, Throwable t) {
            hideLoading();
            Toast.makeText(MainActivity.this, 
                    "网络错误: " + t.getMessage(), Toast.LENGTH_SHORT).show();
        }
    });
}

private void showLoading() {
    // 显示加载进度条
    // 可以使用ProgressBar或自定义加载视图
}

private void hideLoading() {
    // 隐藏加载进度条
}

8.4.2 添加错误处理

创建错误处理工具类:

package com.example.weatherapp;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.widget.Toast;

public class NetworkUtils {
    public static boolean isNetworkAvailable(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        if (cm != null) {
            NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
            return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
        }
        return false;
    }

    public static void showNetworkError(Context context) {
        Toast.makeText(context, "网络不可用,请检查网络连接", Toast.LENGTH_SHORT).show();
    }
}

修改MainActivity:

private void fetchWeatherData(String city) {
    if (!NetworkUtils.isNetworkAvailable(this)) {
        NetworkUtils.showNetworkError(this);
        return;
    }
    
    // 继续执行网络请求...
}

第九部分:进阶主题

9.1 Jetpack组件

9.1.1 ViewModel

ViewModel用于管理UI相关的数据,生命周期更长。

示例:使用ViewModel管理天气数据:

package com.example.weatherapp;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;

public class WeatherViewModel extends ViewModel {
    private MutableLiveData<List<WeatherData>> weatherDataList = new MutableLiveData<>();
    private MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
    private MutableLiveData<String> errorMessage = new MutableLiveData<>();

    public LiveData<List<WeatherData>> getWeatherData() {
        return weatherDataList;
    }

    public LiveData<Boolean> getIsLoading() {
        return isLoading;
    }

    public LiveData<String> getErrorMessage() {
        return errorMessage;
    }

    public void fetchWeatherData(String city, String apiKey) {
        isLoading.setValue(true);
        
        // 网络请求逻辑
        // ...
        
        isLoading.setValue(false);
    }
}

9.1.2 LiveData

LiveData是可观察的数据持有者,具有生命周期感知能力。

示例:在Activity中使用LiveData:

public class MainActivity extends AppCompatActivity {
    private WeatherViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        viewModel = new ViewModelProvider(this).get(WeatherViewModel.class);
        
        // 观察数据变化
        viewModel.getWeatherData().observe(this, weatherDataList -> {
            if (weatherDataList != null) {
                adapter.setData(weatherDataList);
            }
        });
        
        viewModel.getIsLoading().observe(this, isLoading -> {
            if (isLoading) {
                showLoading();
            } else {
                hideLoading();
            }
        });
        
        viewModel.getErrorMessage().observe(this, errorMessage -> {
            if (errorMessage != null && !errorMessage.isEmpty()) {
                Toast.makeText(this, errorMessage, Toast.LENGTH_SHORT).show();
            }
        });
    }
}

9.2 Kotlin协程

Kotlin协程是Android开发中处理异步操作的推荐方式。

示例:使用协程进行网络请求

添加依赖:

dependencies {
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0'
}

修改WeatherApi接口:

interface WeatherApi {
    @GET("weather")
    suspend fun getCurrentWeather(
        @Query("q") city: String,
        @Query("appid") apiKey: String,
        @Query("units") units: String
    ): WeatherData
}

修改MainActivity为Kotlin:

class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: WeatherViewModel
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        viewModel = ViewModelProvider(this).get(WeatherViewModel::class.java)
        
        // 使用协程进行网络请求
        lifecycleScope.launch {
            try {
                val weatherData = withContext(Dispatchers.IO) {
                    // 在IO线程执行网络请求
                    api.getCurrentWeather(city, API_KEY, "metric")
                }
                // 在主线程更新UI
                updateUI(weatherData)
            } catch (e: Exception) {
                // 处理错误
                showError(e.message)
            }
        }
    }
}

9.3 依赖注入

9.3.1 Dagger Hilt

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

添加依赖:

// build.gradle (Project)
buildscript {
    dependencies {
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.38.1'
    }
}

// build.gradle (Module: app)
plugins {
    id 'dagger.hilt.android.plugin'
}

dependencies {
    implementation 'com.google.dagger:hilt-android:2.38.1'
    annotationProcessor 'com.google.dagger:hilt-compiler:2.38.1'
}

创建Application类:

@HiltAndroidApp
public class MyApp extends Application {
}

创建Module:

@Module
@InstallIn(SingletonComponent.class)
public class NetworkModule {
    @Provides
    @Singleton
    public Retrofit provideRetrofit() {
        return new Retrofit.Builder()
                .baseUrl("https://api.openweathermap.org/data/2.5/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }
    
    @Provides
    @Singleton
    public WeatherApi provideWeatherApi(Retrofit retrofit) {
        return retrofit.create(WeatherApi.class);
    }
}

在Activity中使用:

@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
    @Inject
    WeatherApi weatherApi;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 现在可以直接使用weatherApi
    }
}

第十部分:发布与测试

10.1 单元测试

示例:测试UserDatabaseHelper

UserDatabaseHelperTest.java:

package com.example.helloworld;

import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import static org.junit.Assert.*;

@RunWith(AndroidJUnit4.class)
public class UserDatabaseHelperTest {
    private UserDatabaseHelper dbHelper;
    private Context context;

    @Before
    public void setUp() {
        context = ApplicationProvider.getApplicationContext();
        dbHelper = new UserDatabaseHelper(context);
    }

    @After
    public void tearDown() {
        dbHelper.close();
    }

    @Test
    public void testAddUser() {
        User user = new User(0, "Test User", "test@example.com", 25);
        dbHelper.addUser(user);
        
        List<User> users = dbHelper.getAllUsers();
        assertFalse(users.isEmpty());
        assertEquals("Test User", users.get(0).getName());
    }

    @Test
    public void testGetUser() {
        User user = new User(0, "Test User", "test@example.com", 25);
        dbHelper.addUser(user);
        
        User retrievedUser = dbHelper.getUser(1);
        assertNotNull(retrievedUser);
        assertEquals("Test User", retrievedUser.getName());
    }

    @Test
    public void testUpdateUser() {
        User user = new User(0, "Test User", "test@example.com", 25);
        dbHelper.addUser(user);
        
        user.setName("Updated User");
        user.setEmail("updated@example.com");
        user.setAge(30);
        
        int rowsAffected = dbHelper.updateUser(user);
        assertEquals(1, rowsAffected);
        
        User updatedUser = dbHelper.getUser(1);
        assertEquals("Updated User", updatedUser.getName());
        assertEquals("updated@example.com", updatedUser.getEmail());
        assertEquals(30, updatedUser.getAge());
    }

    @Test
    public void testDeleteUser() {
        User user = new User(0, "Test User", "test@example.com", 25);
        dbHelper.addUser(user);
        
        dbHelper.deleteUser(1);
        
        User deletedUser = dbHelper.getUser(1);
        assertNull(deletedUser);
    }
}

10.2 UI测试

示例:使用Espresso测试登录界面

LoginActivityTest.java:

package com.example.helloworld;

import androidx.test.core.app.ActivityScenario;
import androidx.test.espresso.Espresso;
import androidx.test.espresso.action.ViewActions;
import androidx.test.espresso.assertion.ViewAssertions;
import androidx.test.espresso.matcher.ViewMatchers;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

@RunWith(AndroidJUnit4.class)
public class LoginActivityTest {

    @Test
    public void testLoginSuccess() {
        ActivityScenario<LoginActivity> scenario = ActivityScenario.launch(LoginActivity.class);
        
        // 输入用户名
        onView(withId(R.id.etUsername))
                .perform(ViewActions.typeText("testuser"));
        
        // 输入密码
        onView(withId(R.id.etPassword))
                .perform(ViewActions.typeText("password123"));
        
        // 点击登录按钮
        onView(withId(R.id.btnLogin))
                .perform(ViewActions.click());
        
        // 验证登录成功后的界面
        onView(withText("登录成功"))
                .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
        
        scenario.close();
    }

    @Test
    public void testEmptyFields() {
        ActivityScenario<LoginActivity> scenario = ActivityScenario.launch(LoginActivity.class);
        
        // 直接点击登录按钮
        onView(withId(R.id.btnLogin))
                .perform(ViewActions.click());
        
        // 验证错误提示
        onView(withText("用户名不能为空"))
                .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
        
        scenario.close();
    }
}

10.3 应用发布

10.3.1 生成签名密钥

# 使用keytool生成签名密钥
keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000

10.3.2 配置签名

gradle.properties:

MYAPP_RELEASE_STORE_FILE=my-release-key.keystore
MYAPP_RELEASE_KEY_ALIAS=my-key-alias
MYAPP_RELEASE_STORE_PASSWORD=your_password
MYAPP_RELEASE_KEY_PASSWORD=your_password

build.gradle (Module: app):

android {
    signingConfigs {
        release {
            storeFile file(MYAPP_RELEASE_STORE_FILE)
            storePassword MYAPP_RELEASE_STORE_PASSWORD
            keyAlias MYAPP_RELEASE_KEY_ALIAS
            keyPassword MYAPP_RELEASE_KEY_PASSWORD
        }
    }
    
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

10.3.3 生成APK/AAB

  1. 在Android Studio中,点击”Build” → “Generate Signed Bundle / APK”
  2. 选择”Android App Bundle”(推荐)或”APK”
  3. 选择签名配置
  4. 点击”Finish”生成发布包

总结

本文通过详细的实例分析和实战指南,从零开始介绍了Android开发的各个方面。我们涵盖了环境搭建、UI设计、数据存储、网络请求、常见问题解决方案、性能优化、实战项目以及进阶主题。通过学习和实践这些内容,你可以逐步掌握Android开发的核心技能,并能够独立开发功能完整的Android应用。

记住,Android开发是一个持续学习的过程。随着技术的不断发展,新的工具和框架不断涌现。保持好奇心,不断实践,参与开源项目,阅读官方文档,都是提升技能的好方法。祝你在Android开发的道路上取得成功!