HarmonyOS开发:Android迁移鸿蒙——架构对比与迁移策略
HarmonyOS开发:Android迁移鸿蒙——架构对比与迁移策略
📌 核心要点:Android到鸿蒙的迁移不是简单的API替换,而是从命令式到声明式的思维范式转换,理解Activity→Ability、XML→ArkTS的映射关系是迁移的第一步。
背景与动机
你是一个Android老手,写了三五年Kotlin,闭着眼睛都能撸出一个MVVM架构。突然有一天,老板说:“咱们的App要上鸿蒙。”
你打开DevEco Studio,一看——ArkTS?Ability?@State?这都什么跟什么?
别慌。Android迁移鸿蒙,本质上就是换了一套开发范式。你积累的架构思维、设计模式、业务逻辑,大部分都能复用。真正要变的,是你写代码的"姿势"。
为什么必须迁移?市场数据摆在那儿——鸿蒙设备覆盖量已经突破8亿,而且还在加速。你不去适配,就等于把用户往外推。但迁移不是重写,如果你把现有Android代码推倒重来,那不是迁移,那是自杀。
这篇文章,就是帮你理清Android和鸿蒙的架构差异,给你一张清晰的迁移路线图。
核心原理
架构对比:两套完全不同的设计哲学
Android的架构,说白了就是"命令式+组件化"。你用XML写布局,用Kotlin/Java写逻辑,通过findViewById或者DataBinding把两者绑在一起。Activity是核心,所有东西都围着它转。
鸿蒙呢?声明式+分布式。你用ArkTS一把梭,UI和逻辑写在一起,用@State驱动刷新。Ability是核心,但它比Activity轻得多,更像一个容器。
先看这张对比图:
graph TB
subgraph Android["Android 架构"]
A1[Activity] --> A2[Fragment]
A1 --> A3[Service]
A1 --> A4[BroadcastReceiver]
A1 --> A5[ContentProvider]
A2 --> A6[XML Layout]
A2 --> A7[ViewModel]
A7 --> A8[LiveData]
end
subgraph HarmonyOS["鸿蒙架构"]
H1[UIAbility] --> H2[Page]
H1 --> H3[ExtensionAbility]
H2 --> H4[ArkTS 声明式UI]
H2 --> H5[@State/@Link]
H5 --> H6[AppStorage]
H3 --> H7[ServiceExtension]
H3 --> H8[DataShareExtension]
end
classDef androidStyle fill:#3DDC84,stroke:#2E7D32,color:#000,font-weight:bold
classDef harmonyStyle fill:#0A59F7,stroke:#0A3CC7,color:#fff,font-weight:bold
class A1,A2,A3,A4,A5,A6,A7,A8 androidStyle
class H1,H2,H3,H4,H5,H6,H7,H8 harmonyStyle
四大组件映射关系
Android有四大组件,鸿蒙怎么对应?直接看表:
| Android组件 | 鸿蒙对应 | 关键差异 |
|---|---|---|
| Activity | UIAbility | UIAbility更轻量,一个Ability可管理多个Page |
| Fragment | 自定义组件(@Component) | 不再有Fragment生命周期,组件即Fragment |
| Service | ServiceExtension / 后台任务 | 鸿蒙对后台限制更严格 |
| BroadcastReceiver | CommonEvent | 订阅机制类似,但权限管理更细 |
| ContentProvider | DataShareExtension | 分布式数据共享能力更强 |
这里面最大的坑在哪?Activity→UIAbility的映射。
Android里,一个Activity就是一个页面,生命周期方法一堆:onCreate、onStart、onResume、onPause、onStop、onDestroy。你在这堆方法里塞了多少逻辑?
鸿蒙的UIAbility生命周期简化了很多:onCreate、onWindowStageCreate、onForeground、onBackground、onDestroy。少了onStart/onResume/onPause/onStop,因为页面级别的生命周期交给了@Component。
生命周期映射
graph LR
subgraph Android生命周期
A_CREATE[onCreate] --> A_START[onStart]
A_START --> A_RESUME[onResume]
A_RESUME --> A_PAUSE[onPause]
A_PAUSE --> A_STOP[onStop]
A_STOP --> A_DESTROY[onDestroy]
end
subgraph 鸿蒙UIAbility生命周期
H_CREATE[onCreate] --> H_WINDOW[onWindowStageCreate]
H_WINDOW --> H_FORE[onForeground]
H_FORE --> H_BACK[onBackground]
H_BACK --> H_DESTROY[onDestroy]
end
A_CREATE -.->|对应| H_CREATE
A_RESUME -.->|对应| H_FORE
A_PAUSE -.->|对应| H_BACK
A_DESTROY -.->|对应| H_DESTROY
classDef aStyle fill:#4CAF50,stroke:#2E7D32,color:#fff,font-weight:bold
classDef hStyle fill:#2196F3,stroke:#0A3CC7,color:#fff,font-weight:bold
classDef mapStyle fill:#FF9800,stroke:#E65100,color:#000,font-weight:bold
class A_CREATE,A_START,A_RESUME,A_PAUSE,A_STOP,A_DESTROY aStyle
class H_CREATE,H_WINDOW,H_FORE,H_BACK,H_DESTROY hStyle
你看到了吧?Android的7个生命周期方法,在鸿蒙里精简到了5个。那些你写在onResume里的逻辑,要挪到onForeground里。onPause里的逻辑,挪到onBackground里。onStart和onStop呢?大部分情况下,你的逻辑应该放到自定义组件的生命周期里。
代码实战
基础用法:Activity迁移到UIAbility
先看一个最简单的Android Activity:
// Android: 一个典型的Activity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.textView.text = "Hello Android"
binding.button.setOnClickListener {
viewModel.loadData()
}
viewModel.data.observe(this) { data ->
binding.textView.text = data
}
}
override fun onResume() {
super.onResume()
viewModel.refresh()
}
}
迁移到鸿蒙:
// 鸿蒙: UIAbility + 声明式UI
@Entry
@Component
struct MainPage {
@State message: string = 'Hello HarmonyOS'
@StorageLink('viewModel') viewModel: MainViewModel = new MainViewModel()
aboutToAppear() {
// 对应Activity的onResume
this.viewModel.refresh()
}
build() {
Column() {
Text(this.message)
.fontSize(24)
.margin({ top: 20 })
Button('加载数据')
.onClick(() => {
this.viewModel.loadData()
})
.margin({ top: 20 })
}
.width('100%')
.height('100%')
}
}
// UIAbility本身很简洁,页面逻辑都在@Component里
export default class MainAbility extends UIAbility {
onCreate(want, launchParam) {
// 初始化,对应Android的onCreate
console.info('MainAbility onCreate')
}
onWindowStageCreate(windowStage) {
// 加载页面
windowStage.loadContent('pages/MainPage', (err, data) => {
if (err.code) {
console.error('Failed to load content: ' + JSON.stringify(err))
return
}
})
}
onForeground() {
// 对应Android的onResume
console.info('MainAbility onForeground')
}
onBackground() {
// 对应Android的onPause
console.info('MainAbility onBackground')
}
}
看出区别了吗?Android里Activity既管生命周期又管UI,鸿蒙里UIAbility只管生命周期,UI全部交给@Component。职责分得更清楚了。
进阶用法:Fragment迁移到自定义组件
Android里Fragment是最让人头疼的东西——生命周期比Activity还复杂,嵌套Fragment更是灾难。
鸿蒙没有Fragment,取而代之的是自定义组件。自定义组件没有Fragment那些乱七八糟的生命周期,只有aboutToAppear和aboutToDisappear。
// Android: Fragment
class UserFragment : Fragment() {
private var _binding: FragmentUserBinding? = null
private val binding get() = _binding!!
private val viewModel: UserViewModel by viewModels()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentUserBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.userList.observe(viewLifecycleOwner) { users ->
binding.recyclerView.adapter = UserAdapter(users)
}
viewModel.loadUsers()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null // 防止内存泄漏
}
}
迁移到鸿蒙:
// 鸿蒙: 自定义组件,比Fragment简洁太多
@Component
export struct UserComponent {
@State userList: UserInfo[] = []
private viewModel: UserViewModel = new UserViewModel()
aboutToAppear() {
// 对应onViewCreated
this.viewModel.loadUsers((users: UserInfo[]) => {
this.userList = users
})
}
build() {
List() {
ForEach(this.userList, (user: UserInfo) => {
ListItem() {
this.UserItem(user)
}
}, (user: UserInfo) => user.id.toString())
}
.width('100%')
.height('100%')
}
@Builder
UserItem(user: UserInfo) {
Row() {
Text(user.name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text(user.email)
.fontSize(14)
.fontColor('#999999')
}
.width('100%')
.padding(12)
}
}
没有onCreateView、onViewCreated、onDestroyView的纠缠,没有_binding的空安全噩梦。组件创建时aboutToAppear,销毁时aboutToDisappear,干净利落。
完整示例:Android MVVM迁移到鸿蒙
来一个完整的MVVM架构迁移。Android端:
// Android: ViewModel
class ArticleViewModel : ViewModel() {
private val _articles = MutableLiveData<List<Article>>()
val articles: LiveData<List<Article>> = _articles
private val _loading = MutableLiveData<Boolean>()
val loading: LiveData<Boolean> = _loading
fun loadArticles() {
viewModelScope.launch {
_loading.value = true
try {
val result = Repository.getArticles()
_articles.value = result
} catch (e: Exception) {
// 错误处理
} finally {
_loading.value = false
}
}
}
}
// Android: Activity
class ArticleActivity : AppCompatActivity() {
private val viewModel: ArticleViewModel by viewModels()
private lateinit var adapter: ArticleAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ... 初始化
viewModel.articles.observe(this) { articles ->
adapter.submitList(articles)
}
viewModel.loading.observe(this) { isLoading ->
binding.progressBar.isVisible = isLoading
}
viewModel.loadArticles()
}
}
鸿蒙端完整迁移:
// 鸿蒙: ViewModel用@ObservedV2装饰
@ObservedV2
export class ArticleViewModel {
@Trace articles: Article[] = []
@Trace loading: boolean = false
private repository: ArticleRepository = new ArticleRepository()
async loadArticles() {
this.loading = true
try {
this.articles = await this.repository.getArticles()
} catch (e) {
console.error('加载文章失败: ' + e.message)
} finally {
this.loading = false
}
}
}
// 鸿蒙: 页面
@Entry
@Component
struct ArticlePage {
@Local viewModel: ArticleViewModel = new ArticleViewModel()
aboutToAppear() {
this.viewModel.loadArticles()
}
build() {
Column() {
// 标题栏
Row() {
Text('文章列表')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
if (this.viewModel.loading) {
// 加载状态
LoadingProgress()
.width(48)
.height(48)
.color('#0A59F7')
} else {
// 文章列表
List() {
ForEach(this.viewModel.articles, (article: Article) => {
ListItem() {
this.ArticleCard(article)
}
}, (article: Article) => article.id.toString())
}
.width('100%')
.layoutWeight(1)
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
@Builder
ArticleCard(article: Article) {
Column() {
Text(article.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(article.summary)
.fontSize(14)
.fontColor('#666666')
.margin({ top: 8 })
.maxLines(3)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Row() {
Text(article.author)
.fontSize(12)
.fontColor('#999999')
Text(article.date)
.fontSize(12)
.fontColor('#999999')
.margin({ left: 12 })
}
.margin({ top: 8 })
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
.margin({ left: 16, right: 16, top: 8, bottom: 8 })
}
}
对比一下:Android的LiveData+ViewModel,到了鸿蒙变成了@ObservedV2+@Trace。观察者模式还是那个观察者模式,只是写法变了。
踩坑与注意事项
坑1:UIAbility不是Activity,别把它当Activity用
Android开发者最容易犯的错误——在UIAbility里写UI逻辑。UIAbility只负责生命周期管理和WindowStage调度,UI逻辑全部放到@Component里。你在onCreate里初始化的那些UI相关的东西,统统挪到aboutToAppear里。
坑2:生命周期对不上,逻辑放错地方
Android的onResume/onPause在鸿蒙里对应onForeground/onBackground,但它们触发的时机不完全一样。onForeground是整个Ability回到前台,不是某个页面获得焦点。页面级别的焦点变化,要用onPageShow/onPageHide。
坑3:Context不一样
Android的Context是个万能钥匙,啥都能干。鸿蒙的Context分了好几种:AbilityContext、ApplicationContext、UIAbilityContext。用错了Context,权限就申请不了,资源就加载不了。
// 正确用法:在UIAbility中获取Context
export default class MainAbility extends UIAbility {
onCreate(want, launchParam) {
// 这是UIAbilityContext
let context = this.context
}
}
// 在@Component中获取Context
@Entry
@Component
struct MainPage {
aboutToAppear() {
// 通过getContext获取
let context = getContext(this)
}
}
坑4:后台任务限制更严格
Android的Service可以跑前台服务保活,鸿蒙的后台任务管控严格得多。长时任务需要申请Background Task,而且有明确的场景限制(音乐播放、导航、蓝牙连接等)。你那个"后台默默上传数据"的需求,在鸿蒙上可能要改用延迟任务或者代理提醒。
坑5:权限模型变了
Android的运行时权限申请,到了鸿蒙变成了accesstoken机制。权限分了normal、system_basic、system_core三个等级,你的应用只能申请normal级别的权限。system_basic和system_core需要系统签名,普通应用想都别想。
HarmonyOS 6适配说明
HarmonyOS 6在迁移适配上做了几个重要改进:
-
UIAbility生命周期进一步简化:新增了onPrepare生命周期回调,在onCreate之前执行,专门处理Ability预加载逻辑。
-
@ObservedV2替代@Observed:HarmonyOS 6推荐使用@ObservedV2 + @Trace组合,性能比@Observed + @Watch好得多。深度观测不再需要手动设置深度监听层数。
-
组件复用能力增强:@Reusable装饰器可以让组件实例被回收复用,长列表场景下性能提升明显。迁移时记得给列表项组件加上@Reusable。
-
迁移工具链升级:DevEco Studio 6内置了Android代码迁移助手,能自动识别常见的Android API并给出鸿蒙替代方案。虽然不能100%自动转换,但至少能帮你省掉一半的手动查找工作。
-
分布式能力原生支持:HarmonyOS 6的UIAbility天然支持跨设备迁移,你不需要额外写代码就能实现"手机上看到哪,平板接着看"的效果。这是Android完全没有的能力。
总结
Android迁移鸿蒙,核心就三件事:理解架构差异、掌握映射关系、调整编码习惯。
| 维度 | 学习难度 | 使用频率 | 重要程度 |
|---|---|---|---|
| Activity→UIAbility | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Fragment→@Component | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Service→ExtensionAbility | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 生命周期映射 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 权限模型迁移 | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 后台任务适配 | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
最关键的心态转变:别想着1:1翻译Android代码。鸿蒙的声明式UI天然比Android的命令式UI更简洁,同样的功能,鸿蒙代码量通常只有Android的60%-70%。利用好这个优势,而不是把Android的冗余也搬过来。
下一篇,我们聊聊iOS开发者怎么转鸿蒙——SwiftUI和ArkUI的相似度,可能比你想象的高。
- 点赞
- 收藏
- 关注作者
评论(0)