从 AOSP 源码角度分析 Android 定位性能问题

从 AOSP 源码看 Android 定位性能问题

源码基准:AOSP android-17.0.0_r1

  • platform/frameworks/base @ 23149ba144e8
  • platform/hardware/interfaces @ 32a116715a69

写作日期:2026-10-10。文中所有类名、方法名、常量值、行号均已对照该标签实际源码逐条核实;行号以 (文件:行号) 形式标注,便于直接跳转查证。

1. 为什么定位性能问题难排查

定位链路横跨应用进程、system_server、GNSS HAL 进程和基带/芯片四层,中间夹杂 Binder IPC、请求合并、权限消毒、省电策略、占空比调度等多重机制。logcat 里能看到的 LocationManager 调用栈只是入口,真正的瓶颈往往在更深的位置:LPM 的合并请求形态、GNSS Provider 的串行线程、HAL 的同步 Binder 调用,或者干脆是应用自己的注册模式。

本文完全依据 android-17.0.0_r1 源码,按「调用链逐层下钻 → 每层的性能热点 → 排查手段」的结构展开。

2. 定位链路全景(源码结构)

复制代码
App 进程: LocationManager.requestLocationUpdates()
   │  Binder(ILocationManager.aidl)
   ▼
system_server: LocationManagerService(权限校验、请求消毒、Provider 装配)
   │  per-provider
   ▼
LocationProviderManager(请求 N→1 合并、分发管线、模糊化、节流)
   │
   ▼
AbstractLocationProvider 体系
   ├─ GnssLocationProvider(GPS,进程内)
   ├─ ProxyLocationProvider(network / fused,绑定外部服务,通常为 GMS)
   └─ PassiveLocationProvider / PassiveLocationProviderManager(被动监听)
   │
   ▼
GnssNative(Java 单例)──JNI──▶ libservices.core-gnss
                                    │(静态库,经 libservices.core 整体链入 libandroid_servers.so)
                                    ▼
                          Binder ──▶ IGnss HAL 进程(AIDL,冻结至 V7)

几个容易被旧资料误导、但源码里很清楚的事实:

  • 不存在 ServiceManager.getService("gps") 这类服务名寻址 。GNSS HAL 连接由 JNI 层(services/core/jni/gnss/)建立,Java 侧只面对 GnssNative。JNI 静态库名为 libservices.core-gnss(services/core/jni/gnss/Android.bp:11),经 libservices.core 以 whole_static_libs 链入 libandroid_servers(services/Android.bp:462-465)。
  • GPS Provider 可以被 overlay 替换 。config_useGnssHardwareProvider 为 false 时,LMS 先尝试用 ProxyLocationProvider.create(..., ACTION_GNSS_PROVIDER, ...) 绑定外部 GNSS Provider;未找到才回退到进程内 GnssLocationProvider。overlay 存在时,原始 HAL 以 GPS_HARDWARE_PROVIDER(需 LOCATION_HARDWARE 权限)单独暴露,源码注释明确说明"GNSS HAL 只支持单一客户端"(LocationManagerService.java:543-576)。
  • network 必须先于 GNSS 初始化 。onSystemThirdPartyAppsCanStart() 的注释写明 GPS Provider 对 network Provider 有硬依赖(LocationManagerService.java:496-497)。完整启动顺序:network → fused → GnssNative + GnssManagerService → GnssLocationProvider(或 overlay)→ geocode → population density → hardware AR(Flags.disableHardwareAr() 门控)→ geofence(LocationManagerService.java:495-615)。
  • GnssManagerService 是 GNSS 专属 API 的汇聚点 :状态回调、NMEA、原始测量(GnssMeasurementsProvider)、导航电文、天线信息、硬件地理围栏(内部类 GnssGeofenceHalModule → GeofenceHardwareImpl)、GnssMetrics 健康监控,并在 onSystemReady() 中无条件绑定 ProxyGnssAssistanceProvider 注册结构化 assistance 回调(GnssManagerService.java:106-113)。

3. 应用层入口:一次注册到底发生了什么

3.1 真实调用链

LocationManager.requestLocationUpdates(provider, request, executor, listener)(LocationManager.java:1579-1607)做三件事:

  1. 参数校验(provider / request 非空);

  2. Transport 复用 :静态表 sLocationListeners(listener → WeakReference<LocationListenerTransport>)查找该 listener 是否已有包装,没有才 new LocationListenerTransport(listener, executor)------同一个 listener 重复注册会复用同一个 Binder Stub 并更新 executor(LocationManager.java:1587-1602);

  3. 跨进程调用:

    mService.registerLocationListener(provider, locationRequest, transport,
    packageName, attributionTag, AppOpsManager.toReceiverId(listener))

对应 AIDL(ILocationManager.aidl:57-61):

aidl 复制代码
void registerLocationListener(String provider, in LocationRequest request,
        in ILocationListener listener, String packageName,
        @nullable String attributionTag, String listenerId);
void registerLocationPendingIntent(String provider, in LocationRequest request,
        in PendingIntent pendingIntent, String packageName,
        @nullable String attributionTag);

注意:Binder 接口上没有 requestLocationUpdates,PendingIntent 变体叫 registerLocationPendingIntent(不是 registerLocationListenerPendingIntent)。

3.2 应用层的性能含义

  1. 每次注册/注销都是一次 Binder 事务 。在 onResume/onPause 中反复注册注销,事务量线性累积,且会推动 GNSS 会话反复 start/stop(见 §6.3)。
  2. Transport 泄漏 :不调用 removeUpdates 时,Stub 驻留在 system_server 的注册表里,回调持续投递,AppOps 持续记账。
  3. PendingIntent 请求的系统 API 禁令 :BLOCK_PENDING_INTENT_SYSTEM_API_USAGE(change id 169887240L)生效时,PendingIntent 请求禁止 isLowPower、isHiddenFromAppOps、isLocationSettingsIgnored 和非空 WorkSource,违反直接抛 SecurityException。源码注释解释了原因:PendingIntent 没有可随权限回收而被杀掉的宿主进程(LocationManagerService.java:957-971)。

4. LocationManagerService:并发模型与校验开销

4.1 真实的锁模型

java 复制代码
// LocationManagerService.java:257
final Object mLock = new Object();
// LocationManagerService.java:287
final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers = ...;
  • mLock 是普通对象锁 + synchronized,不是 ReentrantLock;它保护的是 LMS 自身的少量状态(如 mLocationUsageLogger 等 @GuardedBy("mLock") 字段)。
  • Provider 查找走 mProviderManagers 的遍历------CopyOnWriteArrayList 读无锁,只有增删 Provider(addLocationProviderManager)才 synchronized (mProviderManagers)(LocationManagerService.java:377-405)。
  • 结论 :LMS 层的锁竞争比直觉轻得多。真正的串行点在 LPM 内部(ListenerMultiplexer 的 mMultiplexerLock 与各 Registration 状态锁)和 GNSS Provider 的单线程 executor。

4.2 请求消毒(每次注册都执行)

registerLocationListener() → validateLocationRequest()(LocationManagerService.java:981-1055)逐项处理:

请求属性 要求 不满足时
非空 WorkSource UPDATE_DEVICE_STATS SecurityException
isLowPower LOCATION_HARDWARE(LOW_POWER_EXCEPTIONS 变更生效时强制;否则静默置 false) SecurityException / 消毒
isHiddenFromAppOps UPDATE_APP_OPS_STATS SecurityException
isAdasGnssBypass automotive 设备 + GPS Provider + bypass 权限 IllegalArgumentException
isLocationSettingsIgnored bypass 权限 SecurityException

bypass 权限校验本身已是纯权限检查------LocationPermissions.enforceBypassPermission() 只查 LOCATION_BYPASS,无 flag 参与(LocationPermissions.java:140-146)。注意 location_bypass flag 仍保留在 location.aconfig 中(is_exported),只是定位服务代码内已无调用点。

4.3 后台与省电限制的三套机制

  1. 后台节流白名单 :SettingsHelper.getBackgroundThrottlePackageWhitelist(),豁免包不受后台限制(LocationManagerService.java:855)。
  2. 省电模式状态机 :LPM 监听 LocationPowerSaveModeHelper,五种模式在源码中的常量为 PowerManager.LOCATION_MODE_NO_CHANGE / LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF / LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF / LOCATION_MODE_FOREGROUND_ONLY / LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF(Android 15 起迁入 android.os.PowerManager,LPM 在 LocationProviderManager.java:2361, 2561 处分支处理)。
  3. COARSE 硬下限 :MIN_COARSE_INTERVAL_MS = 10 * 60 * 1000,仅持粗略权限的注册,间隔与最小间隔都被钳制到 10 分钟(LocationProviderManager.java:161, 713-718)。

4.4 静止节流(Android 17 已大幅收窄)

addLocationProviderManager() 中只有同时满足三个条件才会包 StationaryThrottlingLocationProvider:Flags.keepGnssStationaryThrottling() 开启 且 Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE == 1(手表默认 0、手机默认 1)且 目标是 GPS Provider(LocationManagerService.java:384-400)。network/fused 不再被包裹。

5. LocationProviderManager:性能问题的核心战场

LPM(3123 行)是 system_server 侧定位代码中最大的类 (作为参照,LocationManager.java 为 3745 行但运行在应用进程)。每个命名 Provider 一个实例,继承 ListenerMultiplexer。

5.1 关键常量(全部位于 155--180 行,逐字引用)

java 复制代码
private static final long WAKELOCK_TIMEOUT_MS = 30 * 1000;                 // :155
private static final long MIN_COARSE_INTERVAL_MS = 10 * 60 * 1000;         // :161
private static final long MAX_HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;      // :164
private static final long MAX_CURRENT_LOCATION_AGE_MS = 30 * 1000;         // :167
private static final long MAX_GET_CURRENT_LOCATION_TIMEOUT_MS = 30 * 1000; // :170
private static final float FASTEST_INTERVAL_JITTER_PERCENTAGE = .10f;      // :173
private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 30 * 1000;       // :176
private static final long MIN_REQUEST_DELAY_MS = 30 * 1000;                // :180

5.2 请求合并:木桶效应依然成立

mergeRegistrations()(LocationProviderManager.java:2405)把 N 个注册合并成一个 ProviderRequest,经 AbstractLocationProvider.onSetRequest(ProviderRequest)(AbstractLocationProvider.java:338)下发。合并取"最严格"需求:一个高频 FINE 注册会把合并后的请求顶到高频,GNSS 芯片随之高频工作,全体 FINE 监听者收到高频回调。COARSE 注册被 10 分钟下限保护,不受拖累。

合并还叠加 10% 最快间隔抖动(上限 30 秒,LocationProviderManager.java:973-975),避免多设备同时唤醒芯片------这是平台级的防"惊群"设计。

5.3 分发管线(真实路径)

AbstractLocationProvider.reportLocation() → LocationProviderManager.onReportLocation()(LocationProviderManager.java:2657):

  1. processReportedLocation():校验合法性;若位置缺 MSL 海拔且有椭球高,经 AltitudeConverter.tryAddMslAltitudeToLocation() 补海拔(DeviceConfig enable_location_provider_manager_msl 门控,默认开;异步回填补缓存)(LocationProviderManager.java:2700-2735);
  2. setLastLocation() 更新 last known(:2685);
  3. deliverToListeners() 逐注册过滤:激活状态 → 权限等级 → COARSE 模糊化 → 最小间隔(含抖动)→ maxUpdates;
  4. 经 LocationTransport(listener 回调 / PendingIntent / getCurrentLocation 三种实现)投递,投递期间持 30 秒超时唤醒锁(:1035)。

5.4 粗略位置模糊化的开销

LocationFudger(fudger/LocationFudger.java):

  • 随机偏移每 1 小时 更新一次,每次只变化 3% (CHANGE_PER_INTERVAL = 0.03,:47-51),再吸附网格------偏移"缓慢漂移"是为了防止多次采样取平均还原精确位置;
  • 网格边长默认 2 km (DEFAULT_COARSE_LOCATION_ACCURACY_M = 2000.0f,SystemSettingsHelper.java:74),经 Settings.Secure.LOCATION_COARSE_ACCURACY_M 可配,下限 200 m (MIN_ACCURACY_M,LocationFudger.java:43);
  • 人口密度模式:LocationFudgerCache 咨询 ProxyPopulationDensityProvider,城区用更细的 S2 单元、农村用更粗的单元。density_based_coarse_locations / population_density_provider 两个 flag 已从 location.aconfig 移除,该路径不再受门控。

每次向 COARSE 注册投递都要过一次模糊化(带缓存),高频 + 大量 COARSE 注册时这部分 CPU 开销可观。

6. GnssLocationProvider 与 HAL:时延与功耗的决定层

6.1 线程模型

GnssLocationProvider 的所有 HAL 回调与请求处理串行在 provider 自己的 executor 线程上。GnssNative 定义 16 组回调接口(Base/Status/SvStatus/Location/Nmea/Measurement/AntennaInfo/NavigationMessage/Geofence/Time/LocationRequest/Psds/GnssAssistance/AGps/Notification/PowerStats,GnssNative.java:159-309),GnssLocationProvider 与 GnssManagerService 各取所需。任何一次慢操作(PSDS 下载、同步 Binder 阻塞)都会延迟其后所有位置上报------这是"定位慢"最常见的系统侧根因。

6.2 关键时序常量(GnssLocationProvider.java:170-237,逐字引用)

java 复制代码
private static final long LOCATION_UPDATE_MIN_TIME_INTERVAL_MILLIS = 1000;    // 1 Hz 下限
private static final long LOCATION_UPDATE_DURATION_MILLIS = 10 * 1000;        // 位置注入会话时长
private static final int  EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER = 3;  // 紧急模式 ×3
private static final long MAX_BATCH_LENGTH_MS = DateUtils.DAY_IN_MILLIS;      // 批量上限 1 天
private static final int  NO_FIX_TIMEOUT = 60 * 1000;                         // 60s 无 fix 即停
private static final int  GPS_POLLING_THRESHOLD_INTERVAL = 10 * 1000;         // 占空比阈值
private static final long RETRY_INTERVAL = 5 * 60 * 1000;                     // PSDS 退避起点
private static final long MAX_RETRY_INTERVAL = 4 * 60 * 60 * 1000;            // 退避上限

6.3 占空比:一个常被忽略的前提

源码里占空比逻辑都带 !mGnssNative.getCapabilities().hasScheduling() 前缀:

java 复制代码
// GnssLocationProvider.java:1419-1422
if (!mGnssNative.getCapabilities().hasScheduling() && mStarted
        && mFixInterval > GPS_POLLING_THRESHOLD_INTERVAL) {
    hibernate();   // 拿到 fix 且间隔 >10s → 休眠,AlarmManager 下次唤醒
}

也就是说:

  • HAL 声明了调度能力(CAPABILITY_SCHEDULING)时 ,合并后的间隔直接经 setPositionMode 下发(:1065-1067, 1251-1252),占空比由芯片固件自行实现,框架不干预;
  • HAL 不具备调度能力时 ,框架在 >10 秒间隔的场景下手工执行 Navigating → Hibernate 循环,每次唤醒伴随一次热启动。此场景下应用频繁注册/注销会把 Provider 推进反复 start/stop/再捕获循环,宏观表现为"定位慢、首 fix 时间长"。

注意现行代码没有显式的状态枚举,Idle/Navigating/Hibernate 是由 mStarted、hibernate() 与 AlarmManager 闹钟组合出的隐式状态机。

6.4 辅助数据(A-GNSS)链路

  • PSDS :HAL 经 PsdsCallbacks.onRequestPsdsDownload(int psdsType) 请求(GnssNative.java:275-277),GnssPsdsDownloader 从 GnssConfiguration 中的 LONGTERM_PSDS_SERVER_1..3 / NORMAL_PSDS_SERVER / REALTIME_PSDS_SERVER(GnssConfiguration.java:84-88)下载注入,失败按 5 分钟→4 小时指数退避。PsdsType 为 1-based:LONG_TERM=1 / NORMAL=2 / REALTIME=3;按 AIDL 注释,三类数据的有效期量级分别是"数小时至数天"、"数小时"、"分钟级"(PsdsType.aidl)。
  • 配置加载顺序 :CarrierConfig → 资源 overlay → /vendor/etc/gps_debug.conf → /etc/gps_debug.conf,后加载者覆盖先加载者,即 /etc/gps_debug.conf 优先级最高(GnssConfiguration.java:278-296)。SIM/运营商配置变化触发 subscriptionOrCarrierConfigChanged() 重载。
  • 时间注入 :NetworkTimeHelper(NTP)→ injectTime();位置注入 :network fix 经 injectLocation() / injectBestLocation() 回灌(GnssLocationProvider.java:559, 660-662, 714, 742-751)。
  • 结构化 assistance :GnssManagerService.onSystemReady() 无条件绑定 ProxyGnssAssistanceProvider 并注册 GnssAssistanceCallbacks(GnssManagerService.java:106-113);gnss_assistance_interface_jni flag 已移除。

6.5 已废弃的批次 API

startGnssBatch()(@SystemApi,已废弃)内部就是一次普通的 registerLocationListener,用 setMaxUpdateDelayMillis(intervalMs × 批次大小) 实现 on-chip batching(LocationManagerService.java:723-749)。这从侧面说明:应用层想要批量定位,直接用 LocationRequest.Builder.setMaxUpdateDelayMillis() 即可。

7. HAL 层:IGnss AIDL V7

7.1 版本与方法面

  • AIDL 冻结至 V7 (aidl_api/android.hardware.gnss/ 下有 1--7 + current;JNI 链接 android.hardware.gnss-V7-cpp);Android 17 的 FCM 声明接受 2--7。
  • 根接口方法:setCallback / close / start / stop / startSvStatus / stopSvStatus / startNmea / stopNmea / setPositionMode(PositionModeOptions) / injectTime / injectLocation / injectBestLocation / deleteAidingData 及一组 getExtension*()。PositionModeOptions 把 mode/recurrence/minIntervalMs/preferredAccuracyMeters/preferredTimeMs/lowPowerMode 打包为单一 parcelable(IGnss.aidl:281-314)。
  • 扩展接口多数 @nullable(未实现返回 null),但 getExtensionGnssConfiguration / getExtensionGnssDebug / getExtensionGnssAntennaInfo / getExtensionGnssVisibilityControl 未标 @nullable,HAL 必须实现。
  • 旧资料里的 IGnss.update() / updateConstellation() 不存在。

7.2 V7 新增与性能相关的能力位

IGnssCallback 新增 bit 16:CAPABILITY_ENGINE_RESTART_AFTER_POWER_MODE_CHANGE(IGnssCallback.aidl:100),SDK 侧由 GnssCapabilities.hasGnssEngineRestartAfterPowerModeChange() 暴露(flag gnss_capability_restart_engine_after_power_mode_change 门控)。它的性能含义:低功耗模式切换后引擎是否需要重启------需要重启的设备上,频繁在低功耗/普通模式间切换的代价接近重新定位,应用层应避免 interval/quality 的来回抖动。

同期转正的还有:GnssNavigationMessage.TYPE_IRN_L1(NavIC L1,flag gnss_api_navic_l1)、GnssStatus 的 codeType/elapsedRealtime(flag support_codetype_in_gnss_status)、QZSS SVID 范围 183,206→183,212(flag gnss_qzss_svid_range_extension)、结构化 assistance 的 IonexAssistance 与历书 toaSeconds 字段。

7.3 Binder 开销的三个断面

  1. system_server → 应用:每次位置投递一次事务;高频 × 多监听者场景下带宽可观。
  2. GnssNative → HAL :JNI 发起跨进程 Binder;start()/stop()/setPositionMode() 为同步调用,HAL 实现缓慢会直接阻塞 provider 线程(§6.1),放大为全局上报延迟。
  3. system_server ↔ fused/network(GMS) :ProxyLocationProvider 经 ServiceWatcher 绑定,服务死亡自动重绑;断连期间 Provider 上报 not-allowed,LPM 停止向客户端分发------表现为"定位突然没回调"。

8. 性能问题根源速查表

现象 最可能的层 源码机制 确认手段
首 fix 慢、定位时好时坏 GNSS Provider 占空比状态机被频繁注册/注销打乱(§6.3) 统计应用注册/注销频率;dumpsys location 看注册表变化
全系统定位都变慢 GNSS Provider 线程 provider 单线程串行,某个同步调用阻塞(§6.1、§7.3-2) kill -3 $(pidof system_server) 看线程栈
耗电高 LPM 合并 一个高频 FINE 注册顶高合并请求(§5.2) dumpsys location 看合并后的 ProviderRequest
后台定位被掐断 省电策略 LOCATION_MODE_FOREGROUND_ONLY 等(§4.3-2) 前台服务 + FOREGROUND_SERVICE_TYPE_LOCATION
COARSE 应用"不更新" 设计如此 10 分钟硬下限(§4.3-3) 检查权限等级与合并间隔
fused 定位突然无回调 绑定断连 ServiceWatcher 重绑期间 not-allowed(§7.3-3) dumpsys location 看 Provider 状态

9. 排查命令(均已在源码中核实)

bash 复制代码
# 全局视图:Provider 状态、全部注册、合并请求、事件日志
adb shell dumpsys location

# GNSS 指标(proto 输出;LocationManagerService.java:1640)
adb shell dumpsys location --gnssmetrics

# GNSS 配置(注意 /etc/gps_debug.conf 优先级最高)
adb shell cat /vendor/etc/gps_debug.conf

# 开关定位(LocationShellCommand.java:60)
adb shell cmd location set-location-enabled true

# 删除辅助数据做冷启动 TTFF 基准(GnssLocationProvider.java:1175)
adb shell cmd location send-extra-command gps delete_aiding_data

# system_server 线程栈
adb shell kill -3 $(pidof system_server)

10. 优化建议

应用侧

  • 导航级场景才用 1 秒 + QUALITY_HIGH_ACCURACY;普通场景间隔 ≥30 秒或 balanced。
  • 理解 10 秒阈值:间隔 >10 秒时,无调度能力的设备会进入框架占空比,省电但引入周期性热启动时延------按业务权衡,避免在 10 秒上下抖动 interval。
  • 及时 removeUpdates;利用 sLocationListeners 的复用语义(同 listener 重复注册不新增 Stub),但仍应避免高频注册/注销。
  • 批量场景用 setMaxUpdateDelayMillis()(§6.5),让芯片 on-chip batching,减少 AP 唤醒。
  • 后台持续定位必须前台服务 + FOREGROUND_SERVICE_TYPE_LOCATION。
  • 关注 hasGnssEngineRestartAfterPowerModeChange() 为 true 的设备:避免低功耗模式反复横跳。

系统/ROM 侧

  • 给 GNSS provider 线程的 onReportLocation / onSetRequest 入口出口打点,定位同步 Binder 阻塞源。
  • 监控 GnssMetrics(TTFF、CN0 已内建);必要时对 HAL setPositionMode 耗时设告警。
  • COARSE 注册占比高的产品,注意 LocationFudger + LocationFudgerCache 的投递时开销,确保密度缓存预热。
  • 调整合并策略需谨慎:10% 间隔抖动、10 分钟 COARSE 下限都是隐私/功耗的硬约束,不是可调参数。

11. 参考文件(android-17.0.0_r1)

文件 路径
LocationManager frameworks/base/location/java/android/location/LocationManager.java(3745 行)
ILocationManager frameworks/base/location/java/android/location/ILocationManager.aidl
LocationManagerService frameworks/base/services/core/java/com/android/server/location/LocationManagerService.java(2073 行)
LocationProviderManager .../location/provider/LocationProviderManager.java(3123 行)
AbstractLocationProvider .../location/provider/AbstractLocationProvider.java
GnssLocationProvider .../location/gnss/GnssLocationProvider.java(1883 行)
GnssManagerService / GnssConfiguration / GnssPsdsDownloader .../location/gnss/
GnssNative(Java)+ JNI .../location/gnss/hal/GnssNative.java、services/core/jni/gnss/
LocationFudger / LocationFudgerCache .../location/fudger/
功能 flag location/java/android/location/flags/location.aconfig
IGnss 及子接口 hardware/interfaces/gnss/aidl/android/hardware/gnss/
相关推荐
悟道子HD2 小时前
网络安全基本功——MySQL基础
android·mysql·web安全
纪念 2292 小时前
C++ string(三)
android·c++
纪念 2292 小时前
c++ string(最终篇)
android·c++
aqi0011 小时前
鸿蒙版本的电子书阅读APP开放源码啦
android·华为·ai编程·harmonyos·鸿蒙
墨天梦11 小时前
B19_SunnyWeather到阅读客户端
android·kotlin
美狐美颜sdk12 小时前
直播APP源码可以直接接入视频美颜sdk吗?技术方案详解
android·人工智能·音视频·美颜sdk·直播美颜sdk
用户693717500138415 小时前
2026,程序员的时代拐点到了
android·前端·后端
浪潮IT馆17 小时前
Android Studio 非最新版本下载安装教程
android·ide·android studio
xiaozongt198918 小时前
MasterGo + Claude Code 生成 Android 布局:完整实践指南
android