ExoPlayer KMP UI框架:使用Compose Multiplatform构建跨平台媒体播放界面
2026-02-05 04:46:26作者:姚月梅Lane
前言:跨平台媒体播放的技术痛点与解决方案
你是否还在为Android和iOS平台分别开发媒体播放界面而烦恼?是否面临着双端代码维护成本高、UI一致性难以保证、播放器状态同步复杂等问题?本文将系统介绍如何基于ExoPlayer和Compose Multiplatform(Kotlin Multiplatform UI框架)构建真正跨平台的媒体播放解决方案,实现"一次编码,双端运行"的开发效率飞跃。
读完本文你将获得:
- 掌握ExoPlayer在KMP项目中的集成方法
- 学会构建跨平台媒体播放组件(播放器视图、控制栏、进度条)
- 理解平台特定代码与共享UI的分离策略
- 解决音视频播放中的平台兼容性问题
- 获取完整的KMP媒体播放器项目架构参考
技术选型对比:为什么选择ExoPlayer+Compose Multiplatform?
| 方案组合 | 跨平台能力 | 媒体格式支持 | UI一致性 | 性能表现 | 开发效率 |
|---|---|---|---|---|---|
| ExoPlayer+Jetpack Compose | 仅限Android | ★★★★★ | 单一平台一致 | ★★★★★ | 中等 |
| AVPlayer+SwiftUI | 仅限iOS | ★★★★☆ | 单一平台一致 | ★★★★★ | 中等 |
| 原生播放器+Flutter | 全平台 | ★★★☆☆ | 高 | ★★★☆☆ | 高 |
| ExoPlayer+Compose Multiplatform | Android/iOS桌面/Web | ★★★★★ | 极高 | ★★★★☆ | 极高 |
核心优势:ExoPlayer提供强大的媒体处理能力,支持几乎所有主流媒体格式和流媒体协议;Compose Multiplatform则允许使用Kotlin语言构建跨平台UI,两者结合既保留了ExoPlayer的媒体播放优势,又实现了UI代码的跨平台共享。
项目架构设计:分层与模块划分
整体架构图
flowchart TD
subgraph 共享模块 (shared)
UI[Compose UI组件] --> ViewModel[媒体播放ViewModel]
ViewModel --> PlayerController[播放器控制器接口]
PlayerController --> ExoPlayerWrapper[ExoPlayer包装器]
PlayerController --> AVPlayerWrapper[AVPlayer包装器]
ExoPlayerWrapper --> MediaDataSource[媒体数据源管理]
AVPlayerWrapper --> MediaDataSource
end
subgraph Android平台
AndroidApp[Android应用] --> AndroidMain[Android入口]
AndroidMain --> shared
shared --> ExoPlayerAndroid[ExoPlayer Android实现]
end
subgraph iOS平台
iOSApp[iOS应用] --> iOSMain[iOS入口]
iOSMain --> shared
shared --> ExoPlayeriOS[ExoPlayer iOS实现]
end
subgraph 桌面平台
DesktopApp[桌面应用] --> DesktopMain[桌面入口]
DesktopMain --> shared
end
模块职责说明
-
shared模块:核心共享代码
- UI层:Compose Multiplatform组件
- 业务逻辑层:媒体播放状态管理
- 数据层:媒体数据源和播放控制接口
-
平台特定模块:
- Android:ExoPlayer原生实现、权限处理
- iOS:ExoPlayer iOS绑定、平台配置
- 桌面:窗口管理和输入处理
环境搭建:开发环境配置与依赖集成
系统要求
- JDK 17+
- Android Studio Hedgehog或更高版本
- Xcode 14.3+(iOS开发)
- Kotlin 1.9.0+
- Compose Multiplatform 1.5.0+
项目配置步骤
1. 创建KMP项目
# 使用Kotlin官方模板创建KMP项目
curl -s https://get.sdkman.io | bash
sdk install kotlin 1.9.0
sdk install gradle 8.2
kdoctor # 检查开发环境
ktor create my-exoplayer-kmp --project-type=mobile-multiplatform
2. 配置settings.gradle.kts
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
maven("https://androidx.dev/storage/compose-compiler/repository")
}
}
3. 配置共享模块build.gradle.kts
plugins {
kotlin("multiplatform")
id("com.android.library")
id("org.jetbrains.compose")
}
kotlin {
androidTarget()
iosX64()
iosArm64()
iosSimulatorArm64()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation("androidx.media3:media3-exoplayer:1.2.0")
implementation("androidx.media3:media3-ui:1.2.0")
implementation("io.insert-koin:koin-core:3.4.0")
}
}
val androidMain by getting {
dependencies {
implementation("androidx.activity:activity-compose:1.8.0")
implementation("androidx.compose.ui:ui-graphics:1.5.0")
}
}
val iosMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}
}
}
}
核心实现:ExoPlayer与Compose Multiplatform集成
1. 播放器控制器接口定义
// 共享模块 - 播放器控制接口
interface MediaPlayerController {
// 播放状态
val playbackState: State<PlaybackState>
// 当前播放位置
val currentPosition: State<Long>
// 媒体总时长
val duration: State<Long>
// 是否正在缓冲
val isBuffering: State<Boolean>
// 播放控制方法
fun play(url: String)
fun pause()
fun resume()
fun seekTo(positionMs: Long)
fun release()
}
// 播放状态密封类
sealed class PlaybackState {
object Idle : PlaybackState()
object Loading : PlaybackState()
object Playing : PlaybackState()
object Paused : PlaybackState()
object Completed : PlaybackState()
data class Error(val message: String) : PlaybackState()
}
2. ExoPlayer实现(Android平台)
// Android平台实现
class AndroidExoPlayerController(
private val context: Context
) : MediaPlayerController {
private val exoPlayer = ExoPlayer.Builder(context).build()
private val _playbackState = mutableStateOf<PlaybackState>(PlaybackState.Idle)
private val _currentPosition = mutableStateOf(0L)
private val _duration = mutableStateOf(0L)
private val _isBuffering = mutableStateOf(false)
// 状态更新协程
private val updatePositionJob = CoroutineScope(Dispatchers.Main).launch {
while (isActive) {
if (exoPlayer.isPlaying) {
_currentPosition.value = exoPlayer.currentPosition
}
delay(1000) // 每秒更新一次位置
}
}
init {
exoPlayer.addListener(object : Player.Listener {
override fun onPlaybackStateChanged(state: Int) {
_playbackState.value = when (state) {
Player.STATE_IDLE -> PlaybackState.Idle
Player.STATE_BUFFERING -> {
_isBuffering.value = true
PlaybackState.Loading
}
Player.STATE_READY -> {
_isBuffering.value = false
if (exoPlayer.playWhenReady) PlaybackState.Playing
else PlaybackState.Paused
}
Player.STATE_ENDED -> PlaybackState.Completed
else -> PlaybackState.Idle
}
if (state == Player.STATE_READY) {
_duration.value = exoPlayer.duration
}
}
override fun onPlayerError(error: PlaybackException) {
_playbackState.value = PlaybackState.Error(error.message ?: "Unknown error")
}
})
}
override val playbackState: State<PlaybackState> = _playbackState
override val currentPosition: State<Long> = _currentPosition
override val duration: State<Long> = _duration
override val isBuffering: State<Boolean> = _isBuffering
override fun play(url: String) {
val mediaItem = MediaItem.fromUri(url)
exoPlayer.setMediaItem(mediaItem)
exoPlayer.prepare()
exoPlayer.playWhenReady = true
}
override fun pause() {
exoPlayer.playWhenReady = false
}
override fun resume() {
exoPlayer.playWhenReady = true
}
override fun seekTo(positionMs: Long) {
exoPlayer.seekTo(positionMs)
}
override fun release() {
updatePositionJob.cancel()
exoPlayer.release()
}
}
3. 共享Compose播放器组件
// 共享UI组件 - 媒体播放器
@Composable
fun MediaPlayerScreen(
controller: MediaPlayerController,
modifier: Modifier = Modifier
) {
val coroutineScope = rememberCoroutineScope()
Column(
modifier = modifier
.fillMaxSize()
.background(Color.Black),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
// 视频播放表面
VideoSurface(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(16f / 9f)
.background(Color.Black)
)
// 播放状态显示
when (val state = controller.playbackState.value) {
is PlaybackState.Loading, PlaybackState.Idle -> {
CircularProgressIndicator(
color = Color.White,
modifier = Modifier.size(48.dp)
)
}
is PlaybackState.Error -> {
Text(
text = "播放错误: ${state.message}",
color = Color.Red,
modifier = Modifier.padding(16.dp)
)
}
else -> Unit
}
// 进度条
MediaProgressBar(
currentPosition = controller.currentPosition.value,
duration = controller.duration.value,
isBuffering = controller.isBuffering.value,
onSeek = { positionMs ->
controller.seekTo(positionMs)
}
)
// 控制按钮栏
MediaControlButtons(
state = controller.playbackState.value,
onPlayPauseClick = {
when (controller.playbackState.value) {
is PlaybackState.Playing -> controller.pause()
is PlaybackState.Paused, is PlaybackState.Completed -> controller.resume()
else -> Unit
}
}
)
}
}
// 视频播放表面
@Composable
expect fun VideoSurface(modifier: Modifier)
// Android平台实现
@Composable
actual fun VideoSurface(modifier: Modifier) {
AndroidView(
factory = { context ->
StyledPlayerView(context).apply {
player = LocalPlayer.current
useController = false // 禁用默认控制器
setShowBuffering(StyledPlayerView.SHOW_BUFFERING_ALWAYS)
}
},
modifier = modifier
)
}
// iOS平台实现
@Composable
actual fun VideoSurface(modifier: Modifier) {
// iOS平台的视频表面实现
Box(
modifier = modifier
.background(Color.Black)
.border(1.dp, Color.Gray)
) {
// iOS平台特定的视频渲染实现
// 此处需要集成ExoPlayer的iOS渲染视图
}
}
4. 播放控制组件实现
// 媒体进度条
@Composable
fun MediaProgressBar(
currentPosition: Long,
duration: Long,
isBuffering: Boolean,
onSeek: (Long) -> Unit,
modifier: Modifier = Modifier
) {
val formattedCurrentTime = remember(currentPosition) {
formatTime(currentPosition)
}
val formattedDuration = remember(duration) {
formatTime(duration)
}
Column(modifier = modifier.fillMaxWidth()) {
Slider(
value = currentPosition.toFloat(),
valueRange = 0f..duration.toFloat(),
onValueChange = { position ->
onSeek(position.toLong())
},
enabled = duration > 0,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = formattedCurrentTime,
color = Color.White,
fontSize = 12.sp
)
Row {
if (isBuffering) {
CircularProgressIndicator(
color = Color.White,
modifier = Modifier.size(12.dp),
strokeWidth = 2.dp
)
Spacer(modifier = Modifier.width(4.dp))
}
Text(
text = formattedDuration,
color = Color.White,
fontSize = 12.sp
)
}
}
}
}
// 媒体控制按钮
@Composable
fun MediaControlButtons(
state: PlaybackState,
onPlayPauseClick: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
onClick = { /* 后退功能 */ },
modifier = Modifier.size(48.dp)
) {
Icon(
imageVector = Icons.Default.Replay10,
contentDescription = "后退10秒",
tint = Color.White,
modifier = Modifier.size(24.dp)
)
}
Spacer(modifier = Modifier.width(16.dp))
IconButton(
onClick = onPlayPauseClick,
modifier = Modifier.size(64.dp)
) {
Icon(
imageVector = when (state) {
is PlaybackState.Playing -> Icons.Default.PauseCircle
else -> Icons.Default.PlayCircle
},
contentDescription = if (state is PlaybackState.Playing) "暂停" else "播放",
tint = Color.White,
modifier = Modifier.size(48.dp)
)
}
Spacer(modifier = Modifier.width(16.dp))
IconButton(
onClick = { /* 前进功能 */ },
modifier = Modifier.size(48.dp)
) {
Icon(
imageVector = Icons.Default.Forward10,
contentDescription = "前进10秒",
tint = Color.White,
modifier = Modifier.size(24.dp)
)
}
}
}
// 时间格式化工具函数
private fun formatTime(milliseconds: Long): String {
if (milliseconds < 0) return "00:00"
val totalSeconds = milliseconds / 1000
val minutes = totalSeconds / 60
val seconds = totalSeconds % 60
return when {
minutes >= 60 -> {
val hours = minutes / 60
val remainingMinutes = minutes % 60
"%d:%02d:%02d".format(hours, remainingMinutes, seconds)
}
else -> "%02d:%02d".format(minutes, seconds)
}
}
平台适配策略:处理平台特定差异
1. 依赖注入与平台模块配置
// 共享模块 - 依赖注入配置
val commonModule = module {
factory<MediaPlayerController> {
get<PlatformModule>().provideMediaPlayerController()
}
}
// 平台模块接口
expect class PlatformModule {
fun provideMediaPlayerController(): MediaPlayerController
}
// Android平台模块
actual class PlatformModule(private val context: Context) {
actual fun provideMediaPlayerController(): MediaPlayerController {
return AndroidExoPlayerController(context)
}
}
// iOS平台模块
actual class PlatformModule {
actual fun provideMediaPlayerController(): MediaPlayerController {
return IosExoPlayerController()
}
}
2. 权限处理(Android平台)
// Android权限请求
@Composable
fun RequestPermissions(
permissions: List<String>,
onPermissionsGranted: () -> Unit,
content: @Composable () -> Unit
) {
val permissionState = rememberMultiplePermissionsState(permissions)
LaunchedEffect(permissionState) {
if (permissionState.allPermissionsGranted) {
onPermissionsGranted()
} else {
permissionState.launchMultiplePermissionRequest()
}
}
if (permissionState.allPermissionsGranted) {
content()
} else {
PermissionRequestScreen(
onRequestPermissions = {
permissionState.launchMultiplePermissionRequest()
}
)
}
}
// 权限请求界面
@Composable
private fun PermissionRequestScreen(
onRequestPermissions: () -> Unit
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "需要媒体播放权限",
fontSize = 18.sp,
color = Color.Black,
modifier = Modifier.padding(16.dp)
)
Button(onClick = onRequestPermissions) {
Text("授予权限")
}
}
}
3. 视频渲染优化
// Android平台视频渲染优化
@Composable
fun OptimizedVideoSurface(
modifier: Modifier,
player: ExoPlayer
) {
val context = LocalContext.current
var playerView by remember { mutableStateOf<StyledPlayerView?>(null) }
AndroidView(
factory = { ctx ->
StyledPlayerView(ctx).apply {
this.player = player
useController = false
setShowBuffering(StyledPlayerView.SHOW_BUFFERING_WHEN_PLAYING)
// 启用硬件加速
setRenderMode(StyledPlayerView.RENDER_MODE_SURFACE_VIEW)
setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT)
// 性能优化配置
setKeepScreenOn(true)
setEnableAudioTrackSelection(false)
playerView = this
}
},
modifier = modifier
)
// 生命周期感知处理
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_PAUSE -> {
playerView?.onPause()
}
Lifecycle.Event.ON_RESUME -> {
playerView?.onResume()
}
Lifecycle.Event.ON_DESTROY -> {
playerView?.player = null
}
else -> Unit
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
性能优化实践:提升媒体播放体验
1. 播放器状态管理优化
// 优化的播放器状态管理
@Composable
fun rememberPlayerState(controller: MediaPlayerController): PlayerState {
// 使用derivedStateOf减少重组
val formattedPosition = remember {
derivedStateOf {
formatTime(controller.currentPosition.value)
}
}
val formattedDuration = remember {
derivedStateOf {
formatTime(controller.duration.value)
}
}
val isPlaying = remember {
derivedStateOf {
controller.playbackState.value is PlaybackState.Playing
}
}
return PlayerState(
formattedPosition = formattedPosition.value,
formattedDuration = formattedDuration.value,
isPlaying = isPlaying.value,
isBuffering = controller.isBuffering.value
)
}
// 播放器状态数据类
data class PlayerState(
val formattedPosition: String,
val formattedDuration: String,
val isPlaying: Boolean,
val isBuffering: Boolean
)
2. 内存管理与资源释放
// 播放器生命周期管理
@Composable
fun rememberMediaPlayerController(
platformModule: PlatformModule
): MediaPlayerController {
val controller = remember {
platformModule.provideMediaPlayerController()
}
DisposableEffect(Unit) {
onDispose {
// 组件销毁时释放播放器资源
controller.release()
}
}
return controller
}
// Activity/Fragment中的使用
class PlayerActivity : ComponentActivity() {
private lateinit var platformModule: PlatformModule
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
platformModule = PlatformModule(applicationContext)
setContent {
MaterialTheme {
val controller = rememberMediaPlayerController(platformModule)
MediaPlayerScreen(
controller = controller,
modifier = Modifier.fillMaxSize()
)
}
}
}
override fun onPause() {
super.onPause()
// 页面暂停时暂停播放
if (this::controller.isInitialized) {
controller.pause()
}
}
}
常见问题解决方案
1. 音频焦点处理
// 音频焦点管理
class AudioFocusManager(
private val context: Context,
private val player: MediaPlayerController
) {
private val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
private val audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setOnAudioFocusChangeListener { focusChange ->
when (focusChange) {
AudioManager.AUDIOFOCUS_LOSS -> {
player.pause()
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
player.pause()
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
// 降低音量
}
AudioManager.AUDIOFOCUS_GAIN -> {
player.resume()
}
}
}
.build()
// 请求音频焦点
fun requestAudioFocus(): Boolean {
val result = audioManager.requestAudioFocus(audioFocusRequest)
return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
}
// 放弃音频焦点
fun abandonAudioFocus() {
audioManager.abandonAudioFocusRequest(audioFocusRequest)
}
}
2. 网络状态监听
// 网络状态监听
@Composable
fun NetworkStateMonitor(
onNetworkUnavailable: () -> Unit,
content: @Composable () -> Unit
) {
val context = LocalContext.current
val connectivityManager = remember {
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
}
var isConnected by remember { mutableStateOf(false) }
DisposableEffect(connectivityManager) {
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
isConnected = true
}
override fun onLost(network: Network) {
isConnected = false
onNetworkUnavailable()
}
}
connectivityManager.registerDefaultNetworkCallback(networkCallback)
// 初始检查
isConnected = connectivityManager.activeNetworkInfo?.isConnected == true
onDispose {
connectivityManager.unregisterNetworkCallback(networkCallback)
}
}
if (isConnected) {
content()
} else {
NetworkErrorScreen()
}
}
完整项目结构与代码组织
exoplayer-kmp/
├── app/
│ ├── android/ # Android应用
│ ├── ios/ # iOS应用
│ └── desktop/ # 桌面应用
├── shared/ # 共享模块
│ ├── src/
│ │ ├── commonMain/ # 通用代码
│ │ │ ├── kotlin/
│ │ │ │ ├── data/ # 数据层
│ │ │ │ ├── domain/ # 领域层
│ │ │ │ ├── ui/ # UI组件
│ │ │ │ └── Main.kt # 共享入口
│ │ ├── androidMain/ # Android平台代码
│ │ ├── iosMain/ # iOS平台代码
│ │ └── desktopMain/ # 桌面平台代码
│ ├── build.gradle.kts # 共享模块构建配置
├── build.gradle.kts # 项目构建配置
└── settings.gradle.kts # 项目设置
总结与未来展望
本文详细介绍了如何使用ExoPlayer和Compose Multiplatform构建跨平台媒体播放解决方案,从架构设计、环境搭建到核心组件实现,全面覆盖了KMP媒体播放器开发的关键技术点。通过这种组合方案,开发者可以显著减少双端开发工作量,同时保持ExoPlayer强大的媒体播放能力。
关键收获
- 跨平台UI共享:使用Compose Multiplatform实现90%以上的UI代码共享
- 平台特定代码隔离:通过expect/actual机制处理平台差异
- 状态管理最佳实践:使用Jetpack Compose的State API管理播放状态
- 性能优化技巧:减少重组、优化资源释放、提升渲染效率
未来改进方向
- Web平台支持:扩展到Web平台,实现全平台覆盖
- 更丰富的媒体功能:添加字幕支持、多音轨切换、画中画模式
- 自定义渲染器:实现更高级的视频处理和特效
- 单元测试覆盖:增加播放器组件的单元测试和集成测试
下一步行动
- 点赞收藏本文,关注作者获取更多KMP开发干货
- 克隆GitHub代码库实践本文示例:
git clone https://gitcode.com/gh_mirrors/ex/ExoPlayer - 尝试扩展功能,添加自定义播放控制
- 关注ExoPlayer和Compose Multiplatform的最新版本更新
通过本文介绍的方案,你可以构建出功能强大、性能优异的跨平台媒体播放应用,为用户提供一致的播放体验,同时大幅提升开发效率。祝你在KMP媒体开发之旅中取得成功!
登录后查看全文
热门项目推荐
相关项目推荐
Kimi-K2.5Kimi K2.5 是一款开源的原生多模态智能体模型,它在 Kimi-K2-Base 的基础上,通过对约 15 万亿混合视觉和文本 tokens 进行持续预训练构建而成。该模型将视觉与语言理解、高级智能体能力、即时模式与思考模式,以及对话式与智能体范式无缝融合。Python00- QQwen3-Coder-Next2026年2月4日,正式发布的Qwen3-Coder-Next,一款专为编码智能体和本地开发场景设计的开源语言模型。Python00
xw-cli实现国产算力大模型零门槛部署,一键跑通 Qwen、GLM-4.7、Minimax-2.1、DeepSeek-OCR 等模型Go06
PaddleOCR-VL-1.5PaddleOCR-VL-1.5 是 PaddleOCR-VL 的新一代进阶模型,在 OmniDocBench v1.5 上实现了 94.5% 的全新 state-of-the-art 准确率。 为了严格评估模型在真实物理畸变下的鲁棒性——包括扫描伪影、倾斜、扭曲、屏幕拍摄和光照变化——我们提出了 Real5-OmniDocBench 基准测试集。实验结果表明,该增强模型在新构建的基准测试集上达到了 SOTA 性能。此外,我们通过整合印章识别和文本检测识别(text spotting)任务扩展了模型的能力,同时保持 0.9B 的超紧凑 VLM 规模,具备高效率特性。Python00
KuiklyUI基于KMP技术的高性能、全平台开发框架,具备统一代码库、极致易用性和动态灵活性。 Provide a high-performance, full-platform development framework with unified codebase, ultimate ease of use, and dynamic flexibility. 注意:本仓库为Github仓库镜像,PR或Issue请移步至Github发起,感谢支持!Kotlin08
VLOOKVLOOK™ 是优雅好用的 Typora/Markdown 主题包和增强插件。 VLOOK™ is an elegant and practical THEME PACKAGE × ENHANCEMENT PLUGIN for Typora/Markdown.Less00
项目优选
收起
deepin linux kernel
C
27
11
OpenHarmony documentation | OpenHarmony开发者文档
Dockerfile
533
3.75 K
Nop Platform 2.0是基于可逆计算理论实现的采用面向语言编程范式的新一代低代码开发平台,包含基于全新原理从零开始研发的GraphQL引擎、ORM引擎、工作流引擎、报表引擎、规则引擎、批处理引引擎等完整设计。nop-entropy是它的后端部分,采用java语言实现,可选择集成Spring框架或者Quarkus框架。中小企业可以免费商用
Java
12
1
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Java
67
20
暂无简介
Dart
773
191
Ascend Extension for PyTorch
Python
342
406
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
886
596
喝着茶写代码!最易用的自托管一站式代码托管平台,包含Git托管,代码审查,团队协作,软件包和CI/CD。
Go
23
0
React Native鸿蒙化仓库
JavaScript
303
355
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
336
178