引言
Android作为全球最流行的移动操作系统,拥有超过25亿活跃设备。对于初学者来说,掌握Android开发不仅能打开移动应用开发的大门,还能创造巨大的职业机会。本指南将从零基础开始,通过详细的实例分析,带你一步步构建完整的实战项目。
第一部分:Android开发基础
1.1 Android开发环境搭建
Android Studio是Google官方推荐的集成开发环境(IDE)。安装步骤如下:
- 下载安装:访问Android开发者官网下载最新版本
- 配置SDK:首次启动时,Android Studio会引导你安装Android SDK和必要的工具
- 创建虚拟设备:通过AVD Manager创建模拟器,建议选择Pixel系列设备进行测试
# 验证安装是否成功
adb devices
# 应该显示连接的设备或模拟器列表
1.2 Android项目结构解析
创建一个新项目后,你会看到以下主要目录:
MyApp/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/ # Java/Kotlin源代码
│ │ │ ├── res/ # 资源文件
│ │ │ │ ├── layout/ # XML布局文件
│ │ │ │ ├── values/ # 字符串、颜色等
│ │ │ │ └── drawable/ # 图片资源
│ │ │ └── AndroidManifest.xml # 应用配置文件
│ │ └── test/ # 单元测试
│ └── build.gradle # 模块级构建配置
├── build.gradle # 项目级构建配置
└── settings.gradle # 项目设置
1.3 第一个Android应用:Hello World
让我们创建一个简单的”Hello World”应用来理解基本结构:
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 textView = findViewById(R.id.textView);
textView.setText("Hello, Android Developer!");
}
}
activity_main.xml
<?xml version="1.0" 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:textSize="24sp"
android:textColor="#333333"
android:text="Hello World!" />
</LinearLayout>
第二部分:核心组件与UI开发
2.1 Activity生命周期详解
Activity是Android应用的基本组件,理解其生命周期至关重要:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@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被销毁");
}
}
生命周期图解:
onCreate() → onStart() → onResume() → [运行状态] → onPause() → onStop() → onDestroy()
2.2 常用UI组件与布局
2.2.1 布局管理器
LinearLayout(线性布局)
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="按钮1" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="按钮2" />
</LinearLayout>
RelativeLayout(相对布局)
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标题"
android:layout_centerHorizontal="true" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确定"
android:layout_below="@id/title"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp" />
</RelativeLayout>
ConstraintLayout(约束布局)
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Me"
app:layout_constraintTop_toBottomOf="@id/textView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
2.2.2 常用控件
Button与点击事件
// 在Activity中
Button button = findViewById(R.id.myButton);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "按钮被点击了!", Toast.LENGTH_SHORT).show();
}
});
// 使用Lambda表达式(Java 8+)
button.setOnClickListener(v -> {
Toast.makeText(MainActivity.this, "按钮被点击了!", Toast.LENGTH_SHORT).show();
});
EditText与输入验证
EditText editText = findViewById(R.id.editText);
Button submitButton = findViewById(R.id.submitButton);
submitButton.setOnClickListener(v -> {
String input = editText.getText().toString().trim();
if (input.isEmpty()) {
editText.setError("请输入内容");
return;
}
if (input.length() < 3) {
editText.setError("至少需要3个字符");
return;
}
// 处理有效输入
Toast.makeText(this, "输入有效: " + input, Toast.LENGTH_SHORT).show();
});
Spinner(下拉选择)
Spinner spinner = findViewById(R.id.spinner);
String[] items = {"选项1", "选项2", "选项3", "选项4"};
ArrayAdapter<String> adapter = new ArrayAdapter<>(
this,
android.R.layout.simple_spinner_item,
items
);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String selected = items[position];
Toast.makeText(MainActivity.this, "选择了: " + selected, Toast.LENGTH_SHORT).show();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// 未选择任何项
}
});
2.3 RecyclerView:高效列表展示
RecyclerView是现代Android开发中展示列表数据的首选组件。
1. 添加依赖
// app/build.gradle
dependencies {
implementation 'androidx.recyclerview:recyclerview:1.3.2'
}
2. 创建列表项布局
<!-- item_user.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:id="@+id/avatar"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/ic_user" />
<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/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#666666" />
</LinearLayout>
</LinearLayout>
3. 创建数据模型
public class User {
private String name;
private String email;
private int avatarResId;
public User(String name, String email, int avatarResId) {
this.name = name;
this.email = email;
this.avatarResId = avatarResId;
}
// Getters and Setters
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 getAvatarResId() { return avatarResId; }
public void setAvatarResId(int avatarResId) { this.avatarResId = avatarResId; }
}
4. 创建Adapter
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHolder> {
private List<User> userList;
private OnItemClickListener listener;
public interface OnItemClickListener {
void onItemClick(User user);
}
public UserAdapter(List<User> userList, OnItemClickListener listener) {
this.userList = userList;
this.listener = listener;
}
@NonNull
@Override
public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_user, parent, false);
return new UserViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
User user = userList.get(position);
holder.name.setText(user.getName());
holder.email.setText(user.getEmail());
holder.avatar.setImageResource(user.getAvatarResId());
// 设置点击事件
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onItemClick(user);
}
});
}
@Override
public int getItemCount() {
return userList.size();
}
// 更新数据的方法
public void updateData(List<User> newUsers) {
userList = newUsers;
notifyDataSetChanged();
}
// ViewHolder内部类
static class UserViewHolder extends RecyclerView.ViewHolder {
TextView name, email;
ImageView avatar;
public UserViewHolder(@NonNull View itemView) {
super(itemView);
name = itemView.findViewById(R.id.name);
email = itemView.findViewById(R.id.email);
avatar = itemView.findViewById(R.id.avatar);
}
}
}
5. 在Activity中使用
public class UserListActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private UserAdapter adapter;
private List<User> userList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
// 初始化数据
initData();
// 创建并设置Adapter
adapter = new UserAdapter(userList, user -> {
// 处理点击事件
Toast.makeText(this, "点击了: " + user.getName(), Toast.LENGTH_SHORT).show();
});
recyclerView.setAdapter(adapter);
}
private void initData() {
userList.add(new User("张三", "zhangsan@example.com", R.drawable.ic_user));
userList.add(new User("李四", "lisi@example.com", R.drawable.ic_user));
userList.add(new User("王五", "wangwu@example.com", R.drawable.ic_user));
userList.add(new User("赵六", "zhaoliu@example.com", R.drawable.ic_user));
}
}
第三部分:数据存储与网络通信
3.1 SharedPreferences:轻量级数据存储
SharedPreferences适合存储简单的键值对数据,如用户设置、登录状态等。
1. 保存数据
public class SharedPreferencesHelper {
private static final String PREF_NAME = "MyAppPrefs";
private static final String KEY_USERNAME = "username";
private static final String KEY_IS_LOGGED_IN = "is_logged_in";
public static void saveUsername(Context context, String username) {
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(KEY_USERNAME, username);
editor.apply(); // 异步提交
}
public static void saveLoginStatus(Context context, boolean isLoggedIn) {
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(KEY_IS_LOGGED_IN, isLoggedIn);
editor.apply();
}
}
2. 读取数据
public static String getUsername(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
return prefs.getString(KEY_USERNAME, "默认用户");
}
public static boolean isLoggedIn(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
return prefs.getBoolean(KEY_IS_LOGGED_IN, false);
}
3. 删除数据
public static void clearUserData(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.remove(KEY_USERNAME);
editor.remove(KEY_IS_LOGGED_IN);
editor.apply();
}
3.2 SQLite数据库:结构化数据存储
SQLite是Android内置的轻量级关系型数据库,适合存储结构化数据。
1. 创建数据库帮助类
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "MyApp.db";
private static final int DATABASE_VERSION = 1;
// 表名和列名
public static final String TABLE_USERS = "users";
public static final String COLUMN_ID = "id";
public static final String COLUMN_NAME = "name";
public static final String COLUMN_EMAIL = "email";
public static final String COLUMN_AGE = "age";
// 创建表的SQL语句
private static final String CREATE_TABLE_USERS =
"CREATE TABLE " + TABLE_USERS + " (" +
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
COLUMN_NAME + " TEXT NOT NULL, " +
COLUMN_EMAIL + " TEXT UNIQUE, " +
COLUMN_AGE + " INTEGER)";
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_USERS);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 数据库升级时的处理
db.execSQL("DROP TABLE IF EXISTS " + TABLE_USERS);
onCreate(db);
}
}
2. 数据操作类
public class UserRepository {
private DatabaseHelper dbHelper;
public UserRepository(Context context) {
dbHelper = new DatabaseHelper(context);
}
// 插入用户
public long insertUser(String name, String email, int age) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(DatabaseHelper.COLUMN_NAME, name);
values.put(DatabaseHelper.COLUMN_EMAIL, email);
values.put(DatabaseHelper.COLUMN_AGE, age);
long id = db.insert(DatabaseHelper.TABLE_USERS, null, values);
db.close();
return id;
}
// 查询所有用户
public List<User> getAllUsers() {
List<User> users = new ArrayList<>();
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query(
DatabaseHelper.TABLE_USERS,
null, // 所有列
null, // WHERE条件
null, // WHERE参数
null, // GROUP BY
null, // HAVING
DatabaseHelper.COLUMN_NAME + " ASC" // ORDER BY
);
if (cursor != null && cursor.moveToFirst()) {
do {
int id = cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_ID));
String name = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_NAME));
String email = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_EMAIL));
int age = cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_AGE));
User user = new User(name, email, R.drawable.ic_user);
user.setId(id);
user.setAge(age);
users.add(user);
} while (cursor.moveToNext());
cursor.close();
}
db.close();
return users;
}
// 根据ID查询用户
public User getUserById(int id) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query(
DatabaseHelper.TABLE_USERS,
null,
DatabaseHelper.COLUMN_ID + " = ?",
new String[]{String.valueOf(id)},
null, null, null
);
User user = null;
if (cursor != null && cursor.moveToFirst()) {
String name = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_NAME));
String email = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_EMAIL));
int age = cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_AGE));
user = new User(name, email, R.drawable.ic_user);
user.setId(id);
user.setAge(age);
cursor.close();
}
db.close();
return user;
}
// 更新用户
public int updateUser(int id, String name, String email, int age) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(DatabaseHelper.COLUMN_NAME, name);
values.put(DatabaseHelper.COLUMN_EMAIL, email);
values.put(DatabaseHelper.COLUMN_AGE, age);
int rowsAffected = db.update(
DatabaseHelper.TABLE_USERS,
values,
DatabaseHelper.COLUMN_ID + " = ?",
new String[]{String.valueOf(id)}
);
db.close();
return rowsAffected;
}
// 删除用户
public int deleteUser(int id) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int rowsDeleted = db.delete(
DatabaseHelper.TABLE_USERS,
DatabaseHelper.COLUMN_ID + " = ?",
new String[]{String.valueOf(id)}
);
db.close();
return rowsDeleted;
}
}
3.3 Retrofit:网络请求与API集成
Retrofit是Square公司开发的HTTP客户端,是Android网络请求的首选库。
1. 添加依赖
// app/build.gradle
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
}
2. 创建数据模型
// 用户数据模型
public class UserResponse {
private int id;
private String name;
private String email;
private String phone;
private Address address;
private Company company;
// 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 String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
public Company getCompany() { return company; }
public void setCompany(Company company) { this.company = company; }
// 内部类
public static class Address {
private String street;
private String suite;
private String city;
private String zipcode;
private Geo geo;
// Getters and Setters
public String getStreet() { return street; }
public void setStreet(String street) { this.street = street; }
public String getSuite() { return suite; }
public void setSuite(String suite) { this.suite = suite; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getZipcode() { return zipcode; }
public void setZipcode(String zipcode) { this.zipcode = zipcode; }
public Geo getGeo() { return geo; }
public void setGeo(Geo geo) { this.geo = geo; }
public static class Geo {
private String lat;
private String lng;
// Getters and Setters
public String getLat() { return lat; }
public void setLat(String lat) { this.lat = lat; }
public String getLng() { return lng; }
public void setLng(String lng) { this.lng = lng; }
}
}
public static class Company {
private String name;
private String catchPhrase;
private String bs;
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getCatchPhrase() { return catchPhrase; }
public void setCatchPhrase(String catchPhrase) { this.catchPhrase = catchPhrase; }
public String getBs() { return bs; }
public void setBs(String bs) { this.bs = bs; }
}
}
3. 创建API接口
public interface ApiService {
// 获取所有用户
@GET("users")
Call<List<UserResponse>> getUsers();
// 根据ID获取用户
@GET("users/{id}")
Call<UserResponse> getUserById(@Path("id") int id);
// 创建用户
@POST("users")
Call<UserResponse> createUser(@Body UserResponse user);
// 更新用户
@PUT("users/{id}")
Call<UserResponse> updateUser(@Path("id") int id, @Body UserResponse user);
// 删除用户
@DELETE("users/{id}")
Call<Void> deleteUser(@Path("id") int id);
// 带查询参数的请求
@GET("users")
Call<List<UserResponse>> getUsersByCity(@Query("city") String city);
// 带多个查询参数
@GET("users")
Call<List<UserResponse>> getUsersByCityAndName(
@Query("city") String city,
@Query("name") String name
);
}
4. 创建Retrofit客户端
public class RetrofitClient {
private static final String BASE_URL = "https://jsonplaceholder.typicode.com/";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit == null) {
// 创建OkHttp拦截器用于日志
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)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
public static ApiService getApiService() {
return getClient().create(ApiService.class);
}
}
5. 在Activity中使用
public class NetworkActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private UserAdapter adapter;
private List<UserResponse> userList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_network);
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
fetchUsers();
}
private void fetchUsers() {
ApiService apiService = RetrofitClient.getApiService();
Call<List<UserResponse>> call = apiService.getUsers();
call.enqueue(new Callback<List<UserResponse>>() {
@Override
public void onResponse(Call<List<UserResponse>> call, Response<List<UserResponse>> response) {
if (response.isSuccessful() && response.body() != null) {
userList = response.body();
updateUI();
} else {
Toast.makeText(NetworkActivity.this,
"请求失败: " + response.code(), Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<List<UserResponse>> call, Throwable t) {
Toast.makeText(NetworkActivity.this,
"网络错误: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
private void updateUI() {
// 转换为本地User对象
List<User> localUsers = new ArrayList<>();
for (UserResponse response : userList) {
User user = new User(response.getName(), response.getEmail(), R.drawable.ic_user);
user.setId(response.getId());
localUsers.add(user);
}
adapter = new UserAdapter(localUsers, user -> {
// 处理点击事件
showUserDetails(user);
});
recyclerView.setAdapter(adapter);
}
private void showUserDetails(User user) {
// 显示用户详情
Intent intent = new Intent(this, UserDetailActivity.class);
intent.putExtra("user_id", user.getId());
startActivity(intent);
}
}
第四部分:实战项目:用户管理应用
4.1 项目概述
我们将构建一个完整的用户管理应用,包含以下功能:
- 用户列表展示(RecyclerView)
- 用户详情查看
- 添加新用户
- 编辑现有用户
- 删除用户
- 数据持久化(SQLite)
- 网络同步(可选)
4.2 项目架构设计
MVC架构模式:
Model: 数据层(User, UserRepository)
View: 视图层(Activity, Fragment, XML布局)
Controller: 控制层(Activity/Fragment中的业务逻辑)
项目结构:
app/
├── src/main/java/com/example/usermanager/
│ ├── model/
│ │ └── User.java
│ ├── repository/
│ │ └── UserRepository.java
│ ├── database/
│ │ └── DatabaseHelper.java
│ ├── network/
│ │ ├── ApiService.java
│ │ └── RetrofitClient.java
│ ├── ui/
│ │ ├── activity/
│ │ │ ├── MainActivity.java
│ │ │ ├── UserListActivity.java
│ │ │ ├── UserDetailActivity.java
│ │ │ ├── AddUserActivity.java
│ │ │ └── EditUserActivity.java
│ │ ├── adapter/
│ │ │ └── UserAdapter.java
│ │ └── fragment/
│ │ └── UserListFragment.java
│ └── utils/
│ └── SharedPreferencesHelper.java
└── res/
├── layout/
│ ├── activity_main.xml
│ ├── activity_user_list.xml
│ ├── activity_user_detail.xml
│ ├── activity_add_user.xml
│ ├── activity_edit_user.xml
│ └── item_user.xml
├── values/
│ ├── strings.xml
│ ├── colors.xml
│ └── styles.xml
└── drawable/
└── ic_user.xml
4.3 核心功能实现
4.3.1 用户列表界面
UserListActivity.java
public class UserListActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private UserAdapter adapter;
private UserRepository userRepository;
private List<User> userList = new ArrayList<>();
private FloatingActionButton fabAddUser;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
// 初始化组件
recyclerView = findViewById(R.id.recyclerView);
fabAddUser = findViewById(R.id.fabAddUser);
// 设置RecyclerView
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
// 初始化Repository
userRepository = new UserRepository(this);
// 加载数据
loadUsers();
// 设置添加按钮点击事件
fabAddUser.setOnClickListener(v -> {
Intent intent = new Intent(UserListActivity.this, AddUserActivity.class);
startActivity(intent);
});
}
@Override
protected void onResume() {
super.onResume();
// 每次返回时刷新数据
loadUsers();
}
private void loadUsers() {
// 从数据库获取用户列表
userList = userRepository.getAllUsers();
if (adapter == null) {
adapter = new UserAdapter(userList, user -> {
// 点击用户查看详情
Intent intent = new Intent(UserListActivity.this, UserDetailActivity.class);
intent.putExtra("user_id", user.getId());
startActivity(intent);
});
recyclerView.setAdapter(adapter);
} else {
adapter.updateData(userList);
}
// 显示空状态
if (userList.isEmpty()) {
showEmptyState();
} else {
hideEmptyState();
}
}
private void showEmptyState() {
// 显示空状态视图
View emptyView = findViewById(R.id.emptyView);
emptyView.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
}
private void hideEmptyState() {
// 隐藏空状态视图
View emptyView = findViewById(R.id.emptyView);
emptyView.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}
}
4.3.2 添加用户界面
AddUserActivity.java
public class AddUserActivity extends AppCompatActivity {
private EditText etName, etEmail, etAge;
private Button btnSave;
private UserRepository userRepository;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_user);
// 初始化组件
etName = findViewById(R.id.etName);
etEmail = findViewById(R.id.etEmail);
etAge = findViewById(R.id.etAge);
btnSave = findViewById(R.id.btnSave);
// 初始化Repository
userRepository = new UserRepository(this);
// 设置保存按钮点击事件
btnSave.setOnClickListener(v -> saveUser());
}
private void saveUser() {
String name = etName.getText().toString().trim();
String email = etEmail.getText().toString().trim();
String ageStr = etAge.getText().toString().trim();
// 验证输入
if (validateInput(name, email, ageStr)) {
int age = Integer.parseInt(ageStr);
// 保存到数据库
long id = userRepository.insertUser(name, email, age);
if (id > 0) {
Toast.makeText(this, "用户添加成功", Toast.LENGTH_SHORT).show();
finish(); // 返回上一页
} else {
Toast.makeText(this, "添加失败", Toast.LENGTH_SHORT).show();
}
}
}
private boolean validateInput(String name, String email, String ageStr) {
// 验证姓名
if (name.isEmpty()) {
etName.setError("请输入姓名");
etName.requestFocus();
return false;
}
// 验证邮箱
if (email.isEmpty()) {
etEmail.setError("请输入邮箱");
etEmail.requestFocus();
return false;
}
if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
etEmail.setError("邮箱格式不正确");
etEmail.requestFocus();
return false;
}
// 验证年龄
if (ageStr.isEmpty()) {
etAge.setError("请输入年龄");
etAge.requestFocus();
return false;
}
try {
int age = Integer.parseInt(ageStr);
if (age < 0 || age > 150) {
etAge.setError("年龄必须在0-150之间");
etAge.requestFocus();
return false;
}
} catch (NumberFormatException e) {
etAge.setError("请输入有效的年龄");
etAge.requestFocus();
return false;
}
return true;
}
}
4.3.3 用户详情界面
UserDetailActivity.java
public class UserDetailActivity extends AppCompatActivity {
private TextView tvName, tvEmail, tvAge;
private Button btnEdit, btnDelete;
private UserRepository userRepository;
private int userId;
private User currentUser;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_detail);
// 获取传递的用户ID
userId = getIntent().getIntExtra("user_id", -1);
if (userId == -1) {
Toast.makeText(this, "无效的用户ID", Toast.LENGTH_SHORT).show();
finish();
return;
}
// 初始化组件
tvName = findViewById(R.id.tvName);
tvEmail = findViewById(R.id.tvEmail);
tvAge = findViewById(R.id.tvAge);
btnEdit = findViewById(R.id.btnEdit);
btnDelete = findViewById(R.id.btnDelete);
// 初始化Repository
userRepository = new UserRepository(this);
// 加载用户数据
loadUser();
// 设置按钮点击事件
btnEdit.setOnClickListener(v -> editUser());
btnDelete.setOnClickListener(v -> deleteUser());
}
private void loadUser() {
currentUser = userRepository.getUserById(userId);
if (currentUser != null) {
tvName.setText(currentUser.getName());
tvEmail.setText(currentUser.getEmail());
tvAge.setText(String.valueOf(currentUser.getAge()));
} else {
Toast.makeText(this, "用户不存在", Toast.LENGTH_SHORT).show();
finish();
}
}
private void editUser() {
Intent intent = new Intent(this, EditUserActivity.class);
intent.putExtra("user_id", userId);
startActivity(intent);
}
private void deleteUser() {
new AlertDialog.Builder(this)
.setTitle("确认删除")
.setMessage("确定要删除用户 " + currentUser.getName() + " 吗?")
.setPositiveButton("确定", (dialog, which) -> {
int rowsDeleted = userRepository.deleteUser(userId);
if (rowsDeleted > 0) {
Toast.makeText(this, "用户已删除", Toast.LENGTH_SHORT).show();
finish();
} else {
Toast.makeText(this, "删除失败", Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton("取消", null)
.show();
}
}
4.3.4 编辑用户界面
EditUserActivity.java
public class EditUserActivity extends AppCompatActivity {
private EditText etName, etEmail, etAge;
private Button btnSave;
private UserRepository userRepository;
private int userId;
private User currentUser;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_user);
// 获取传递的用户ID
userId = getIntent().getIntExtra("user_id", -1);
if (userId == -1) {
Toast.makeText(this, "无效的用户ID", Toast.LENGTH_SHORT).show();
finish();
return;
}
// 初始化组件
etName = findViewById(R.id.etName);
etEmail = findViewById(R.id.etEmail);
etAge = findViewById(R.id.etAge);
btnSave = findViewById(R.id.btnSave);
// 初始化Repository
userRepository = new UserRepository(this);
// 加载用户数据
loadUser();
// 设置保存按钮点击事件
btnSave.setOnClickListener(v -> updateUser());
}
private void loadUser() {
currentUser = userRepository.getUserById(userId);
if (currentUser != null) {
etName.setText(currentUser.getName());
etEmail.setText(currentUser.getEmail());
etAge.setText(String.valueOf(currentUser.getAge()));
} else {
Toast.makeText(this, "用户不存在", Toast.LENGTH_SHORT).show();
finish();
}
}
private void updateUser() {
String name = etName.getText().toString().trim();
String email = etEmail.getText().toString().trim();
String ageStr = etAge.getText().toString().trim();
// 验证输入
if (validateInput(name, email, ageStr)) {
int age = Integer.parseInt(ageStr);
// 更新数据库
int rowsAffected = userRepository.updateUser(userId, name, email, age);
if (rowsAffected > 0) {
Toast.makeText(this, "用户更新成功", Toast.LENGTH_SHORT).show();
finish(); // 返回上一页
} else {
Toast.makeText(this, "更新失败", Toast.LENGTH_SHORT).show();
}
}
}
private boolean validateInput(String name, String email, String ageStr) {
// 验证姓名
if (name.isEmpty()) {
etName.setError("请输入姓名");
etName.requestFocus();
return false;
}
// 验证邮箱
if (email.isEmpty()) {
etEmail.setError("请输入邮箱");
etEmail.requestFocus();
return false;
}
if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
etEmail.setError("邮箱格式不正确");
etEmail.requestFocus();
return false;
}
// 验证年龄
if (ageStr.isEmpty()) {
etAge.setError("请输入年龄");
etAge.requestFocus();
return false;
}
try {
int age = Integer.parseInt(ageStr);
if (age < 0 || age > 150) {
etAge.setError("年龄必须在0-150之间");
etAge.requestFocus();
return false;
}
} catch (NumberFormatException e) {
etAge.setError("请输入有效的年龄");
etAge.requestFocus();
return false;
}
return true;
}
}
4.4 项目优化与扩展
4.4.1 添加搜索功能
在UserListActivity中添加搜索功能
public class UserListActivity extends AppCompatActivity {
// ... 其他代码 ...
private SearchView searchView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
// ... 初始化代码 ...
// 设置搜索功能
setupSearch();
}
private void setupSearch() {
searchView = findViewById(R.id.searchView);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
filterUsers(newText);
return true;
}
});
}
private void filterUsers(String query) {
if (query.isEmpty()) {
// 显示所有用户
adapter.updateData(userList);
} else {
// 过滤用户
List<User> filteredList = new ArrayList<>();
for (User user : userList) {
if (user.getName().toLowerCase().contains(query.toLowerCase()) ||
user.getEmail().toLowerCase().contains(query.toLowerCase())) {
filteredList.add(user);
}
}
adapter.updateData(filteredList);
}
}
}
4.4.2 添加分页功能
修改UserRepository添加分页查询
public class UserRepository {
// ... 其他代码 ...
// 分页查询用户
public List<User> getUsersByPage(int page, int pageSize) {
List<User> users = new ArrayList<>();
SQLiteDatabase db = dbHelper.getReadableDatabase();
int offset = (page - 1) * pageSize;
Cursor cursor = db.query(
DatabaseHelper.TABLE_USERS,
null,
null,
null,
null,
null,
DatabaseHelper.COLUMN_NAME + " ASC LIMIT " + pageSize + " OFFSET " + offset
);
if (cursor != null && cursor.moveToFirst()) {
do {
// ... 解析数据 ...
} while (cursor.moveToNext());
cursor.close();
}
db.close();
return users;
}
// 获取总记录数
public int getTotalCount() {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM " + DatabaseHelper.TABLE_USERS, null);
int count = 0;
if (cursor != null && cursor.moveToFirst()) {
count = cursor.getInt(0);
cursor.close();
}
db.close();
return count;
}
}
4.4.3 添加数据验证与错误处理
创建统一的验证工具类
public class ValidationUtils {
public static boolean isValidEmail(String email) {
return Patterns.EMAIL_ADDRESS.matcher(email).matches();
}
public static boolean isValidAge(int age) {
return age >= 0 && age <= 150;
}
public static boolean isValidName(String name) {
return name != null && !name.trim().isEmpty() && name.trim().length() >= 2;
}
public static String validateUserInput(String name, String email, String ageStr) {
if (!isValidName(name)) {
return "姓名至少需要2个字符";
}
if (!isValidEmail(email)) {
return "邮箱格式不正确";
}
try {
int age = Integer.parseInt(ageStr);
if (!isValidAge(age)) {
return "年龄必须在0-150之间";
}
} catch (NumberFormatException e) {
return "请输入有效的年龄";
}
return null; // 验证通过
}
}
第五部分:高级主题与最佳实践
5.1 Fragment的使用
Fragment是构建灵活UI的重要组件,特别适合平板电脑和大屏幕设备。
1. 创建Fragment
public class UserListFragment extends Fragment {
private RecyclerView recyclerView;
private UserAdapter adapter;
private UserRepository userRepository;
private List<User> userList = new ArrayList<>();
private OnUserClickListener listener;
public interface OnUserClickListener {
void onUserClick(User user);
}
public UserListFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_user_list, container, false);
recyclerView = view.findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
userRepository = new UserRepository(getContext());
loadUsers();
return view;
}
private void loadUsers() {
userList = userRepository.getAllUsers();
if (adapter == null) {
adapter = new UserAdapter(userList, user -> {
if (listener != null) {
listener.onUserClick(user);
}
});
recyclerView.setAdapter(adapter);
} else {
adapter.updateData(userList);
}
}
public void setOnUserClickListener(OnUserClickListener listener) {
this.listener = listener;
}
public void refreshData() {
loadUsers();
}
}
2. 在Activity中使用Fragment
public class MainActivity extends AppCompatActivity
implements UserListFragment.OnUserClickListener {
private UserListFragment userListFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 检查是否是平板模式
if (isTabletMode()) {
// 平板模式:同时显示列表和详情
setupTabletLayout();
} else {
// 手机模式:只显示列表
setupPhoneLayout();
}
}
private boolean isTabletMode() {
return findViewById(R.id.detail_container) != null;
}
private void setupPhoneLayout() {
// 手机模式:只显示列表Fragment
userListFragment = new UserListFragment();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, userListFragment)
.commit();
}
private void setupTabletLayout() {
// 平板模式:同时显示列表和详情
userListFragment = new UserListFragment();
userListFragment.setOnUserClickListener(this);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.list_container, userListFragment)
.commit();
}
@Override
public void onUserClick(User user) {
if (isTabletMode()) {
// 平板模式:在右侧显示详情
UserDetailFragment detailFragment = UserDetailFragment.newInstance(user.getId());
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.detail_container, detailFragment)
.commit();
} else {
// 手机模式:启动新的Activity
Intent intent = new Intent(this, UserDetailActivity.class);
intent.putExtra("user_id", user.getId());
startActivity(intent);
}
}
}
5.2 ViewModel与LiveData(MVVM架构)
1. 添加依赖
// app/build.gradle
dependencies {
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.6.2'
implementation 'androidx.lifecycle:lifecycle-livedata:2.6.2'
implementation 'androidx.lifecycle:lifecycle-runtime:2.6.2'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
}
2. 创建ViewModel
public class UserViewModel extends ViewModel {
private UserRepository userRepository;
private MutableLiveData<List<User>> userListLiveData = new MutableLiveData<>();
private MutableLiveData<User> userLiveData = new MutableLiveData<>();
private MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
private MutableLiveData<String> errorMessage = new MutableLiveData<>();
public UserViewModel(Application application) {
userRepository = new UserRepository(application);
}
public LiveData<List<User>> getUserList() {
return userListLiveData;
}
public LiveData<User> getUser() {
return userLiveData;
}
public LiveData<Boolean> getIsLoading() {
return isLoading;
}
public LiveData<String> getErrorMessage() {
return errorMessage;
}
public void loadUsers() {
isLoading.setValue(true);
// 在后台线程执行数据库查询
new Thread(() -> {
try {
List<User> users = userRepository.getAllUsers();
userListLiveData.postValue(users);
} catch (Exception e) {
errorMessage.postValue("加载失败: " + e.getMessage());
} finally {
isLoading.postValue(false);
}
}).start();
}
public void loadUser(int userId) {
isLoading.setValue(true);
new Thread(() -> {
try {
User user = userRepository.getUserById(userId);
if (user != null) {
userLiveData.postValue(user);
} else {
errorMessage.postValue("用户不存在");
}
} catch (Exception e) {
errorMessage.postValue("加载失败: " + e.getMessage());
} finally {
isLoading.postValue(false);
}
}).start();
}
public void addUser(String name, String email, int age) {
isLoading.setValue(true);
new Thread(() -> {
try {
long id = userRepository.insertUser(name, email, age);
if (id > 0) {
// 重新加载列表
loadUsers();
} else {
errorMessage.postValue("添加失败");
}
} catch (Exception e) {
errorMessage.postValue("添加失败: " + e.getMessage());
} finally {
isLoading.postValue(false);
}
}).start();
}
public void updateUser(int userId, String name, String email, int age) {
isLoading.setValue(true);
new Thread(() -> {
try {
int rowsAffected = userRepository.updateUser(userId, name, email, age);
if (rowsAffected > 0) {
// 重新加载数据
loadUser(userId);
loadUsers();
} else {
errorMessage.postValue("更新失败");
}
} catch (Exception e) {
errorMessage.postValue("更新失败: " + e.getMessage());
} finally {
isLoading.postValue(false);
}
}).start();
}
public void deleteUser(int userId) {
isLoading.setValue(true);
new Thread(() -> {
try {
int rowsDeleted = userRepository.deleteUser(userId);
if (rowsDeleted > 0) {
// 重新加载列表
loadUsers();
} else {
errorMessage.postValue("删除失败");
}
} catch (Exception e) {
errorMessage.postValue("删除失败: " + e.getMessage());
} finally {
isLoading.postValue(false);
}
}).start();
}
}
3. 在Activity中使用ViewModel
public class UserListActivity extends AppCompatActivity {
private UserViewModel viewModel;
private RecyclerView recyclerView;
private UserAdapter adapter;
private List<User> userList = new ArrayList<>();
private ProgressBar progressBar;
private View emptyView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
// 初始化组件
recyclerView = findViewById(R.id.recyclerView);
progressBar = findViewById(R.id.progressBar);
emptyView = findViewById(R.id.emptyView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
// 创建ViewModel
viewModel = new ViewModelProvider(this).get(UserViewModel.class);
// 观察LiveData
observeViewModel();
// 加载数据
viewModel.loadUsers();
}
private void observeViewModel() {
// 观察用户列表
viewModel.getUserList().observe(this, users -> {
if (users != null) {
userList = users;
updateUI();
}
});
// 观察加载状态
viewModel.getIsLoading().observe(this, isLoading -> {
if (isLoading != null) {
progressBar.setVisibility(isLoading ? View.VISIBLE : View.GONE);
}
});
// 观察错误消息
viewModel.getErrorMessage().observe(this, errorMessage -> {
if (errorMessage != null && !errorMessage.isEmpty()) {
Toast.makeText(this, errorMessage, Toast.LENGTH_SHORT).show();
}
});
}
private void updateUI() {
if (userList.isEmpty()) {
emptyView.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
} else {
emptyView.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
if (adapter == null) {
adapter = new UserAdapter(userList, user -> {
// 处理点击事件
Intent intent = new Intent(this, UserDetailActivity.class);
intent.putExtra("user_id", user.getId());
startActivity(intent);
});
recyclerView.setAdapter(adapter);
} else {
adapter.updateData(userList);
}
}
}
}
5.3 依赖注入(Dagger Hilt)
1. 添加依赖
// app/build.gradle
dependencies {
implementation 'com.google.dagger:hilt-android:2.48'
kapt 'com.google.dagger:hilt-compiler:2.48'
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
}
2. 创建Application类
@HiltAndroidApp
public class MyApp extends Application {
// Hilt会自动处理
}
3. 创建Module
@Module
@InstallIn(SingletonComponent.class)
public class AppModule {
@Provides
@Singleton
public DatabaseHelper provideDatabaseHelper(@ApplicationContext Context context) {
return new DatabaseHelper(context);
}
@Provides
@Singleton
public UserRepository provideUserRepository(DatabaseHelper dbHelper) {
return new UserRepository(dbHelper);
}
@Provides
@Singleton
public ApiService provideApiService() {
return RetrofitClient.getApiService();
}
}
4. 在Activity中使用
@AndroidEntryPoint
public class UserListActivity extends AppCompatActivity {
@Inject
UserRepository userRepository;
@Inject
ApiService apiService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
// userRepository和apiService已经自动注入
// 可以直接使用
}
}
5.4 测试
1. 单元测试
@RunWith(AndroidJUnit4.class)
public class UserRepositoryTest {
private UserRepository userRepository;
private Context context;
@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
userRepository = new UserRepository(context);
}
@Test
public void testInsertUser() {
long id = userRepository.insertUser("Test User", "test@example.com", 25);
assertTrue(id > 0);
}
@Test
public void testGetUserById() {
long id = userRepository.insertUser("Test User", "test@example.com", 25);
User user = userRepository.getUserById((int) id);
assertNotNull(user);
assertEquals("Test User", user.getName());
}
@Test
public void testUpdateUser() {
long id = userRepository.insertUser("Test User", "test@example.com", 25);
int rowsAffected = userRepository.updateUser((int) id, "Updated User", "updated@example.com", 30);
assertEquals(1, rowsAffected);
}
@Test
public void testDeleteUser() {
long id = userRepository.insertUser("Test User", "test@example.com", 25);
int rowsDeleted = userRepository.deleteUser((int) id);
assertEquals(1, rowsDeleted);
}
}
2. UI测试
@RunWith(AndroidJUnit4.class)
public class UserListActivityTest {
@Rule
public ActivityScenarioRule<UserListActivity> activityRule =
new ActivityScenarioRule<>(UserListActivity.class);
@Test
public void testRecyclerViewIsDisplayed() {
onView(withId(R.id.recyclerView)).check(matches(isDisplayed()));
}
@Test
public void testAddUserButtonIsDisplayed() {
onView(withId(R.id.fabAddUser)).check(matches(isDisplayed()));
}
@Test
public void testClickAddUserButton() {
onView(withId(R.id.fabAddUser)).perform(click());
onView(withId(R.id.etName)).check(matches(isDisplayed()));
}
}
第六部分:发布与优化
6.1 性能优化
1. 内存优化
// 避免内存泄漏
public class UserListActivity extends AppCompatActivity {
private Handler handler = new Handler(Looper.getMainLooper());
private Runnable runnable = new Runnable() {
@Override
public void run() {
// 执行耗时操作
}
};
@Override
protected void onDestroy() {
super.onDestroy();
// 移除所有回调
handler.removeCallbacks(runnable);
// 清理资源
if (adapter != null) {
adapter = null;
}
}
}
2. 网络优化
// 使用缓存
public class RetrofitClient {
private static final String BASE_URL = "https://api.example.com/";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit == null) {
// 创建缓存
int cacheSize = 10 * 1024 * 1024; // 10 MB
Cache cache = new Cache(new File("cache"), cacheSize);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(new 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") // 缓存60秒
.build();
}
})
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
3. UI优化
// 使用ViewHolder模式优化RecyclerView
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHolder> {
// ... 其他代码 ...
static class UserViewHolder extends RecyclerView.ViewHolder {
TextView name, email;
ImageView avatar;
public UserViewHolder(@NonNull View itemView) {
super(itemView);
name = itemView.findViewById(R.id.name);
email = itemView.findViewById(R.id.email);
avatar = itemView.findViewById(R.id.avatar);
}
}
// 使用DiffUtil优化列表更新
public void updateData(List<User> newUsers) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
new UserDiffCallback(userList, newUsers)
);
userList = newUsers;
diffResult.dispatchUpdatesTo(this);
}
static class UserDiffCallback extends DiffUtil.Callback {
private List<User> oldList;
private List<User> newList;
public UserDiffCallback(List<User> oldList, List<User> newList) {
this.oldList = oldList;
this.newList = newList;
}
@Override
public int getOldListSize() {
return oldList.size();
}
@Override
public int getNewListSize() {
return newList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).getId() == newList.get(newItemPosition).getId();
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
User oldUser = oldList.get(oldItemPosition);
User newUser = newList.get(newItemPosition);
return oldUser.getName().equals(newUser.getName()) &&
oldUser.getEmail().equals(newUser.getEmail()) &&
oldUser.getAge() == newUser.getAge();
}
}
}
6.2 安全性考虑
1. 数据加密
public class EncryptionUtils {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
public static String encrypt(String data, String secretKey) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encrypted = cipher.doFinal(data.getBytes());
return Base64.encodeToString(encrypted, Base64.DEFAULT);
}
public static String decrypt(String encryptedData, String secretKey) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decoded = Base64.decode(encryptedData, Base64.DEFAULT);
byte[] decrypted = cipher.doFinal(decoded);
return new String(decrypted);
}
}
2. 安全存储
public class SecureStorage {
private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
private static final String KEY_ALIAS = "MyAppKey";
public static void saveSecureData(Context context, String key, String value) throws Exception {
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
keyStore.load(null);
if (!keyStore.containsAlias(KEY_ALIAS)) {
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER);
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build();
keyGenerator.init(spec);
keyGenerator.generateKey();
}
SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_ALIAS, null);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encrypted = cipher.doFinal(value.getBytes());
String encryptedString = Base64.encodeToString(encrypted, Base64.DEFAULT);
SharedPreferences prefs = context.getSharedPreferences("secure_prefs", Context.MODE_PRIVATE);
prefs.edit().putString(key, encryptedString).apply();
}
public static String getSecureData(Context context, String key) throws Exception {
SharedPreferences prefs = context.getSharedPreferences("secure_prefs", Context.MODE_PRIVATE);
String encryptedString = prefs.getString(key, null);
if (encryptedString == null) return null;
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
keyStore.load(null);
SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_ALIAS, null);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decoded = Base64.decode(encryptedString, Base64.DEFAULT);
byte[] decrypted = cipher.doFinal(decoded);
return new String(decrypted);
}
}
6.3 发布准备
1. 配置签名
// app/build.gradle
android {
signingConfigs {
release {
storeFile file("my-release-key.jks")
storePassword "password"
keyAlias "my-key-alias"
keyPassword "password"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
2. 配置ProGuard
# proguard-rules.pro
# 保持所有模型类
-keep class com.example.usermanager.model.** { *; }
-keep class com.example.usermanager.network.** { *; }
# Retrofit
-keepattributes Signature
-keepattributes *Annotation*
-keep class retrofit2.** { *; }
-keepclasseswithmembers class * {
@retrofit2.http.* <methods>;
}
# Gson
-keep class com.google.gson.** { *; }
-keep class sun.misc.Unsafe { *; }
-keep class com.example.usermanager.model.** { *; }
# SQLite
-keep class android.database.** { *; }
-keep class android.database.sqlite.** { *; }
3. 生成APK
# 在Android Studio中
# Build → Generate Signed Bundle / APK
# 或使用命令行
./gradlew assembleRelease
第七部分:扩展学习与资源
7.1 推荐学习路径
基础阶段(1-2个月)
- Java/Kotlin基础
- Android基础组件
- UI开发与布局
进阶阶段(2-3个月)
- 数据存储与网络通信
- 架构模式(MVC/MVP/MVVM)
- 第三方库使用
高级阶段(3-6个月)
- 性能优化
- 安全性
- 测试与调试
- 发布与维护
7.2 推荐资源
官方文档:
在线课程:
- Udacity Android开发课程
- Coursera Android专项课程
- Google Android开发者培训
开源项目:
社区与论坛:
- Stack Overflow
- Reddit r/androiddev
- Android开发者社区
7.3 持续学习建议
- 关注官方博客:Google Android开发者博客
- 参与开源项目:GitHub上有很多优秀的Android项目
- 参加技术会议:Google I/O, Android Dev Summit
- 阅读源码:学习优秀开源项目的实现
- 实践项目:不断开发新项目,积累经验
结语
Android开发是一个不断发展的领域,从零基础到实战项目开发需要系统的学习和大量的实践。本指南提供了从环境搭建到项目开发的完整流程,涵盖了Android开发的各个方面。
记住,编程最重要的是实践。不要只是阅读代码,要动手编写,调试,优化。每个项目都是学习的机会,每个错误都是进步的阶梯。
祝你在Android开发的道路上取得成功!
