简介:这份《软件学院移动应用软件开发技术实验报告》面向软件学院移动应用开发课程学生及Android入门学习者,围绕实验课的环境搭建与基础开发能力训练展开,可用于对照完成实验、整理报告和复习关键操作。内容覆盖JDK安装及JAVA_HOME环境变量配置、Eclipse与Android SDK/ADT的安装与在线更新、离线SDK组件配置,并延伸到Activity的创建启动、生命周期管理、Intent跳转传值,以及TextView、Button、ImageView等基础控件和LinearLayout、RelativeLayout、GridLayout等布局与XML界面设计。压缩包共1个doc文件,约1.63MB,以完整实验报告文档形式组织,按实验项目记录目的、要求、内容与结果,便于直接查阅和参考报告结构。已有768人学习,适合需要按实验流程搭建Android开发环境、理解Activity运用与UI设计要点,并完成课程实验作业的读者。
1. 从一份 2013 年的 Android 实验报告谈起:四个实验能复用什么
移动应用这门课的实验报告,十有八九最后写成了截图合集——图贴得很满,隔半年再看,却一行也跑不起来。这份资源是一份完整的学生实验报告,覆盖四个实验:Android 系统环境搭建、Activity 运用、UI 设计(一)与(二),配套源码、XML 布局、运行截图四样齐全。它的价值不在“交作业”,而在于把移动应用开发最早那条工具链——JDK、Eclipse、SDK、ADT——和 Activity 跳转、Intent 传值、布局嵌套这三块基本功一次性摊开。适合两类人:要带实验课、需要一份能直接改的模板;以及从 Android Studio 时代入门、想搞清setContentView背后那套老骨架的人。
2. 环境搭建链路:JDK、Eclipse、SDK、ADT 谁先谁后
2.1 为什么这套老工具链还值得走一遍
报告里那套 JDK + Eclipse + Android SDK + ADT 的组合,放到今天属于“博物馆级”配置,但它有一个现代工具链给不了的好处:每个环节都是显式的。Android Studio 把 SDK 下载、Gradle 依赖、构建变体全打包进一个 IDE,新人遇到Could not resolve只能盯着日志发呆;而 ADT 时代你必须亲手指定 JDK 路径、SDK Location、ADT 插件源,链路上的每一环都能看到、能改、能断。这就像学开车先摸手动挡,离合在哪、挡位怎么咬合,心里有数。
选型上,报告的顺序是对的:先 JDK,再 Eclipse,然后 SDK,最后 ADT。原因很实在——ADT 是 Eclipse 插件,装上之后要在偏好设置里填 SDK 路径,而 SDK 的tools目录依赖 JDK 运行sdkmanager之类工具,Eclipse 本身又需要 JDK 才能启动。倒过来的话,你会收获一串“找不到 JRE”“SDK Location 无效”的报错。
需要提醒的是版本边界。报告里用的是 JDK 6 以上、SDK r16、ADT 对应 Eclipse Indigo,这套组合在当年是标准答案,今天复现就得接受它的局限:API Level 最高只到 8 左右,AlertDialog的 Builder 链式写法还是老签名,findViewById必须强转。想原样跑通,就老老实实按报告的版本区间来;想借着这份报告学原理、跑新版,那就把 IDE 换成 Android Studio,只沿用报告里的 Activity 与布局思路。两条路都行,别混着走。
2.2 JDK 与 JAVA_HOME:先让命令行认识 javac
报告里第一步是装 JDK 并配JAVA_HOME,这一步最容易出“装完了但 cmd 里不认”的情况。Windows 下的做法是在“系统属性 → 高级 → 环境变量”里新建JAVA_HOME,值指向 JDK 根目录,然后在Path里追加%JAVA_HOME%\bin。注意JAVA_HOME要指到 JDK 根,不能指到bin或jre子目录,这是最高频的一处错。
# Windows CMD,管理员权限执行 # 1) JAVA_HOME 指向 JDK 根目录,指向 bin 或 jre 都会在后续环节翻车 setx JAVA_HOME "C:\Program Files\Java\jdk1.8.0_202" /M # 2) Path 里追加 bin,让 javac / java 在任意目录可用 # 注意:/M 会写系统变量,已有 Path 内容建议先备份再改 setx PATH "%PATH%;%JAVA_HOME%\bin" /MLinux 或 macOS 下换成export写进 shell 配置文件即可,语义完全相同:
export JAVA_HOME=/opt/jdk1.8.0_202 export PATH=$JAVA_HOME/bin:$PATH # CLASSPATH 里的 "." 代表当前目录,老 JDK 上不加这句偶尔会报找不到主类 export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar环境变量改完必须重开一个终端窗口,旧窗口读的还是旧环境,这是很多人以为配置没生效的原因。验证方式报告里写得很朴素——写个test.java编译运行,这个办法比任何检测脚本都直接:
public class test { public static void main(String[] args) { // 打印版本号,确认 java 命令实际指向哪套 JDK System.out.println("java.version=" + System.getProperty("java.version")); // 打印一行中文,顺带验证控制台编码链路是否正常 System.out.println("hello 移动应用"); } }# 编译时必须显式指定编码,否则中文在 GBK 控制台里会变问号 javac -encoding UTF-8 test.java java test-encoding UTF-8这个参数不是可有可无的。当年实验室机器默认是 GBK 代码页,源码用 UTF-8 存、编译器按 GBK 读,中文直接变成乱码,学生以为是 JDK 装坏了,其实只是编码没对齐。看到java.version打印出来、中文正常显示,环境这一环才算真正闭环。失败时看什么?如果javac提示“不是内部或外部命令”,问题一定在Path;如果javac能跑但java报版本不一致,说明机器上装了多套 JRE,Path里顺序不对,把%JAVA_HOME%\bin提到最前面。
2.3 SDK 离线包与目录约定:platforms / samples / usb_driver 各放什么
SDK 这一步报告给的是“在线 + 离线”双方案。在线走 SDK Manager,勾选平台版本点安装,网络通畅时最省事;离线则是当年机房的真实解法——下一堆 zip 回来,手动解压到对应目录,再让 SDK Manager 认到它们。离线方案看着笨,但把 SDK 的目录结构讲得最透,值得照着做一遍。
| 目录名 | 放什么 | 缺失后的典型表现 |
|---|---|---|
platforms | 各 API Level 的android.jar与框架资源 | 新建工程时列表为空,编译找不到android.*包 |
docs | 离线 API 文档 | 只影响 IDE 里悬停提示,不影响编译运行 |
samples | 官方示例工程 | 无法从示例反查 API 用法,排查效率下降 |
usb_driver | Windows 下真机调试驱动 | 设备管理器出现未知设备,adb devices空列表 |
market_licensing | 应用市场授权校验库 | 只做普通实验用不到,保留空目录即可 |
# 进到 SDK 解压后的根目录,先把 ADT 会按名字找的文件夹建好 cd /path/to/android-sdk-windows mkdir -p platforms docs samples usb_driver market_licensing # 每个下载回来的平台包解压进 platforms,目录名要保留原始 API 命名 # 例如 android-2.2_r01-windows.zip 解压后得到 platforms/android-8/ unzip android-2.2_r01-windows.zip -d platforms/ unzip tools_r06-windows.zip -d .这里的坑在于目录名。SDK Manager 是靠目录名和内部的source.properties双重识别的,手动解压时如果多套了一层文件夹,比如变成platforms/android-8/android-8/,界面里就什么也看不到。判断方法很简单:进platforms/android-8/看有没有android.jar,有就说明层级对了。另一个坑是tools目录,它必须直接位于 SDK 根下,因为 Eclipse 的 SDK Location 校验会去找<sdk>/tools/adb.exe,找不到就报“SDK 未正确配置”。
在线更新时还要注意代理与镜像——机房里如果直连不畅,SDK Manager 会卡在Fetching URL不动,看起来像卡死,其实是在等超时。我的习惯是先把离线包备齐,再开 SDK Manager 让它扫本地,扫描到的东西会显示为已安装,剩下的再在线补,这样能把一大半等待时间省掉。
2.4 ADT 装不上时看哪三处
ADT 是整套链路里最“玄学”的一环,因为它是 Eclipse 插件,装失败的原因横跨网络、Eclipse 版本、JDK 版本三层。报告里的步骤是标准动作:Help → Install New Software → Add,Name 填ADT,Location 填插件更新站点,然后勾选Developer Tools一路 Next、Finish,重启 Eclipse。步骤本身没问题,出问题的是环境差异。
第一处看 Eclipse 版本。ADT 对 Eclipse 主版本有硬性要求,Indigo、Juno、Kepler 各自对应的 ADT 版本不一样,装错了会在安装阶段报依赖缺失,或者装完重启后菜单里看不到 Android 选项。第二处看 JDK。Eclipse 自身跑在某个 JRE 上,如果这个 JRE 版本比 ADT 要求的低,插件能装上但一开就抛UnsupportedClassVersionError。判断方式是看Help → About Eclipse → Installation Details → Configuration,里面有一行java.version。第三处看更新站点是否可达,连不上时 Eclipse 会静默停在“Pending…”,什么提示都没有,这是最坑的一种。
装完还有最后一步,报告写得很清楚但很多人跳过:Window → Preferences → Android,在SDK Location里填 SDK 根目录,点 OK。这一步不做,新建 Android 工程时按钮是灰的,你会以为是 ADT 没装好,其实是没告诉它 SDK 在哪。填完如果路径旁边出现绿色的勾和一堆 API 版本号,说明 SDK 也被正确识别了;出现红叉就回 2.3 检查目录层级。
3. Activity 与 Intent:两个 Demo 拆到可抄的程度
3.1 显式 Intent 的启动链路与清单注册义务
报告里实验二的第一个 Demo 是TestActivity01:主界面一个按钮,点了跳到另一个只有TextView的页面。代码很短,但它把 Android 页面跳转的两个必需条件都摆出来了——代码里发 Intent,清单里注册 Activity。缺任何一个都会失败,而且失败方式完全不同。
public class MainActivity extends Activity { private Button jumpBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 绑定布局,这一步是 Activity 与 XML 的接缝 setContentView(R.layout.activity_main); jumpBtn = (Button) findViewById(R.id.button1); jumpBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 显式 Intent:直接点名目标类,不参与系统隐式匹配 // 第一个参数是上下文,第二个参数是目标 Activity 的 Class 对象 Intent intent = new Intent(MainActivity.this, OtherActivity.class); startActivity(intent); } }); } }new Intent(Context, Class)这个双参构造是显式 Intent 的标志。它绕过了intent-filter的匹配逻辑,系统直接按类名去找组件,所以速度快、不易出错;代价是耦合,目标类改名或换包,这里编译期就会报错。相比之下隐式 Intent 靠 action 和 category 匹配,灵活但匹配失败时只在运行时抛ActivityNotFoundException,排查成本高。实验阶段用显式,是合理的选择。
<application android:label="实验二"> <!-- 带 MAIN + LAUNCHER 的才是桌面入口,缺了它应用装上去也点不开 --> <activity android:name=".MainActivity" android:label="主页面"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- 这一行是坑点:不注册的话 startActivity 直接抛 ActivityNotFoundException --> <activity android:name=".OtherActivity" android:label="第二页" /> </application>清单这段是实验报告里最容易被学生漏掉的部分。当年最常见的一幕是:代码写完、编译通过、一点按钮应用闪退,Logcat里一行Unable to find explicit activity class。原因就是新加的 Activity 没在AndroidManifest.xml里登记。注册的时候android:name的写法要注意,.OtherActivity前面那个点表示“相对于 manifest 里package属性的包名”,写成全限定名也行,但混着写容易错。另外android:exported在当年的 SDK 上默认是 true,所以外部能拉起;放到新版 SDK 上必须显式声明,这是移植时会踩到的一处差异。
3.2 Bundle 传值:从 putString 到 getExtras 的完整链路
第二个 DemoTestActivity02才是这份报告的技术核心:把主页面上的字符串“中国”传到第二页显示。它把 Intent 的两重身份讲明白了——Intent 既是“我要去哪”的路由,也是“我带了什么”的载体。数据挂在Bundle上,Bundle挂在Intent上,接收端再从getIntent().getExtras()取回来。
// 发送端:MainActivity Intent testIntent = new Intent(); Bundle testBundle = new Bundle(); // putString 的键名是字符串,收发两端必须逐字一致,大小写也算 testBundle.putString("name", "中国"); testIntent.putExtras(testBundle); // setClass 与双参构造等价,二选一即可 testIntent.setClass(TextMainActivity.this, test_2.class); startActivity(testIntent);// 接收端:test_2 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.second); TextView test = (TextView) findViewById(R.id.ruirui); // getExtras() 可能为 null:直接启动该 Activity(不经发送端)时就没有 extras Bundle tbd = this.getIntent().getExtras(); if (tbd != null) { String name = tbd.getString("name"); // 取不到时 getString 返回 null,setText(null) 不会崩,但界面是空的 test.setText(name); } }这段代码里有三个必须讲清的参数点。第一是键名,"name"是收发双方的唯一契约,拼错了不会编译报错,运行时getString返回 null,界面一片空白,这就是典型的“代码没错但就是不出结果”。第二是getExtras()的 null 判断,报告里的原始代码没做判空,直接tbd.getString(...),一旦有人从别处直接启动test_2,就是一次空指针崩溃。第三是类型匹配,putString存进去的必须用getString取,用getInt取会返回默认值 0 而不报错,这类静默失败最难查。
再往下可以延伸一步:除了putString,Bundle还支持putInt、putBoolean、putSerializable、putParcelable。基本类型随便传,自定义对象要求实现Serializable或Parcelable,后者性能更好但需要写CREATOR。实验里传个字符串当然用不上这些,但理解了“Bundle 是一个键值容器、Intent 是它的运输工具”,后面传对象、传数组、传列表就都是同一个套路。
3.3 生命周期回调与“数据丢了”的排查顺序
报告里的代码只覆盖了onCreate,但实验报告如果不讲生命周期,后面一定会出问题:跳到第二页再返回,第一页的输入框内容没了;转屏之后页面重建,状态丢了。这些都属于 Activity 生命周期范畴,是这份资源往下深挖最值得补的一块。
标准顺序是onCreate → onStart → onResume → onPause → onStop → onDestroy。onCreate里做布局绑定和初始化,onResume里做需要界面可见才能干的事,onPause里保存需要持久化的轻量状态。转屏会触发onPause → onStop → onDestroy之后重建一个新实例,所以成员变量全丢。补救办法是重写onSaveInstanceState,把关键数据塞进outState,重建时从onCreate的savedInstanceState参数取回来。
排查“数据丢了”有个固定顺序,我一般按这个走:先确认是同一实例还是重建实例,在onCreate里打一行带hashCode()的日志就能看出来;再看数据存在成员变量还是持久化存储里,成员变量跨实例必丢;最后看是不是getIntent()取错了地方,getIntent()在重建后返回的还是原始 Intent,但如果你在中途又setIntent过一次,取到的就是新的。这三步走完,九成的“玄学丢数据”都能定位。
4. 布局嵌套:从图反推结构,把 weight 算明白
4.1 三层嵌套的布局分析:从示意图反推结构
实验三的第一题给了一张图,要求用LinearLayout、TableLayout组合出来。报告里的分析思路很值得学:先看最外层方向,再数每一层有几个子块,最后才决定每个块用什么容器。图里的结构是最外层纵向LinearLayout,里面依次是横向四列、三行三列的TableLayout、纵向四行。这个“先分层、再选容器”的顺序,比一上来就写 XML 靠谱得多。
<!-- 最外层:纵向排列,三个子块从上到下 --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> <!-- 第一块:横向四列,靠 weight 平分宽度 --> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="2" android:orientation="horizontal"> <TextView android:layout_width="0dp" android:layout_height="fill_parent" android:layout_weight="1" android:background="#CE0000" android:gravity="center" android:text="red" /> <!-- 其余三列同构,只改背景色和 text --> </LinearLayout> <!-- 第二块:TableLayout,行数由 TableRow 数量决定,列数由最多的那行决定 --> <TableLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1" android:stretchColumns="*"> <TableRow> <TextView android:text="1-1" android:gravity="center" /> <TextView android:text="1-2" android:gravity="center" /> <TextView android:text="1-3" android:gravity="center" /> </TableRow> <!-- 再补两行 TableRow,凑成三行三列 --> </TableLayout> </LinearLayout>这里有两个参数值得单独说。android:stretchColumns="*"表示所有列都拉伸填满剩余宽度,不写的话表格会挤在左边;android:gravity="center"是控件内部内容居中,和layout_gravity(控件自身在父容器里居中)是两个完全不同的属性,写混了界面对不上图,是初学者翻车的重灾区。
还有一处细节:TableLayout的列数是隐式决定的,它取所有TableRow里子控件数最多的那一行的数量。所以想让三行三列对齐,每行都得放三个子控件;某一行少放一个,那一行的第三个位置就是空的,视觉上看着像错位,其实是符合预期的。
4.2 layout_weight 到底怎么算
layout_weight是布局里最容易被误用的一处。它不是“按比例分配整个屏幕”,而是“分配父容器测量完所有子控件之后剩下的空间”。这个区别决定了你写width="0dp"还是width="wrap_content"会得到完全不同的结果。
算式可以写清楚:假设父容器可用宽度 W,三个子控件layout_width分别是 w1、w2、w3,layout_weight分别是 1、2、3,那么剩余空间 R = W − (w1 + w2 + w3),每个子控件最终宽度 = 自身宽度 + R × (自己的 weight / weight 总和)。当三个控件都设成width="0dp"时,自身宽度为 0,R 就等于 W,最终宽度严格按 1:2:3 分配;如果设成wrap_content,文字长短会影响自身宽度,比例就“看起来不对”了。
所以那条经验规则就成立了:想严格按比例分,就把对应方向的尺寸设为 0dp;想保留内容自适应再加权,就设wrap_content并接受结果偏差。另外weight也可以设成 0,表示不参与剩余空间分配,只占自身内容大小。这三种写法在实际工程里各有用处,不是非得设成 0dp 才叫“正确”。
还有个容易忽略的点:weight是相对于“同一父容器内的兄弟控件”计算的,跨层级没有可比性。你在外层纵向布局上设layout_weight="2",那是在分配纵向的剩余高度;内层横向布局里再设weight="1",分配的是横向剩余宽度。两个方向、两个作用域,别混为一谈。
4.3 AlertDialog 多输入框:从 XML 到回显的完整链路
实验三第二题要求做一个带两个输入框的对话框,点确定之后回显内容,回显方式报告里给了三种选择:Toast、TextView、Logcat。三种都能交作业,但适用场景不一样——Toast适合短提示、几秒后自动消失;TextView适合需要留在页面上对照的长内容;Logcat是给自己调试用的,不算用户可见的输出。
// 1) 把对话框的布局文件膨胀成 View,作为自定义内容塞进 Builder LayoutInflater inflater = LayoutInflater.from(this); View dialogView = inflater.inflate(R.layout.dialog_input, null); // 2) 在膨胀后的 View 里找控件,不能直接 findViewById,那会去 Activity 的布局里找 final EditText etName = (EditText) dialogView.findViewById(R.id.et_name); final EditText etAge = (EditText) dialogView.findViewById(R.id.et_age); new AlertDialog.Builder(this) .setTitle("请输入信息") .setView(dialogView) // 关键:把自定义布局挂上去 .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 3) 取值必须判空 + trim,用户可能什么都不输 String name = etName.getText().toString().trim(); if (name.length() == 0) { Toast.makeText(MainActivity.this, "名称不能为空", Toast.LENGTH_SHORT).show(); return; } Toast.makeText(MainActivity.this, "你输入的是:" + name, Toast.LENGTH_SHORT).show(); } }) .setNegativeButton("取消", null) // 传 null 表示只关闭,不做额外处理 .show();这里面最容易错的是第 2 步。dialogView.findViewById()必须通过那个膨胀出来的 View 去调,如果写成Activity.this.findViewById(),找到的是 Activity 自己布局里的控件,大概率返回 null,然后点确定就空指针。这个错误当年在机房里出现频率极高,因为两个布局里的控件 id 长得像,编辑器也不报错。
第二个点是Builder的生命周期。setPositiveButton的回调是在对话框显示之后触发的,那时候 Activity 可能已经onDestroy,直接拿this弹 Toast 在老版本上没事,新版本上会警告窗口泄漏。规范写法是在onDestroy里dismiss掉对话框,或者在回调里判断isFinishing()。第三点是setView的时机,它必须在show()之前调用,show()之后再setView是不生效的——这个坑很隐蔽,代码不报错,就是界面不显示。
5. 避坑记录:五个当年翻车最多的点
5.1 应用装上了,桌面找不到图标
现象:adb install显示 Success,应用列表里翻遍也找不到,只能靠adb shell am start拉起。
原因:AndroidManifest.xml里该 Activity 缺少MAINaction 与LAUNCHERcategory 的intent-filter。系统判断“哪个入口能出现在桌面”,靠的就是这个过滤器,没有它,Activity 注册了也不出现在启动器里。
解决:给主 Activity 补上完整的intent-filter,并且确认android:name的包路径写法正确。改完必须卸载重装,因为清单变更不会通过覆盖安装生效。
5.2 点按钮直接闪退,日志说找不到 Activity 类
现象:编译零警告,运行点按钮瞬间退出,Logcat里是ActivityNotFoundException: Unable to find explicit activity class。
原因:目标 Activity 没在清单里注册,或者注册的android:name与实际类名、包名不匹配。另一种可能是目标类在别的包里,写.OtherActivity时被解析到了错误的包下。
解决:先在清单里补<activity>节点,再用全限定名写法排除包名歧义。改完Clean Project一次,避免旧产物干扰。
5.3 Intent 传了值,第二页永远是空白
现象:发送端确实putString了,接收端setText也调了,界面上什么都没有,也不崩溃。
原因:键名不一致,或者取值的类型和存的类型不匹配。前者返回 null,后者返回默认值,两种都是静默失败,没有任何异常提示。
解决:把键名抽成常量,收发两端引用同一个常量,从源头消灭拼写差异;取值前先判getExtras() != null,再用同一类型的方法取。调试时在该处打一行Log.d把extras.keySet()打出来,一眼就能看出键名对不对。
5.4 SDK Location 填了红叉,工程建不了
现象:Preferences → Android → SDK Location填了路径,旁边还是红叉,新建工程按钮灰色。
原因:路径指到了platforms或tools子目录,而不是 SDK 根目录;或者根目录下platforms是空的、tools/adb.exe缺失,导致校验不通过。
解决:确认路径层级是<sdk>/tools与<sdk>/platforms/android-*并存;platforms里至少有一个完整的平台包,判据是android.jar存在。如果只是手动解压了平台包但没重启 Eclipse,也需要重启让它重新扫描。
5.5 布局在预览器里好看,真机上全乱
现象:Eclipse 的 Graphical Layout 里排版正常,跑起来要么控件挤成一坨,要么宽度溢出屏幕。
原因:预览器按某个默认屏幕尺寸渲染,而layout_weight、fill_parent的组合对不同分辨率敏感;另外dp和px混用,以及gravity与layout_gravity写反,都会导致预览与真机不一致。
解决:把关键尺寸改成dp,严格按 4.2 的规则确认weight与width的搭配;gravity管内容对齐,layout_gravity管控件自身在父容器里的位置,逐个核对。最后在真机或模拟器上切两三个分辨率各看一遍,别只信预览器。
6. 把报告改成可复现模板:源码目录规范与截图自检
这份报告本身是“结果呈现”,要让它变成能反复用的模板,还得在结构上补一层。我一般会按下面这个目录组织,让“截图”和“源码”一一对应,别人拿到手能按图索骥:
| 目录 / 文件 | 内容 | 用来解决什么 |
|---|---|---|
exp1-env/ | 环境变量配置截图、test.java与运行输出 | 证明工具链闭环,可复现 |
exp2-activity/ | 两个 Demo 的src、res/layout、AndroidManifest.xml | 跳转与传值的完整链路 |
exp3-ui/ | 布局 XML、对话框 XML、Activity 源码 | 布局结构与回显逻辑 |
screenshots/ | 按exp序号-步骤号命名,与正文图号对应 | 图文互查,避免截图对不上描述 |
README.md | 版本区间、复现步骤、已知差异 | 交代边界,减少无效尝试 |
再补一份交付前的自检清单,这五条是我踩过最多次的:
- 每个实验的源码是否完整到“解压即可导入”,包括
res/values/strings.xml里被引用的字符串资源。少了它,@string/my_activity会直接编译失败。 - 每张截图是否能对应到正文里的一个具体步骤,而不是“随手截的界面”。图注写清“做了什么、看到什么”,比图本身更重要。
- 清单文件里的每个 Activity 是否都有对应的
.java文件,反之亦然。多注册一个不存在的类,编译期不报错,运行期才炸。 - 所有
id是否在 XML 与 Java 两侧对得上。改名之后忘了同步,findViewById返回 null,是最典型的静默失败。 - 版本区间有没有写在显眼位置。JDK 8 配老 ADT、老 SDK 配新 Gradle,这些组合问题不写清楚,复现的人会怀疑人生。
有个小技巧值得单独提:把 Android Studio 的“旧工程导入”当成验证手段。把这份 Eclipse 工程按Import Project (Eclipse ADT, Gradle, etc.)导一遍,能顺利构建并在模拟器上跑起来,说明代码本身是干净的,剩下的差异都出在工具链而不是业务逻辑上。这一步相当于给报告做了一次静态体检,比人眼逐行看靠谱。
最后说个习惯。早期我做这类整理,总喜欢先截图再补代码,觉得“界面跑起来了代码肯定没问题”,结果交付出去别人一导入就报资源找不到——截的是运行态,代码是半成品。从那以后我每次整理实验工程,都强制走一遍“清空bin与gen、重新Clean构建、真机跑通、再截图”的流程,截图必须是最终代码跑出来的那一版。这份报告里的四个实验,按这个流程过一遍,能少掉一大半“我这跑不起来”的反馈。希望帮到你。
本文还有配套的精品资源,点击获取