Android 开发规范指南实战教程

Android 开发规范指南实战教程

【免费下载链接】android-guidelines Architecture and code guidelines we use at ribot when developing for Android 【免费下载链接】android-guidelines 项目地址: https://gitcode.com/gh_mirrors/an/android-guidelines

还在为Android项目代码风格混乱、架构不清晰而烦恼吗?本文将为你提供一套完整的Android开发规范实战指南,涵盖项目结构、代码风格、架构设计等核心内容,助你打造高质量、易维护的Android应用。

📋 文章内容概览

读完本文你将掌握:

  • Android项目标准化结构设计
  • Java代码规范与最佳实践
  • XML资源文件命名规范
  • MVP架构模式实战应用
  • 单元测试与Espresso测试规范
  • 异常处理与日志管理策略

🏗️ 项目结构规范

标准Gradle项目结构

遵循Android Gradle插件定义的标准项目结构:

app/
├── src/
│   ├── main/
│   │   ├── java/com/yourapp/
│   │   ├── res/
│   │   │   ├── drawable/
│   │   │   ├── layout/
│   │   │   ├── menu/
│   │   │   └── values/
│   │   └── AndroidManifest.xml
│   └── androidTest/java/com/yourapp/
└── build.gradle

文件命名规范

类文件命名

类名采用UpperCamelCase(大驼峰命名法),继承Android组件的类应以组件名结尾:

// Good examples
SignInActivity
UserProfileFragment
ImageUploadService
ChangePasswordDialog

// Bad examples
signin_activity  // 不使用下划线
userProfileFrag  // 不完整的组件名
资源文件命名

资源文件采用lowercase_underscore(小写下划线命名法):

资源类型命名规则示例
布局文件组件类型_描述activity_main.xml
菜单文件组件名activity_user.xml
值文件复数形式strings.xml, styles.xml

🎨 代码风格规范

字段定义与命名

public class MyClass {
    // 常量:ALL_CAPS_WITH_UNDERSCORES
    public static final int MAX_RETRY_COUNT = 3;
    
    // 公有字段:小写字母开头
    public int publicField;
    
    // 私有静态字段:s前缀
    private static MyClass sInstance;
    
    // 私有非静态字段:m前缀  
    private int mPrivateField;
    
    // 包内可见字段:m前缀
    int mPackagePrivateField;
}

缩进与括号风格

使用4空格缩进,括号与代码同行:

// 正确的缩进和括号风格
if (condition) {
    executeMethod();
} else {
    handleAlternative();
}

// 单行语句可不加括号(仅当条件体和语句能在一行内)
if (condition) doSomething();

// 错误的风格
if (condition)
    doSomething();  // 缺少括号,易出错

导入语句排序

导入语句按以下顺序分组,每组间空一行:

  1. Android imports
  2. 第三方库 imports (com, junit, net, org)
  3. java 和 javax imports
  4. 同项目 imports
// Android imports
import android.app.Activity;
import android.os.Bundle;

// 第三方库
import com.squareup.retrofit2.Retrofit;

// Java标准库
import java.util.List;
import javax.inject.Inject;

// 同项目
import com.yourapp.utils.StringUtils;

📊 资源命名详细规范

Drawable资源命名

资源类型前缀示例
图标ic_ic_star.png
启动图标ic_launcheric_launcher_app.png
菜单图标ic_menuic_menu_settings.png
按钮btn_btn_submit_normal.9.png
对话框dialog_dialog_background.9.png

选择器状态后缀

状态后缀示例
正常_normalbtn_login_normal.png
按下_pressedbtn_login_pressed.png
聚焦_focusedbtn_login_focused.png
禁用_disabledbtn_login_disabled.png

布局文件命名规范

组件类名布局文件名
ActivityUserProfileActivityactivity_user_profile.xml
FragmentSignUpFragmentfragment_sign_up.xml
DialogChangePasswordDialogdialog_change_password.xml
列表项-item_user.xml
部分布局-partial_header.xml

🏛️ MVP架构实战

架构组件关系图

mermaid

各层职责说明

View层(UI层)
  • Activities、Fragments等Android标准组件
  • 负责显示Presenter提供的数据
  • 处理用户交互事件
  • 调用Presenter的相应方法
Presenter层
  • 订阅DataManager提供的Observables
  • 处理订阅生命周期
  • 分析/修改DataManager返回的数据
  • 调用View的适当方法显示数据
Model层(数据层)
  • 负责数据的检索、保存、缓存和处理
  • 与本地数据库和API通信
  • 包含Helpers和DataManager

DataManager实现示例

public class UserDataManager {
    private final DatabaseHelper mDatabaseHelper;
    private final PreferencesHelper mPreferencesHelper;
    private final UserService mUserService;

    public Observable<User> getUserProfile(int userId) {
        return mDatabaseHelper.getUser(userId)
                .switchIfEmpty(mUserService.getUser(userId)
                        .doOnNext(user -> mDatabaseHelper.saveUser(user)))
                .doOnNext(user -> mPreferencesHelper.saveLastSeenUser(user));
    }
}

Presenter实现示例

public class UserProfilePresenter {
    private final UserDataManager mDataManager;
    private UserProfileView mView;

    public void loadUserProfile(int userId) {
        mDataManager.getUserProfile(userId)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(user -> mView.displayUser(user),
                        error -> mView.showError("加载用户信息失败"));
    }
}

🐛 异常处理与日志规范

异常处理最佳实践

// 错误做法:忽略异常
void setServerPort(String value) {
    try {
        serverPort = Integer.parseInt(value);
    } catch (NumberFormatException e) { 
        // 绝对不要忽略异常!
    }
}

// 正确做法:适当处理异常
void setServerPort(String value) {
    try {
        serverPort = Integer.parseInt(value);
    } catch (NumberFormatException e) {
        Log.w(TAG, "无效的端口号: " + value);
        serverPort = DEFAULT_PORT; // 提供默认值
    }
}

日志管理规范

使用类名作为TAG,并定义为静态final字段:

public class UserActivity extends Activity {
    private static final String TAG = UserActivity.class.getSimpleName();
    
    private void loadData() {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "开始加载用户数据");
        }
        
        try {
            // 业务逻辑
            Log.i(TAG, "数据加载成功");
        } catch (Exception e) {
            Log.e(TAG, "数据加载失败", e);
        }
    }
}

日志级别使用指南

级别使用场景发布版本处理
VERBOSE详细调试信息必须禁用
DEBUG调试信息必须禁用
INFO普通信息建议禁用
WARN警告信息可保留
ERROR错误信息可保留

🧪 测试规范

单元测试命名规范

测试类名:被测试类名 + Test 测试方法名:methodName + Precondition + ExpectedBehaviour

public class DatabaseHelperTest {
    @Test
    void getUserWithInvalidIdReturnsEmpty() {
        // 测试逻辑
    }
    
    @Test 
    void saveUserWithValidDataSucceeds() {
        // 测试逻辑
    }
}

Espresso测试规范

public class LoginActivityTest {
    @Test
    void loginWithValidCredentialsSucceeds() {
        onView(withId(R.id.edit_text_email))
                .perform(typeText("test@example.com"));
        
        onView(withId(R.id.edit_text_password))
                .perform(typeText("password123"));
        
        onView(withId(R.id.button_login))
                .perform(click());
        
        onView(withId(R.id.text_view_welcome))
                .check(matches(withText("欢迎回来!")));
    }
}

📝 代码组织与排序

类成员排序建议

public class MainActivity extends Activity {
    // 1. 常量
    private static final String TAG = "MainActivity";
    public static final int REQUEST_CODE = 1001;
    
    // 2. 字段
    private String mUserName;
    private TextView mTextViewTitle;
    
    // 3. 构造函数
    public MainActivity() {}
    
    // 4. 生命周期方法(按生命周期顺序)
    @Override
    protected void onCreate(Bundle savedInstanceState) {}
    
    @Override
    protected void onStart() {}
    
    @Override
    protected void onResume() {}
    
    // 5. 重写方法和回调
    @Override
    public void onBackPressed() {}
    
    // 6. 公有方法
    public void updateUserProfile(User user) {}
    
    // 7. 私有方法
    private void initializeViews() {}
    
    // 8. 内部类和接口
    interface OnUserClickListener {}
}

方法参数顺序规范

// Context总是第一个参数
public User loadUser(Context context, int userId);

// 回调接口总是最后一个参数  
public void loadUserAsync(Context context, int userId, UserCallback callback);

// Intent和Bundle的键常量命名
static final String EXTRA_USER_ID = "com.yourapp.extra.USER_ID";
static final String ARGUMENT_USER_NAME = "ARGUMENT_USER_NAME";

🔧 实用工具与技巧

RxJava链式调用样式

public Observable<List<User>> searchUsers(String query) {
    return mDatabaseHelper.searchUsers(query)
            .filter(users -> !users.isEmpty())
            .switchIfEmpty(mApiService.searchUsers(query)
                    .doOnNext(users -> mDatabaseHelper.saveUsers(users)))
            .retryWhen(errors -> errors.flatMap(error -> {
                if (error instanceof NetworkError) {
                    return Observable.timer(5, TimeUnit.SECONDS);
                }
                return Observable.error(error);
            }))
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread());
}

字符串常量管理

public class IntentConstants {
    private IntentConstants() {} // 防止实例化
    
    // Intent Extra前缀
    public static final String EXTRA_USER = "com.yourapp.extra.USER";
    public static final String EXTRA_EMAIL = "com.yourapp.extra.EMAIL";
    
    // Bundle参数前缀
    public static final String ARGUMENT_USER_ID = "ARGUMENT_USER_ID";
    public static final String ARGUMENT_CATEGORY = "ARGUMENT_CATEGORY";
    
    // SharedPreferences前缀
    public static final String PREF_USER_TOKEN = "PREF_USER_TOKEN";
    public static final String PREF_LAST_LOGIN = "PREF_LAST_LOGIN";
}

🚀 性能优化建议

内存泄漏预防

public class MainActivity extends Activity {
    private Subscription mSubscription;
    
    @Override
    protected void onResume() {
        super.onResume();
        mSubscription = mDataManager.getData()
                .subscribe(data -> updateUI(data));
    }
    
    @Override
    protected void onPause() {
        super.onPause();
        if (mSubscription != null && !mSubscription.isUnsubscribed()) {
            mSubscription.unsubscribe();
        }
    }
}

视图绑定优化

// 使用ViewHolder模式优化列表性能
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder> {
    
    public static class ViewHolder extends RecyclerView.ViewHolder {
        TextView nameTextView;
        ImageView avatarImageView;
        
        public ViewHolder(View itemView) {
            super(itemView);
            nameTextView = itemView.findViewById(R.id.text_name);
            avatarImageView = itemView.findViewById(R.id.image_avatar);
        }
    }
}

📋 总结检查清单

代码规范检查项

  •  类名使用UpperCamelCase
  •  资源文件使用lowercase_underscore
  •  字段命名遵循m/s前缀规范
  •  导入语句正确分组排序
  •  异常处理得当,不忽略异常
  •  日志输出在发布版本中适当禁用

架构规范检查项

  •  遵循MVP架构分层
  •  DataManager正确组合Helpers功能
  •  Presenter处理业务逻辑,不包含Android相关代码
  •  View只负责UI显示和用户交互

测试规范检查项

  •  单元测试覆盖核心业务逻辑
  •  Espresso测试覆盖主要用户流程
  •  测试命名清晰表达预期行为

🔮 未来展望

随着Android开发的不断发展,建议关注以下趋势:

  1. Jetpack Compose - 新一代声明式UI框架
  2. Kotlin协程 - 替代RxJava的异步处理方案
  3. 模块化架构 - 提高大型项目的可维护性
  4. 测试驱动开发 - 更早发现和预防问题

保持对新技术的学习和适应,同时坚持良好的编码规范和架构设计原则,将帮助你在Android开发道路上走得更远。


立即行动:选择项目中最需要改进的一个模块,应用本文介绍的规范进行重构,体验规范化开发带来的好处!

下期预告:我们将深入探讨Android性能优化实战技巧,包括内存管理、网络优化、启动速度提升等核心话题。

【免费下载链接】android-guidelines Architecture and code guidelines we use at ribot when developing for Android 【免费下载链接】android-guidelines 项目地址: https://gitcode.com/gh_mirrors/an/android-guidelines

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值