java.md 12 KB


name: ylgs-android-conventions description: >- Implements and refactors Java code in the Ylgs Android app (ylgs) following project conventions: LatteActivity/ViewBinding, latte-core BaseActivity lifecycle, feature packages (ylpc, tyuan, route, ylgs), Fastjson, GreenDao, tab/fragment patterns, and copy-paste code templates. Use when editing this repository, adding Activities or Fragments, scaffolding new screens, or when

the user references 水表普查, ylpc, PcTaskDetailActivity-style code, or 代码模板.

Ylgs Android 项目约定

ylgs 仓库中写或改 Java/Android 代码时,以 app 模块里与 水表普查 / ylpc 相关的实现为风格基准;PcTaskDetailActivity 是轻量、典型的 LatteActivity 页面示例(无 Presenter、ViewBinding、Tab+Fragment)。

工程与包结构

  • 模块: :app(主应用)、:latte-coreLatteActivityBaseActivity、基础 UI/工具)、:graffiti 等。
  • 包按业务划分(同一 app 内勿混用职责):
    • com.tofly.ylpc — 普查等业务
    • com.tofly.tyuan — 台帐/表务等
    • com.tofly.route — 路线/任务等
    • com.tofly.ylgs — 壳、登录、部分通用 Activity
  • 资源与 ViewBinding: R*Binding 类在 com.tofly.ylgsdatabinding 生成于 app 模块),业务类在 ylpc 等包中 import 自 ylgs 是正常做法。

Activity 标准形态(对齐 PcTaskDetailActivity)

  1. 基类: 一般业务页用 LatteActivitycom.tofly.latte_core.base),其继承 BaseActivity<LatteContract.LattePresenter> 并实现 LatteContract.LatteView
  2. 布局接入: 覆写 Object setLayout()
    • ViewBinding:*Binding.inflate(getLayoutInflater()); return binding.getRoot();
    • 或返回 layout 资源 Integer(旧式 XML-only)。
  3. Presenter: 覆写 createPresenter()。无网络/MVP 需求时 return null(与 PcTaskDetailActivity 一致);非 null 时需在 onDestroy 前由基类处理 attach/detach。
  4. 初始化: 把逻辑放在 initView()(基类在 onCreatesetContentView → ButterKnife → createPresenterinitView()initNet())。不要绕过该顺序在子类重复 setContentView(除非像 PhotoActivity 等有特殊 onCreate 链的特殊基类)。
  5. 类注释: 文件头使用 Javadoc @author(项目内常见 76486 / ychk),保持与邻文件一致。

Toolbar 与错误提示

  • 标题栏:使用基类 initBar(Toolbar toolbar, String title);嵌套 toolbar 时常用 binding.*Toolbar*.mainToolbar 形式(随 layout 命名)。
  • 用户可见错误:调用 onError(String)BaseViewToastUtils),与 PcTaskDetailActivity 扫码失败分支一致。

Intent / 页面传参

  • 简单标量:getIntent().getLongExtra / getIntExtra / getBooleanExtra / getStringExtra,key 使用 字符串字面量(项目现有风格;新代码若引入常量类需与模块内既有做法一致)。
  • 实体跨页:常用 com.alibaba.fastjson.JSON
    • 传出 / Fragment:JSON.toJSONString(entity)
    • 读入:JSON.parseObject(json, XxxEntity.class)
    • Intent 里可同时带 pcId 等,若带 data JSON 则可在读库后再覆盖(见 PcTaskDetailActivity)。

TabLayout + ViewPager + 多 Fragment

  • 使用 ListPageAdapterFragmentPagerAdapter):FragmentManager + 标题列表 + 可选 tab 图标 List<Integer>(R.mipmap.*) + List<Fragment>
  • setupWithViewPager 后,对 tab 设置 setCustomView(listPageAdapter.getTabView(i, this))
  • setOffscreenPageLimit 按页数设置,避免相邻页被销毁导致状态丢失(普查示例为 2)。
  • 初始页:用业务字段(如 type == 2)调用 setCurrentItem

Fragment 与数据下发

  • 新建 Fragment 后将 Bundle 置于 setArguments(bundle),不要在构造里塞大块状态。
  • 普查线继承链常见:PhotoFragment(拍照/地图等)← LatteDelegate;复杂表单 Fragment 使用对应 FragmentXxxBinding
  • 从 Activity 转发扫码等结果到子 Fragment:持有 fragment 引用并调用子类方法(如 setQc),与 PcTaskDetailActivity.onActivityResult 模式一致。

扫码(ZXing)

  • onActivityResult 中 requestCode 49374IntentIntegrator.parseActivityResult 配合使用(与 Google ZXing 集成一致);成功则把内容下发到当前业务 Fragment,失败 onError("扫码失败")

本地存储(ylpc)

  • 普查相关实体常用 PcDaoUtilsStore.getInstance() 等 GreenDao 封装访问(如 queryById);路径与实体定义随 greenDao 包与 com.tofly.ylpc.entity 保持一致。

依赖与 API 习惯(实现时沿用)

  • JSON: Fastjson(JSONJSONObject)。
  • 异步/生命周期: RxAppCompatActivityBaseActivity 父类);订阅需遵守项目既有 RxLifecycle 用法。
  • 图片/相机/地图: 普查相关多依赖 PhotoActivity / PhotoFragmentTakePhoto、高德等——新页面若需同类能力应 继承对应基类,而不是在 LatteActivity 里堆叠重复逻辑。

代码模板

以下模板为 com.tofly.ylpc(或对应业务包)+ com.tofly.ylgs.R / databinding 写法;将占位符替换为实际类名、布局名、实体类与 presenter 实现。布局文件需先在 app 工程中创建(如 activity_xxx.xml),ViewBinding 类名遵循 Android 命名规则。

A. LatteActivity + ViewBinding(无 Presenter,最常用)

package com.tofly.ylpc.ui.activity;

import com.tofly.latte_core.base.LatteActivity;
import com.tofly.latte_core.base.LatteContract;
import com.tofly.ylgs.databinding.ActivityXxxPlaceholderBinding;

/**
 * @author 76486
 */
public class XxxPlaceholderActivity extends LatteActivity {

    private ActivityXxxPlaceholderBinding binding;

    @Override
    public Object setLayout() {
        binding = ActivityXxxPlaceholderBinding.inflate(getLayoutInflater());
        return binding.getRoot();
    }

    @Override
    protected LatteContract.LattePresenter createPresenter() {
        return null;
    }

    @Override
    public void initView() {
        initBar(binding.llToolbar.mainToolbar, "标题");
        // getIntent()、控件事件、RecyclerView 等
    }
}

说明:initBar 第一参随 layout 中 Toolbar 实际 id 调整(无嵌套则可能为 binding.mainToolbar 等)。

B. Activity 跳转并传递实体(Fastjson)

Intent intent = new Intent(fromActivity, TargetActivity.class);
intent.putExtra("id", entity.getId());
intent.putExtra("data", JSON.toJSONString(entity));
fromActivity.startActivity(intent);

接收端:

String data = getIntent().getStringExtra("data");
if (!TextUtils.isEmpty(data)) {
    WaterMeterEntity entity = JSON.parseObject(data, WaterMeterEntity.class);
}

C. TabLayout + ViewPager + ListPageAdapter(多块 Fragment)

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import androidx.fragment.app.Fragment;

import com.tofly.ylpc.ui.adapter.ListPageAdapter;

// 在 initView() 或独立方法中:
List<String> titles = Arrays.asList("标签一", "标签二");
List<Integer> tabIcons = Arrays.asList(R.mipmap.icon_jc, R.mipmap.icon_wf); // 可改为单页时不传图标逻辑
List<Fragment> fragments = new ArrayList<>();
ExampleFragmentTab0 f0 = new ExampleFragmentTab0();
ExampleFragmentTab1 f1 = new ExampleFragmentTab1();
Bundle bundle = new Bundle();
bundle.putString("data", JSON.toJSONString(modelEntity));
f0.setArguments(bundle);
f1.setArguments(bundle);
fragments.add(f0);
fragments.add(f1);

ListPageAdapter adapter = new ListPageAdapter(getSupportFragmentManager(), titles, tabIcons, fragments);
binding.viewPager.setAdapter(adapter);
binding.viewPager.setOffscreenPageLimit(fragments.size());
binding.tabLayout.setupWithViewPager(binding.viewPager);
for (int i = 0; i < binding.tabLayout.getTabCount(); i++) {
    binding.tabLayout.getTabAt(i).setCustomView(adapter.getTabView(i, this));
}
binding.viewPager.setCurrentItem(0);

D. LatteDelegate / LatteFragment + ViewBinding(含 Presenter)

继承 LatteDelegate(或项目中的 PhotoFragment 等你需要的中间基类):

package com.tofly.ylpc.ui.fragment;

import com.tofly.latte_core.base.LatteContract;
import com.tofly.ylgs.databinding.FragmentXxxPlaceholderBinding;

/**
 * @author 76486
 */
public class XxxPlaceholderFragment extends LatteDelegate {

    private FragmentXxxPlaceholderBinding binding;

    @Override
    public Object setLayout() {
        binding = FragmentXxxPlaceholderBinding.inflate(getLayoutInflater());
        return binding.getRoot();
    }

    @Override
    protected LatteContract.LattePresenter createPresenter() {
        return null; // 若有上传/接口实现则 return new YourImpl();
    }

    @Override
    public void initView() {
        Bundle args = getArguments();
        if (args != null) {
            String json = args.getString("data");
            // JSON.parseObject(json, ...)
        }
    }
}

需要拍照/地图等能力时,将 extends LatteDelegate 改为 extends PhotoFragment,并补齐父类要求的构造或生命周期(与同目录已有 Fragment 一致)。

E. ZXing 扫码回调(Activity 中转 Fragment)

import android.content.Intent;

import androidx.annotation.Nullable;

import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;

private static final int ZXING_SCAN_REQUEST = 49374;

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == ZXING_SCAN_REQUEST && resultCode == RESULT_OK) {
        IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
        if (result != null && !android.text.TextUtils.isEmpty(result.getContents())) {
            String text = result.getContents();
            if (yourFragment != null) {
                yourFragment.setQc(text); // 方法名以目标 Fragment 为准,如同模块的 setQc
            }
        } else {
            onError("扫码失败");
        }
    }
}

启动扫码:new IntentIntegrator(this).initiateScan();(与项目现有写法保持一致)。

F. Presenter:仅当页面需要时再实现

若无独立 Presenter,始终 createPresenter()null。若新建 LatteContract.LattePresenter 的实现类(如普查中的 UpLoadImpl),再在对应 Activity/Delegate 里 return new XxxImpl(),并实现契约中的网络方法;不要在模板里虚构方法体,应从同模块 Impl / Activity 复制订阅与 getResult* 回调模式。

禁止与注意事项

  • 不要在无关文件做“顺手重构”;与原文件作者风格(命名、注释量)对齐。
  • 不要把 skill 里的示例当成唯一 key 名;以当前调用链与同类页面为准。
  • 新增资源放在 app 模块既有 drawable/mipmap/layout 命名习惯下;strings 中文标题与业务用语与普查模块现有一致。

自检清单(新 Activity)

  • setLayout 返回 Binding root 或 layout id
  • createPresenter 明确 null 或真实 Presenter
  • initView 内解析 Intent、初始化 toolbar
  • R / Binding 来自 com.tofly.ylgs
  • 错误走 onError,加载走 showLoading/hideLoading(若用网络层)