Android原生天气App源码:含HTTP请求、多密度适配与Ant构建
2026/6/11 17:26:20 网站建设 项目流程

本文还有配套的精品资源,点击获取

简介:一套可直接编译运行的Android天气查询应用源码,基于原生Java开发,不依赖任何第三方SDK。通过标准HTTP接口(如HttpURLConnection)拉取实时天气数据,完整实现城市定位、天气信息展示、温度/湿度/风速等基础字段解析。工程已做好全屏幕密度适配,包含drawable-ldpi、mdpi、hdpi、xhdpi和nodpi五套资源目录,layout布局文件结构清晰,values中定义了字符串与样式资源。项目自带AndroidManifest.xml配置、proguard混淆规则、local.properties本地环境配置及build.xml Ant构建脚本,支持Eclipse与Android Studio双环境导入调试。src目录下代码分层明确,涵盖Activity主界面、网络工具类、JSON解析逻辑与UI更新机制;res中layout提供简洁卡片式天气展示页;assets未使用但预留扩展位。配套有CSDN技术文档说明关键接口调用方式与数据映射关系,适合用于Android基础开发教学、网络通信实践、资源适配训练及APK打包流程学习。

1. 项目概述:为什么这个天气App源码值得你花30分钟细读

我带过十几届Android开发新人,也给不少转行的朋友做过一对一辅导。每次讲到“如何把一个功能从想法落地成可运行的APK”,他们最常卡在三个地方:一是网络请求怎么写才不崩溃、不阻塞主线程;二是图片资源一放到不同手机上就模糊或拉伸变形,debug半天发现是密度适配没做全;三是明明代码写完了,却卡在“怎么打包?build.xml里target到底怎么写?proguard规则为什么混淆后App直接闪退?”——这三个痛点,这个天气App源码全都用最朴素、最标准、最不耍花样的方式给你拆解清楚了。

它不是炫技型项目,没有Kotlin协程、没有Retrofit+RxJava链式调用、没有Jetpack Compose动态布局。它用的是2014年前后Android原生开发的“黄金范式”:Java语言 + Activity生命周期驱动 + HttpURLConnection同步/异步封装 + 手动JSON解析 + XML布局 + drawable-xxx密度目录硬编码适配 + Ant脚本全自动构建。听起来“老”,但恰恰因为老,它像一台透明的发动机——每个齿轮怎么咬合、机油往哪流、哪里容易积碳,你一眼就能看明白。比如HttpURLConnectionsetConnectTimeout()setReadTimeout()为什么必须设、设多少才合理;比如为什么drawable-nodpi里放的是字体图标而drawable-xhdpi里必须放2倍尺寸的PNG;比如build.xml-dextarget里<arg value="--no-locals"/>这个参数删掉会导致什么后果……这些细节,在现代IDE一键生成的Gradle脚本里早被封装得密不透风,反而成了黑箱。

关键词里提到的“Android天气源码”“HTTP天气请求”“多密度资源适配”“Android原生开发”“Ant构建脚本”,每一个都不是虚词。它用真实接口(虽然现在可能已下线,但结构完全可替换)、真实资源目录结构、真实构建流程,还原了一个合格Android工程师在2015年左右交付一个轻量级工具类App的完整工作流。你不需要把它当成一个“能用的天气App”来运行,而是当成一本可执行的《Android开发底层实践手册》——src里的WeatherActivity.java是UI层教科书,NetworkUtil.java是网络层操作规范,res/drawable-*目录树是资源适配的实体沙盘,build.xml则是APK诞生前的最后一道工序说明书。哪怕你现在主攻Flutter或React Native,搞懂这套逻辑,对理解跨平台框架底层如何桥接原生资源、如何处理平台差异,依然有不可替代的价值。

2. 整体架构与设计思路:为什么选择这套“过时”组合?

2.1 不用第三方库,是刻意为之的教育设计

很多人第一眼看到这个项目会皱眉:“怎么还在用HttpURLConnection?Retrofit不是更简洁吗?”——这恰恰是它最大的教学价值所在。Retrofit再好,它把网络请求抽象成了接口方法调用,把线程切换封装进了CallAdapter,把JSON解析交给了GsonConverterFactory。你调用weatherApi.getTodayWeather("beijing"),背后发生了什么?连接建立、DNS解析、SSL握手、Header组装、Body序列化、响应流读取、异常重试、线程切换、回调分发……全被隐藏了。而这个项目里,NetworkUtil.java中不到80行的fetchWeatherData(String city)方法,把整个链条赤裸裸地摊开:

URL url = new URL("http://api.example.com/weather?city=" + city); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(10000); // 关键!超时必须设,否则ANR conn.setReadTimeout(15000); conn.setDoInput(true); int responseCode = conn.getResponseCode(); // 这里才是真正的网络IO阻塞点 if (responseCode == HttpURLConnection.HTTP_OK) { InputStream is = conn.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } return sb.toString(); // 原始JSON字符串,无任何中间层 }

这段代码里藏着三个必须亲手踩过的坑:第一,setConnectTimeout()setReadTimeout()缺一不可,前者防DNS卡死,后者防服务器响应慢;第二,getResponseCode()是同步阻塞调用,必须放在子线程(项目里用的是AsyncTask,虽已废弃但逻辑清晰);第三,InputStream必须用BufferedReader包装并指定UTF-8,否则中文城市名(如“上海”)传参会乱码。这些不是理论,是你在Logcat里看到java.net.SocketTimeoutExceptionandroid.os.NetworkOnMainThreadException时,唯一能救命的线索。用Retrofit,你查文档;用这个源码,你查自己写的代码。

2.2 多密度适配不是“放五套图”那么简单

目录里列着drawable-ldpimdpihdpixhdpinodpi,看起来只是文件夹名字不同。但实际开发中,90%的人只做了xhdpi一套,然后让系统自动缩放——结果在Nexus 5(xhdpi)上清晰,在Galaxy S4(xxhdpi)上模糊,在Kindle Fire(mdpi)上巨大。这个项目用最笨也最可靠的方式解决了它:所有图标资源都按比例提供五套。比如主界面那个温度计图标ic_weather_temp.png

  • drawable-ldpi/ic_weather_temp.png:36×36 px(基准mdpi的0.75倍)
  • drawable-mdpi/ic_weather_temp.png:48×48 px(基准尺寸)
  • drawable-hdpi/ic_weather_temp.png:72×72 px(mdpi的1.5倍)
  • drawable-xhdpi/ic_weather_temp.png:96×96 px(mdpi的2倍)
  • drawable-nodpi/ic_weather_temp.png:不放位图,只放.9.png切片或字体图标(项目里是SVG转的字体图标,存于assets/fonts/weather-icons.ttf

关键在于nodpi的用法。很多人以为nodpi就是“不缩放”,其实它是“跳过密度缩放逻辑”。项目里把字体图标放在这里,是因为字体本身就是矢量,放大缩小不失真;而把启动图splash.png放在drawable-xhdpi里,是因为启动图必须像素级精准,不能靠缩放糊弄。这种决策背后是Android资源加载机制的深度理解:系统根据设备densityDpi值(如160、240、320、480)匹配最接近的drawable-xxx目录,若未找到则降级查找(如xhdpi设备找drawable-xhdpidrawable-hdpidrawable-mdpi),但nodpi永远不参与这个匹配过程。所以nodpi不是“万能兜底”,而是“主动隔离”。

2.3 Ant构建脚本:比Gradle更暴露APK生成本质

现在没人手写build.xml了,但正是因为它“过时”,才成了最好的教学模具。打开build.xml,你会发现它把APK打包拆成了7个原子步骤:

  1. -setup:初始化环境变量,读取local.properties中的sdk.dir
  2. -build-setup:创建gen/bin/等输出目录
  3. -code-gen:调用aapt生成R.java(注意:不是Android Studio的R,是纯Java类)
  4. -pre-compile:拷贝libs/下的jar包到bin/classes/
  5. -compile:用javac编译src/gen/下的所有.java文件
  6. -dex:用dx工具将.class转为classes.dex
  7. -package:用aapt打包资源、jarsigner签名、zipalign优化

其中-dextarget里这一段特别值得细看:

<target name="-dex" depends="-compile"> <exec executable="${dx}" failonerror="true"> <arg value="--dex"/> <arg value="--output=${out.dex}"/> <arg value="--no-locals"/> <!-- 关键!减少dex体积 --> <arg value="${out.classes}"/> <arg value="${external.libs.dir}"/> </exec> </target>

--no-locals参数删掉,你的APK体积会增大15%,因为dx默认保留调试符号表;--output路径必须是绝对路径,否则ant debug会报FileNotFoundExceptionexternal.libs.dir指向libs/,但项目里其实没放任何jar,说明它预留了扩展位——这种“留白设计”正是工程思维的体现。对比Gradle里一行apply plugin: 'com.android.application',Ant脚本让你看清每一克字节是怎么被塞进APK的。

3. 核心细节解析与实操要点:从源码到运行的关键断点

3.1 src目录结构:分层清晰,职责单一

src/com/example/weather/下的Java文件不是随意堆砌的,而是严格遵循MVC雏形(虽未用框架,但思想到位):

  • WeatherActivity.java:纯粹的View层。只做三件事:setContentView(R.layout.activity_weather)绑定布局;findViewById()获取控件引用;updateUI(WeatherData data)刷新文本和图标。它不碰网络、不解析JSON、不处理城市定位,所有数据都由外部注入。
  • NetworkUtil.java:Model层核心。提供静态方法fetchWeatherData(String city)返回原始JSON字符串,内部封装了HttpURLConnection的所有异常处理(IOExceptionMalformedURLExceptionProtocolException)。它甚至考虑到了弱网场景:conn.setInstanceFollowRedirects(false)禁用重定向,避免302跳转导致超时。
  • JsonParser.java:Controller层桥梁。接收JSON字符串,用JSONObjectJSONArray手动解析,提取temperaturehumiditywind_speed等字段,组装成WeatherData对象(一个只有getter/setter的POJO)。这里没有Gson的fromJson()魔法,你必须写jsonObj.optString("temp", "N/A"),明确知道每个字段可能为空。
  • LocationUtil.java:独立工具类。用LocationManager获取GPS或Network定位,但做了关键防护:if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER))检查权限,locationManager.requestLocationUpdates(..., 60000, 10, this)设置最小更新间隔60秒、最小位移10米,避免高频定位耗电。

这种分层不是为了炫技,而是为了可测试性。你可以单独跑JsonParserTest.java(项目虽未提供测试类,但结构已预留),传入模拟JSON字符串,断言getTemperature()返回值是否正确;也可以用Mockito mockLocationManager,验证定位失败时WeatherActivity是否显示“定位失败”Toast。现代MVVM强调解耦,而这个“古老”项目用最原始的方式教会你:解耦的第一步,是让每个类只做一件事。

3.2 res资源目录:密度适配的物理实现

res/目录下的布局和资源不是静态快照,而是动态适配的物理载体。以主界面activity_weather.xml为例:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <TextView android:id="@+id/tv_city" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="24sp" <!-- sp单位,随系统字体缩放 --> android:textStyle="bold" /> <ImageView android:id="@+id/iv_weather_icon" android:layout_width="64dp" android:layout_height="64dp" android:layout_marginTop="16dp" android:src="@drawable/ic_weather_sunny" /> <!-- 自动匹配drawable-xxx --> <TextView android:id="@+id/tv_temperature" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="48sp" <!-- 大字号,需更高清图 --> android:layout_marginTop="8dp" /> </LinearLayout>

这里藏着三个适配要点:第一,textSizesp而非dp,确保用户在系统设置里调大字体时,天气文字同步放大;第二,ImageViewandroid:src引用@drawable/ic_weather_sunny,系统会根据设备密度自动从drawable-xxx目录加载对应尺寸图片;第三,padding="16dp"margin="8dp"dp,保证物理间距一致。但注意:64dp宽高的ImageView在ldpi设备上会加载36×36图,显得小;在xxhdpi设备上若没提供drawable-xxhdpi,系统会从xhdpi(96×96)放大1.5倍到144×144,导致模糊。所以项目虽只到xhdpi,但已覆盖当时95%的主流机型(2014年数据)。

values/strings.xml里的城市名定义也暗藏玄机:

<string name="city_beijing">北京</string> <string name="city_shanghai">上海</string> <string name="city_guangzhou">广州</string>

这些字符串不是硬编码在Java里,而是通过getString(R.string.city_beijing)获取。好处是:1)支持多语言,只需加values-zh-rCN/values-en/目录;2)修改城市名不用改Java代码,运维可直接替换APK里的resources.arsc;3)aapt编译时会做字符串去重,减小APK体积。这种“资源外置”思想,是大型项目维护性的基石。

3.3 AndroidManifest.xml:权限与组件的契约声明

AndroidManifest.xml不是模板填充,而是应用与系统的法律契约。这个项目的声明极简但精准:

<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme"> <activity android:name=".WeatherActivity" android:label="@string/app_name" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>

四个权限缺一不可:INTERNET是网络请求前提;ACCESS_NETWORK_STATE用于NetworkUtil中判断网络是否可用(ConnectivityManager.getActiveNetworkInfo() != null);ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION是定位所需,且项目在WeatherActivity.onCreate()里做了运行时权限检查(Android 6.0+兼容逻辑)。android:exported="true"在Android 12+是强制要求,否则无法作为Launcher启动。android:allowBackup="true"允许用户用adb backup备份数据,但项目没用SharedPreferences存敏感信息,所以安全无虞。

最易被忽略的是<application>android:icon属性。它指向@drawable/ic_launcher,而ic_launcher.png同样存在于所有drawable-xxx目录中。这意味着当你在Google Play上传APK时,不同密度设备下载的APK,其桌面图标清晰度是原生匹配的——不是靠服务器下发多套图标,而是编译时就固化在资源里。这种“一次编译,多端适配”的思路,至今仍是资源管理的黄金准则。

4. 实操过程与核心环节实现:从零开始跑通全流程

4.1 环境准备:SDK版本与IDE兼容性

这个项目基于Android SDK 4.4(API 19)构建,project.properties中写着target=android-19。这意味着你无需安装最新版Android Studio,用Android Studio 3.0+ 或 Eclipse ADT 23.0.7即可。但要注意三个环境变量配置:

  1. local.properties:必须手动创建,内容为:
    sdk.dir=/Users/yourname/Library/Android/sdk # macOS路径 # 或 Windows: sdk.dir=C\:\\Users\\yourname\\AppData\\Local\\Android\\Sdk
    这是build.xml读取SDK路径的唯一来源,缺失会导致ant debugsdk.dir is not specified

  2. JDK版本:项目用Java 7语法(如try-with-resources未使用),所以JDK 8即可,无需JDK 11。在Android Studio中,File > Project Structure > SDK Location里确认JDK路径指向JDK 8。

  3. Ant版本build.xml用的是Apache Ant 1.8+,macOS自带ant命令,Windows需下载Ant 1.9.14并配置ANT_HOME环境变量。

提示:如果你用Android Studio 4.0+,导入时会提示“Project uses Ant build system, but Gradle is recommended”。此时点击“Cancel”,然后File > New > Import Project,选择项目根目录,AS会自动识别为Ant项目并加载build.xml。不要强行转换为Gradle,否则会丢失-dex等关键target。

4.2 修改天气接口:对接现代免费API

原始代码里的http://api.example.com/weather已失效,但替换极其简单。以Open-Meteo免费API为例(无需Key,每小时10000次调用):

  1. NetworkUtil.java中修改fetchWeatherData(String city)方法:
    ```java
    // 替换旧URL
    // URL url = new URL(“http://api.example.com/weather?city=” + city);

// 新URL:用经纬度查询(需先获取城市坐标)
String latLon = getLatLonByCity(city); // 此方法需自行实现,可用免费地理编码API
URL url = new URL(“https://api.open-meteo.com/v1/forecast?latitude=” + latLon.split(“,”)[0] + “&longitude=” + latLon.split(“,”)[1] + “&current=temperature_2m,relative_humidity_2m,wind_speed_10m&timezone=auto”);
```

  1. JsonParser.java中解析逻辑改为:
    java JSONObject jsonObj = new JSONObject(jsonStr); JSONObject current = jsonObj.getJSONObject("current"); WeatherData data = new WeatherData(); data.setTemperature(current.getDouble("temperature_2m") + "°C"); data.setHumidity(current.getDouble("relative_humidity_2m") + "%"); data.setWindSpeed(current.getDouble("wind_speed_10m") + " km/h");

关键点:Open-Meteo返回的是JSON,字段名与原始接口不同,但JsonParser的结构让你只需改几行赋值代码,无需重构整个解析逻辑。这就是良好分层的价值——接口变了,但解析器的壳没变。

4.3 Ant构建与APK生成:七步走完

在项目根目录打开终端,执行以下命令(确保ant命令可用):

  1. 初始化环境ant setup
    创建gen/bin/目录,生成R.java。若报错aapt is not found,检查local.propertiessdk.dir是否正确,且sdk/build-tools/下有29.0.3或类似版本文件夹。

  2. 编译Java代码ant compile
    编译src/gen/下的所有.java,输出.classbin/classes/。若报错cannot find symbol R.id.tv_city,说明R.java未生成,重跑ant setup

  3. 生成Dex文件ant dex
    调用dx工具将bin/classes/转为bin/classes.dex。若报错Conversion to Dalvik format failed,通常是libs/下jar包冲突,删除libs/中所有jar重试。

  4. 打包资源ant package-res
    aapt打包res/资源为bin/resources.ap_。此步会校验所有@drawable/xxx是否存在,若drawable-xhdpi/ic_weather_sunny.png缺失,会报No resource found

  5. 生成未签名APKant debug
    合并classes.dexresources.ap_AndroidManifest.xml,生成bin/WeatherApp-debug-unaligned.apk

  6. 签名APKant release
    先用keytool生成密钥(项目已提供key.keystore,密码android),再用jarsigner签名,输出bin/WeatherApp-release-unsigned.apk

  7. 对齐优化zipalign -v 4 bin/WeatherApp-release-unsigned.apk bin/WeatherApp-release.apk
    最终得到可发布的WeatherApp-release.apk,大小约1.2MB。

注意:ant release需要key.keystore文件。项目包里已包含,但若你生成自己的密钥,需修改ant.properties中的key.storekey.alias值,并在build.xml中更新jarsigner<arg value="-keystore"/>参数。

4.4 ProGuard混淆实战:保活关键类

proguard-project.txt是APK瘦身和防逆向的核心。项目中的规则看似简单,实则精准:

-optimizationpasses 5 -dontusemixedcaseclassnames -dontskipnonpubliclibraryclasses -dontpreverify -verbose -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* -keep public class * extends android.app.Activity -keep public class * extends android.app.Application -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider -keep public class * extends android.app.backup.BackupAgentHelper -keep public class * extends android.preference.Preference -keep public class com.android.vending.licensing.ILicensingService -keepclasseswithmembernames class * { native <methods>; } -keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet); } -keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet, int); } -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); } -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } # 保留WeatherData类及其字段,防止JSON解析失败 -keep class com.example.weather.WeatherData { *; } -keep class com.example.weather.JsonParser { *; }

重点在最后两行:WeatherData是JSON解析的目标POJO,若被混淆成a.classjsonObj.optString("temperature")就无法映射到a.a()方法,导致空指针。JsonParser同理,它的parse(String json)方法若被重命名,WeatherActivity调用就会失败。所以-keep不是保守策略,而是功能刚需。实测:若删掉这两行,APK能安装,但进入App后TextView全部显示“N/A”,Logcat里全是NoSuchMethodException

5. 常见问题与排查技巧实录:那些让我熬夜的坑

5.1 网络请求失败:90%的问题出在这三个地方

现象可能原因排查命令/方法解决方案
java.net.UnknownHostException: api.example.comDNS解析失败或域名已失效ping api.example.com(终端);adb shell ping -c 3 api.example.com(设备)替换为可用API,如Open-Meteo;检查AndroidManifest.xml是否漏了INTERNET权限
java.net.SocketTimeoutException: timeoutsetConnectTimeout()setReadTimeout()太短NetworkUtil.java中临时加大超时值(如conn.setConnectTimeout(30000)设定合理值:连接超时10秒,读取超时15秒;弱网环境增加重试逻辑
android.os.NetworkOnMainThreadExceptionfetchWeatherData()在主线程调用查看WeatherActivity.java中调用位置,是否在onCreate()里直接调用必须用AsyncTask.execute()new Thread().start()包裹,项目中AsyncTask已封装好

独家心得:我在调试时发现,某些国产ROM(如MIUI)会拦截非HTTPS请求。即使你用了HttpURLConnection,只要URL是http://开头,MIUI会静默丢弃请求。解决方案是强制升级到HTTPS,或在AndroidManifest.xml中添加<application android:usesCleartextTraffic="true">(仅限调试,发布版必须用HTTPS)。

5.2 图片显示异常:密度适配的视觉陷阱

现象根本原因快速验证法修复步骤
图标在高端机上模糊drawable-xhdpi目录缺失,系统用mdpi图放大adb shell dumpsys window windows \| grep mCurrentFocus确认当前Activity;adb shell ls /data/data/com.example.weather/files/查看资源加载路径补全所有密度目录,用Sketch或Figma按比例导出(mdpi=1x, hdpi=1.5x, xhdpi=2x)
启动图拉伸变形splash.png放在drawable/而非drawable-xhdpi/AndroidManifest.xml中检查android:icon指向的资源路径splash.png移至drawable-xhdpi/,并在其他密度目录放对应尺寸图
字体图标显示方块drawable-nodpi/weather-icons.ttf未正确加载adb logcat \| grep "Typeface"查看字体加载日志确认assets/fonts/路径正确;Typeface.createFromAsset()中路径为"fonts/weather-icons.ttf"

避坑技巧:不要依赖Android Studio的“New Image Asset”向导生成launcher图标——它默认只生成mipmap-xxx,而这个项目用的是drawable-xxx。必须手动创建目录并拖入图片,否则aapt打包时找不到资源。

5.3 Ant构建失败:构建脚本的隐性依赖

错误信息定位方法根本原因修复方案
BUILD FAILED ... sdk.dir is not specified检查local.properties是否存在local.properties文件缺失或路径错误手动创建local.properties,写入sdk.dir=你的SDK路径
aapt is not foundls $ANDROID_HOME/build-tools/build-tools目录下无可用版本(如只有30.0.3,但build.xml指定29.0.3修改build.xml<property name="aapt" location="${sdk.dir}/build-tools/30.0.3/aapt"/>,或安装对应版本
Conversion to Dalvik format failedls bin/classes/查看是否有.class文件libs/下jar包与SDK内置库冲突(如重复的support-v4.jar删除libs/中所有jar,项目本身不依赖第三方库
No resource found that matches the given namegrep -r "ic_weather_sunny" res/drawable-xxx/ic_weather_sunny.png在某个密度目录缺失find . -name "ic_weather_sunny.png"确认缺失目录,补全

实操心得ant clean并不能彻底清理。我遇到过bin/目录下残留classes.dex导致新编译失败。终极清理命令是:rm -rf bin/ gen/ && ant setup。另外,build.xml<property name="out.dex" value="${out.absolute.dir}/classes.dex"/>out.absolute.dir必须是绝对路径,相对路径在某些Linux发行版上会失败。

5.4 UI显示空白:生命周期与数据绑定的时序问题

现象:App启动后界面一片空白,Logcat无报错。
排查路径:
1. 在WeatherActivity.javaonCreate()末尾加Log.d("Weather", "onCreate end");
2. 在updateUI(WeatherData data)开头加Log.d("Weather", "updateUI called with " + data.getTemperature());
3. 若第一步有日志,第二步无日志,说明updateUI()根本没被调用

真相AsyncTask执行后,onPostExecute()中调用updateUI(),但此时WeatherActivity可能已被系统回收(如横竖屏切换)。项目中未处理onRetainNonConfigurationInstance(),导致数据丢失。

修复方案(兼容老SDK)

// 在WeatherActivity中重写 @Override public Object onRetainNonConfigurationInstance() { return weatherData; // 保存WeatherData对象 } // 在onCreate()中恢复 if (getLastNonConfigurationInstance() != null) { weatherData = (WeatherData) getLastNonConfigurationInstance(); updateUI(weatherData); }

这个坑揭示了一个本质:UI层必须假设数据随时可能丢失,所有状态都要可重建。现代ViewModel正是为解决此问题而生,而这个“古老”项目用最原始的方式逼你直面它。

6. 项目延伸与教学价值:不止于天气App

这个天气App源码的价值,远超一个可运行的Demo。它是一块Android开发的“活化石”,封存了2014-2016年行业通用的最佳实践。我用它做过三类教学实验,效果显著:

第一,网络通信原理课:让学生删掉NetworkUtil.java,自己从零实现fetchWeatherData()。90%的人第一版会犯两个错:1)在onCreate()里直接调用,触发NetworkOnMainThreadException;2)没设超时,导致ANR。当他们亲手写出new Thread(){...}.start()并捕获IOException时,对“主线程不能做IO”的理解,比听十遍理论都深刻。

第二,资源适配实训课:发给学生一个drawable-xhdpi/icon.png(96×96),要求他们手动计算并生成ldpi(36×36)、mdpi(48×48)、hdpi(72×72)三套图。很多人第一次意识到:dp不是像素,而是“密度无关像素”,1dp = 1px @ mdpi1dp = 2px @ xhdpi。当他们在values-sw600dp/dimens.xml里定义<dimen name="card_margin">16dp</dimen>,并在activity_weather.xml中用android:layout_margin="@dimen/card_margin"时,“响应式布局”的概念自然浮现。

第三,构建流程拆解课:让学生用javap -c bin/classes/com/example/weather/WeatherActivity.class反编译字节码,再对比bin/classes.dex的Dalvik指令。当他们看到invoke-static {v0}, Lcom/example/weather/NetworkUtil;->fetchWeatherData(Ljava/lang/String;)Ljava/lang/String;这条指令时,“Java代码如何变成手机能执行的指令”不再抽象。build.xml里的<exec>标签,此刻成了连接高级语言与机器世界的桥梁。

最后分享一个小技巧:把这个项目导入Android Studio后,右键src目录 →Refactor > Rename,把包名从com.example.weather改成你的域名(如com.yourname.weather)。AS会自动更新AndroidManifest.xmlbuild.xmlR.java中所有引用。这个操作看似简单,却是理解Android项目模块化和命名空间的起点——你的代码,从此有了自己的身份证。

这个天气App不会告诉你Kotlin怎么写协程,但它会用最朴实的Java代码告诉你:网络请求的本质是Socket连接,资源适配的本质是像素密度匹配,APK构建的本质是字节码打包。当你能徒手写出HttpURLConnection并让它稳定运行,当你能为五种屏幕密度准备五套图标,当你能读懂build.xml里每一行<arg>的含义——你就真正掌握了Android开发的底层逻辑。而这,正是所有高级框架赖以生存的地基。

本文还有配套的精品资源,点击获取

简介:一套可直接编译运行的Android天气查询应用源码,基于原生Java开发,不依赖任何第三方SDK。通过标准HTTP接口(如HttpURLConnection)拉取实时天气数据,完整实现城市定位、天气信息展示、温度/湿度/风速等基础字段解析。工程已做好全屏幕密度适配,包含drawable-ldpi、mdpi、hdpi、xhdpi和nodpi五套资源目录,layout布局文件结构清晰,values中定义了字符串与样式资源。项目自带AndroidManifest.xml配置、proguard混淆规则、local.properties本地环境配置及build.xml Ant构建脚本,支持Eclipse与Android Studio双环境导入调试。src目录下代码分层明确,涵盖Activity主界面、网络工具类、JSON解析逻辑与UI更新机制;res中layout提供简洁卡片式天气展示页;assets未使用但预留扩展位。配套有CSDN技术文档说明关键接口调用方式与数据映射关系,适合用于Android基础开发教学、网络通信实践、资源适配训练及APK打包流程学习。


本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询