Home

Awesome

Android 代码规范文档

更新日志

目录

前言

代码规范原则

常规规范

目录资源类型
drawable位图文件(.png.9.png.jpg.gif)或编译为以下可绘制对象资源子类型的 XML 文件:位图文件九宫格(可调整大小的位图)状态列表形状动画可绘制对象其他可绘制对象请参阅 Drawable 资源
mipmap适用于不同启动器图标密度的可绘制对象文件。如需了解有关使用 mipmap 文件夹管理启动器图标的详细信息,请参阅管理项目概览

后台接口规范

变量命名规范

String name;
TextView nameView;
FrameLayout nameLayout;
// 命名规范附带技巧(当布局中同个类型的控件只有一个的时候,也可以这样命名)
TextView textView;
RecyclerView recyclerView;
// 不规范写法示例
boolean isDebug;
// 规范写法示例
boolean debug;
static final String REQUEST_INSTALL_PACKAGES;

包名命名规范

方法命名规范

类文件命名规范

HomeActivity.java

SettingFragment.java

HomeAdapter.java

AddressDialog.java
CrashHandler.java

GridSpaceDecoration.java

PickerLayoutManager.java

接口文件命名规范

public class View {

    private View.OnClickListener listener;

    public void setOnClickListener(OnClickListener listener) {
        this.listener = listener;
    }

    public interface OnClickListener {

        void onClick(View v);
    }
}
public class Handler {

    public interface Callback {

        boolean handleMessage(Message msg);
    }
}

代码嵌套规范

// 不规范写法示例
public void test(Object a, Object b, Object c) {
    if (a != null) {
        if (b != null) {
            if (c != null) {
                System.out.println("所有对象不为空");
            } else {
                System.out.println("对象 C 为空");
            }
        } else {
            System.out.println("对象 B 为空");
        }
    } else {
        System.out.println("对象 A 为空");
    }
}
// 规范写法示例
public void test(Object a, Object b, Object c) {
    if (a == null) {
        System.out.println("对象 A 为空");
        return;
    }

    if (b == null) {
        System.out.println("对象 B 为空");
        return;
    }

    if (c == null) {
        System.out.println("对象 C 为空");
        return;
    }

    System.out.println("所有对象不为空");
}

接口实现规范

public final class PasswordEditText extends EditText implements View.OnTouchListener, View.OnFocusChangeListener, TextWatcher {

    public PasswordEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOnTouchListener(this);
        setOnFocusChangeListener(this);
        addTextChangedListener(this);
    }

    @Override
    public void onFocusChange(View view, boolean hasFocus) {
        ......
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        ......
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        ......
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        ......
    }

    @Override
    public void afterTextChanged(Editable s) {
        ......
    }
}
public final class PasswordEditText extends EditText
        implements View.OnTouchListener,
        View.OnFocusChangeListener, TextWatcher {

    public PasswordEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOnTouchListener(this);
        setOnFocusChangeListener(this);
        addTextChangedListener(this);
    }

    /**
     * {@link OnFocusChangeListener}
     */

    @Override
    public void onFocusChange(View view, boolean hasFocus) {
        ......
    }

    /**
     * {@link OnTouchListener}
     */

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        ......
    }

    /**
     * {@link TextWatcher}
     */

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        ......
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        ......
    }

    @Override
    public void afterTextChanged(Editable s) {
        ......
    }
}

异常捕获规范

try {
    Xxx.xxx();
} catch (Exception e) {}
// 捕获这个异常,避免程序崩溃
try {
    // 目前发现在 Android 7.1 主线程被阻塞之后弹吐司会导致崩溃,可使用 Thread.sleep(5000) 进行复现
    // 查看源码得知 Google 已经在 Android 8.0 已经修复了此问题
    // 主线程阻塞之后 Toast 也会被阻塞,Toast 因为显示超时导致 Window Token 失效
    mHandler.handleMessage(msg);
} catch (WindowManager.BadTokenException | IllegalStateException e) {
    // android.view.WindowManager$BadTokenException:Unable to add window -- token android.os.BinderProxy is not valid; is your activity running?
    // java.lang.IllegalStateException:View android.widget.TextView has already been added to the window manager.
    e.printStackTrace();
    // 又或者上报到 Bugly 错误分析中
    // CrashReport.postCatchedException(e);
}
Caused by: java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
   at com.bumptech.glide.manager.RequestManagerRetriever.assertNotDestroyed(RequestManagerRetriever.java:348)
   at com.bumptech.glide.manager.RequestManagerRetriever.get(RequestManagerRetriever.java:148)
   at com.bumptech.glide.Glide.with(Glide.java:826)
try {
    // Activity 销毁后执行加载图片会触发 crash
    Glide.with(this)
            .load(url)
            .into(mImageView);
} catch (IllegalArgumentException e) {
    // java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
    e.printStackTrace();
}
if (isFinishing() || isDestroyed()) {
   // Glide:You cannot start a load for a destroyed activity
    return;
}
Glide.with(this)
        .load(url)
        .into(mImageView);

参数传递规范

public final class WebActivity extends Activity {

    private static final String INTENT_KEY_URL = "url";

    public static void start(Context context, String url) {
        Intent intent = new Intent(context, WebActivity.class);
        intent.putExtra(INTENT_KEY_URL, url);
        context.startActivity(intent);
    }
}
public final class WebActivity extends Activity {

    private static final String INTENT_KEY_URL = "url";

    public static Intent newIntent(Context context, String url) {
        Intent intent = new Intent(context, WebActivity.class);
        intent.putExtra(INTENT_KEY_URL, url);
        return intent;
    }
}
public final class WebFragment extends Fragment {

    private static final String INTENT_KEY_URL = "url";

    public static WebFragment newInstance(String url) {
        WebFragment fragment = new WebFragment();
        Bundle bundle = new Bundle();
        bundle.putString(INTENT_KEY_URL, url);
        fragment.setArguments(bundle);
        return fragment;
    }
}
public final class VideoPlayActivity extends Activity {

    private static final String INTENT_KEY_PARAMETERS = "parameters";

    /**
     * 播放参数构建
     */
    public static final class Builder implements Parcelable {

        /** 视频源 */
        private String videoSource;
        /** 视频标题 */
        private String videoTitle;
        /** 播放进度 */
        private int playProgress;
        /** 手势开关 */
        private boolean gestureEnabled = true;
        /** 循环播放 */
        private boolean loopPlay = false;
        /** 自动播放 */
        private boolean autoPlay = true;
        /** 播放完关闭 */
        private boolean autoOver = true;

        public Builder() {}

        public Builder setVideoSource(File file) {
            this.videoSource = file.getPath();
            if (this.videoTitle == null) {
                this.videoTitle = file.getName();
            }
            return this;
        }

        public Builder setVideoSource(String url) {
            this.videoSource = url;
            return this;
        }

        public Builder setVideoTitle(String title) {
            this.videoTitle = title;
            return this;
        }

        public Builder setPlayProgress(int progress) {
            this.playProgress = progress;
            return this;
        }

        public Builder setGestureEnabled(boolean enabled) {
            this.gestureEnabled = enabled;
            return this;
        }

        public Builder setLoopPlay(boolean enabled) {
            this.loopPlay = enabled;
            return this;
        }

        public Builder setAutoPlay(boolean enabled) {
            this.autoPlay = enabled;
            return this;
        }

        public Builder setAutoOver(boolean enabled) {
            this.autoOver = enabled;
            return this;
        }

        public void start(Context context) {
            Intent intent = new Intent(context, VideoPlayActivity.class);
            intent.putExtra(INTENT_KEY_PARAMETERS, this);
            if (!(context instanceof Activity)) {
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }
            context.startActivity(intent);
        }
    }
}
new VideoPlayActivity.Builder()
        .setVideoTitle("速度与激情特别行动")
        .setVideoSource("http://xxxxx.mp4")
        .start(getAttachActivity());

代码美观性要求

// 不规范写法示例
if (AppConfig.isDebug()) return;
// 规范写法示例
if (AppConfig.isDebug()) {
    return;
}
// 不规范写法示例
if (AppConfig.isDebug())
{
    ......
}
// 规范写法示例
if (AppConfig.isDebug()) {
    ......
}
// 不规范写法示例
public static boolean isAppInstalled(Context context ,String packageName ){
    try {
      context.getPackageManager() .getApplicationInfo(packageName,0);
       return   true;
    }catch( PackageManager.NameNotFoundException e ){
        e.printStackTrace();
        return false ;
    }
}
// 规范写法示例
public static boolean isAppInstalled(Context context, String packageName) {
    try {
        context.getPackageManager().getApplicationInfo(packageName, 0);
        return true;
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
        return false;
    }
}
// 不规范写法示例
ScaleAnimation animation = new ScaleAnimation(1.0f, 1.1f, 1.0f, 1.1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
textView.startAnimation(animation);
// 规范写法示例
ScaleAnimation animation = new ScaleAnimation(1.0f, 1.1f, 1.0f, 1.1f,
        Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
textView.startAnimation(animation);
// 不规范写法示例
GlideApp.with(this).load(url).circleCrop().into(imageView);
// 规范写法示例
GlideApp.with(this)
        .load(url)
        .circleCrop()
        .into(imageView);
public void openSystemFileChooser(Activity activity, FileChooserParams params, ValueCallback<Uri[]> callback) {
    ......
}
// 变量排序示例
public class BaseDialog {

    public static class Builder<B extends BaseDialog.Builder<?>>

        /** 宽度和高度 */
        private int width = WindowManager.LayoutParams.WRAP_CONTENT;
        private int height = WindowManager.LayoutParams.WRAP_CONTENT;

        /** 是否能够被取消 */
        private boolean cancelable = true;
        /** 点击空白是否能够取消  前提是这个对话框可以被取消 */
        private boolean canceledOnTouchOutside = true;

        /** 背景遮盖层开关 */
        private boolean backgroundDimEnabled = true;
        /** 背景遮盖层透明度 */
        private float backgroundDimAmount = 0.5f;

        /** Dialog 创建监听 */
        private BaseDialog.OnCreateListener createListener;
        /** Dialog 显示监听 */
        private final List<BaseDialog.OnShowListener> showListeners = new ArrayList<>();
        /** Dialog 取消监听 */
        private final List<BaseDialog.OnCancelListener> cancelListeners = new ArrayList<>();
        /** Dialog 销毁监听 */
        private final List<BaseDialog.OnDismissListener> dismissListeners = new ArrayList<>();
        /** Dialog 按键监听 */
        private BaseDialog.OnKeyListener keyListener;
    }
}
// 方法排序示例
public class BaseDialog {

    public static class Builder<B extends BaseDialog.Builder<?>>

        /**
         * 设置宽度
         */
        public B setWidth(int width) {
            ......
        }

        /**
         * 设置高度
         */
        public B setHeight(int height) {
            ......
        }

        /**
         * 是否可以取消
         */
        public B setCancelable(boolean cancelable) {
            ......
        }

        /**
         * 是否可以通过点击空白区域取消
         */
        public B setCanceledOnTouchOutside(boolean cancel) {
            ......
        }

        /**
         * 设置背景遮盖层开关
         */
        public B setBackgroundDimEnabled(boolean enabled) {
            ......
        }

        /**
         * 设置背景遮盖层的透明度(前提条件是背景遮盖层开关必须是为开启状态)
         */
        public B setBackgroundDimAmount(float dimAmount) {
            ......
        }

        /**
         * 设置创建监听
         */
        public B setOnCreateListener(BaseDialog.OnCreateListener listener) {
            ......
        }

        /**
         * 添加显示监听
         */
        public B addOnShowListener(BaseDialog.OnShowListener listener) {
            ......
        }

        /**
         * 添加取消监听
         */
        public B addOnCancelListener(BaseDialog.OnCancelListener listener) {
            ......
        }

        /**
         * 添加销毁监听
         */
        public B addOnDismissListener(BaseDialog.OnDismissListener listener) {
            ......
        }

        /**
         * 设置按键监听
         */
        public B setOnKeyListener(BaseDialog.OnKeyListener listener) {
            ......
        }
    }
}

第三方框架使用规范

// 权限请求框架:https://github.com/getActivity/XXPermissions
implementation 'com.github.getActivity:XXPermissions:16.6'

多模块规范

app
base
widget
umeng
course
socket
live
shop
android {

    defaultConfig {
        // 模块混淆配置
        consumerProguardFiles 'proguard-xxx.pro'
    }
}
android {
    // 资源前缀限制
    resourcePrefix "xxx_"
}
ext {

    android = [compileSdkVersion       : 28,
               minSdkVersion           : 19,
               targetSdkVersion        : 28,
               versionCode             : 40102,
               versionName             : "4.1.2",
    ]
    dependencies = [
            "appcompat"                    : "androidx.appcompat:appcompat:1.2.0",
            "material"                     : "com.google.android.material:material:1.2.0",
    ]
}
apply from : '../config.gradle'

android {
    compileSdkVersion rootProject.ext.android["compileSdkVersion"]

    defaultConfig {
        minSdkVersion rootProject.ext.android["minSdkVersion"]
        targetSdkVersion rootProject.ext.android["targetSdkVersion"]
    }
}
dependencies {
    implementation rootProject.ext.dependencies["appcompat"]
    implementation rootProject.ext.dependencies["material"]
}
android {
    compileSdkVersion 28

    defaultConfig {
        minSdkVersion 19
        targetSdkVersion 28
        versionName '4.1.2'
        versionCode 40102
    }
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
}
apply from : '../config.gradle'

代码注释规范

/**
 *    author : Android 轮子哥
 *    github : https://github.com/getActivity/XXPermissions
 *    time   : 2018/06/15
 *    desc   : 权限请求实体类
 *    doc    : https://developer.android.google.cn/reference/android/Manifest.permission?hl=zh_cn
 *             https://developer.android.google.cn/guide/topics/permissions/overview?hl=zh-cn#normal-dangerous
 */
public final class Permission {
    ....
}
/**
 * 设置请求的对象
 *
 * @param activity          当前 Activity,可以传入栈顶的 Activity
 */
public static XXPermissions with(FragmentActivity activity) {
    return ....;
}
/** 请求的权限组 */
private static final String REQUEST_PERMISSIONS = "request_permissions";

/** 权限回调对象 */
private OnPermissionCallback callBack;
// 设置保留实例,不会因为屏幕方向或配置变化而重新创建
fragment.setRetainInstance(true);

代码硬编码规范

// 不规范写法示例
if (view.getVisibility() != 0) {
    return;
}

Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");
startActivity(intent);
// 规范写法示例
if (view.getVisibility() != View.VISIBLE) {
    return;
}

Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
startActivity(intent);
public final class UserInfoManager {

    /** 学生 */
    public static final int TYPE_STUDENT = 0;

    /** 老师 */
    public static final int TYPE_TEACHER = 1;

    /** 家长 */
    public static final int TYPE_PATRIARCH = 2;
}
ValueAnimator animator = ValueAnimator.ofInt(0, 100);
animator.setDuration(500);
animator.start();

布局文件命名规范

home_activity.xml

setting_fragment.xml

menu_item.xml

address_dialog.xml
share_dialog.xml(Root 布局)

share_item.xml(Item 布局)

资源文件命名规范

share_link_ic.png(复制链接)

share_moment_ic.png(分享到朋友圈)

share_qq_ic.png(分享到 QQ 好友)

share_qzone_ic.png(分享到 QQ 空间)

share_wechat_ic.png(分享到微信好友)
button_rect_selector.xml(通用直角按钮样式)

button_round_selector.xml(通用圆角按钮样式)
名称类型
ic图标
bg背景
selector选择器

String ID 命名规范

<!-- 主界面 -->
<string name="home_nav_index">首页</string>
<string name="home_nav_found">发现</string>
<string name="home_nav_message">消息</string>
<string name="home_nav_me">我的</string>
<string name="home_exit_hint">再按一次退出</string>

<!-- 登录界面 -->
<string name="login_register">注册</string>
<string name="login_phone_hint">请输入手机号</string>
<string name="login_password_hint">请输入密码</string>
<string name="login_forget">忘记密码?</string>
<string name="login_text">登录</string>
<string name="login_other">其他登录方式</string>

<!-- 注册界面 -->
<string name="register_title">注册</string>
<string name="register_hint">手机号仅用于登录和保护账号安全</string>
<string name="register_login">登录</string>
<string name="register_password_hint1">设置密码</string>
<string name="register_password_hint2">再次输入密码</string>
<string name="register_password_input_error">两次密码输入不一致,请重新输入</string>

<!-- 设置界面 -->
<string name="setting_title">设置</string>
<string name="setting_language_switchover">语言切换</string>
<string name="setting_language_simple">简体中文</string>
<string name="setting_language_complex">繁体中文</string>
<string name="common_loading">加载中&#8230;</string>

<string name="common_confirm">确定</string>
<string name="common_cancel">取消</string>

<string name="common_year">年</string>
<string name="common_month">月</string>
<string name="common_day">日</string>

<string name="common_hour">时</string>
<string name="common_minute">分</string>
<string name="common_second">秒</string>

Color ID 命名规范

<color name="logcat_level_verbose_color">#FFBBBBBB</color>
<color name="logcat_level_debug_color">#FF33B5E5</color>
<color name="logcat_level_info_color">#FF99CC00</color>
<color name="logcat_level_warn_color">#FFFFBB33</color>
<color name="logcat_level_error_color">#FFFF4444</color>
<color name="logcat_level_other_color">#FFFFFFFF</color>
<!-- App 样式中引用的颜色 -->
<color name="common_primary_color">@color/white</color>
<color name="common_primary_dark_color">@color/black</color>
<color name="common_accent_color">#5A8DDF</color>
<color name="common_window_background_color">#F4F4F4</color>
<color name="common_text_color">#333333</color>
<color name="common_text_hint_color">@color/panda</color>

<!-- 按钮按压时的颜色 -->
<color name="common_button_pressed_color">#AA5A8DDF</color>
<!-- 按钮禁用时的颜色 -->
<color name="common_button_disable_color">#BBBBBB</color>
<!-- 分割线的颜色 -->
<color name="common_line_color">#ECECEC</color>
<!-- 透明色 -->
<color name="transparent">#00000000</color>
<!-- 白色 -->
<color name="white">#FFFFFFFF</color>
<!-- 黑色 -->
<color name="black">#FF000000</color>
<!-- 灰色 -->
<color name="gray">#FF808080</color>
<!-- 红色 -->
<color name="red">#FFFF0000</color>
<!-- 金色 -->
<color name="gold">#FFFFD700</color>
<!-- 黄色 -->
<color name="yellow">#FFFFFF00</color>
<!-- 绿色 -->
<color name="green">#FF008000</color>
<!-- 蓝色 -->
<color name="blue">#FF0000FF</color>
<!-- 紫色 -->
<color name="purple">#FF800080</color>
<!-- 粉色 -->
<color name="pink">#FFFFC0CB</color>
<!-- 橙色 -->
<color name="orange">#FFFFA500</color>
<name="color_FF35BF30">#FF35BF30</color>

Anim ID 命名规范

login_left_balloon_view.xml
login_right_balloon_view.xml
left_in_activity.xml
left_out_activity.xml
bottom_in_dialog.xml
bottom_out_dialog.xml

View ID 命名规范

@+id/R.id.rg_login_type

@+id/R.id.et_login_phone

@+id/R.id.et_login_sms

@+id/R.id.et_login_password

@+id/R.id.btn_login_commit
名称缩写
TextViewtv
EditTextet
Buttonbtn
ImageViewiv
ImageButtonib
ListViewlv
RecyclerViewrv
RadioButtonrb
RadioGrouprg
ProgressBarpb
CheckBoxcb
TableLayouttl
ScrollViewsv
LinearLayoutll
RelativeLayoutrl
FrameLayoutfl

Style 命名规范

<!-- 应用主题样式 -->
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
    .....
</style>

<!-- 全屏主题样式 -->
<style name="FullScreenTheme" parent="AppTheme">
    .....
</style>

<!-- 闪屏页主题样式 -->
<style name="SplashTheme" parent="FullScreenTheme">
    .....
</style>
<!-- 默认圆角按钮样式 -->
<style name="ButtonStyle" parent="Widget.AppCompat.Button.Borderless">
    .....
</style>

<!-- 不带圆角按钮样式 -->
<style name="RectButtonStyle" parent="ButtonStyle">
    .....
</style>

<!-- 默认文本框样式 -->
<style name="EditTextStyle">
    .....
</style>

<!-- 验证码按钮样式 -->
<style name="CountdownViewStyle">
    .....
</style>

XML 编码规范

<!-- 不规范写法示例 -->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="18dp" />
<!-- 规范写法示例 -->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="18sp" />
<!-- 不规范写法示例 -->
<Button
    android:layout_width="180dp"
    android:layout_height="60dp" />
<!-- 规范写法示例 -->
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:paddingStart="80dp"
    android:paddingTop="20dp"
    android:paddingEnd="80dp"
    android:paddingBottom="20dp" />
<!-- 不规范写法示例 -->
<ImageView
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:src="@drawable/example_bg" />
<!-- 规范写法示例 -->
<ImageView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:adjustViewBounds="true"
    android:src="@drawable/example_bg" />
<!-- 不规范写法示例 -->
<androidx.recyclerview.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>

<!-- 不规范写法示例 -->
<TextView
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</TextView>
<!-- 规范写法示例 -->
<androidx.recyclerview.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

<!-- 规范写法示例 -->
<TextView
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

预览属性约定

<FrameLayout 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=".ui.activity.HomeActivity">

</FrameLayout>
tools:context=".ui.activity.HomeActivity"

tools:context=".ui.fragment.SettingFragment"

tools:context=".ui.adapter.HomeAdapter"

tools:context=".ui.dialog.PersonDataDialog"
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/rv_pay_list"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:overScrollMode="never"

    tools:listitem="@layout/item_dialog_pay_password"
    tools:itemCount="9"

    tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
    tools:spanCount="3" />
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    tools:text="学生姓名" />

<ImageView
    android:id="@+id/iv_home_course_image"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    tools:src="@drawable/bg_home_placeholder" />

资源硬编码规范

版本名和版本码规范

versionName '4.8.0'
versionName '4.12.1'
versionCode 41201

Git 版本管理规范

致谢

作者其他开源项目

微信公众号:Android轮子哥

Android 技术 Q 群:10047167

如果您觉得我的开源库帮你节省了大量的开发时间,请扫描下方的二维码随意打赏,要是能打赏个 10.24 :monkey_face:就太:thumbsup:了。您的支持将鼓励我继续创作:octocat:

点击查看捐赠列表

License

Copyright 2021 Huang JinQun

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.