# GGGitCode **Repository Path**: gg2511/gggit-code ## Basic Information - **Project Name**: GGGitCode - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: develop - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-02-03 - **Last Updated**: 2026-02-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # GitCode Android 客户端 [![Android](https://img.shields.io/badge/Android-9.0+-green.svg)](https://www.android.com) [![Kotlin](https://img.shields.io/badge/Kotlin-1.x-blue.svg)](https://kotlinlang.org) [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) 基于 MVVM 架构的 GitCode 官方 Android 客户端,支持OAuth 2.0认证、代码仓库浏览、用户资料管理等功能。 --- ## 📖 项目概述 **GitCode Android 客户端**是一个功能完备的移动端应用,为用户提供GitCode平台的核心功能: - ✅ OAuth 2.0 认证授权,自动Token刷新 - ✅ 热门仓库浏览与搜索 - ✅ 用户资料管理与编辑 - ✅ 仓库收藏与订阅管理 - ✅ 开发者信息查询 - ✅ 消息通知功能 ### 技术亮点 | 特性 | 描述 | |------|------| | **架构模式** | MVVM (Model-View-ViewModel) | | **开发语言** | Kotlin | | **最低支持** | Android 9.0 (API 28) | | **目标版本** | Android 14 (API 36) | | **异步处理** | Kotlin Coroutines + Flow | | **网络请求** | Retrofit + OkHttp | | **状态管理** | LiveData + StateFlow | | **导航管理** | Jetpack Navigation + Safe Args | --- ## 🎯 项目需求 ### 功能需求 | 模块 | 功能描述 | |------|----------| | **启动引导** | 引导页动画、登录状态检测、自动跳转 | | **OAuth认证** | 授权码模式登录、Token自动刷新、401错误处理 | | **首页导航** | 底部导航栏、顶部菜单、多层嵌套导航 | | **热门仓库** | 热门仓库列表、关键词搜索、下拉刷新、LruCache缓存 | | **个人中心** | 用户信息展示、仓库列表、Star列表、订阅列表 | | **资料编辑** | 昵称/简介编辑、头像上传、Base64编码 | | **开发者搜索** | 用户搜索、开发者详情、关注/粉丝列表 | | **设置页面** | 应用设置、退出登录 | ### 非功能需求 - ✅ **性能优化**: LruCache内存缓存、图片懒加载 - ✅ **线程安全**: Token刷新使用AtomicBoolean防止并发 - ✅ **用户体验**: 下拉刷新、加载状态提示、错误处理 - ✅ **代码质量**: MVVM分层、单一职责原则、基类封装 - ✅ **安全性**: SharedPreferences加密存储Token --- ## 🏗️ 项目实现文档 本项目严格遵循 **MVVM 架构**,以下按功能模块分类,每个模块内按**表现层、领域层、数据层**三层进行组织。 --- ## 一、启动引导模块 ### 1.1 表现层 (Presentation Layer) #### StartFragment - **文件**: `feature/start/StartFragment.kt` - **职责**: 启动页,判断登录状态并跳转 ```kotlin class StartFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 检查Token是否存在 if (TokenManager.getToken().isNullOrEmpty()) { findNavController().navigate(R.id.action_startFragment_to_authFragment) } else { findNavController().navigate(R.id.action_startFragment_to_homeFragment) } } } ``` #### Page0/1/2Fragment - **文件**: `feature/start/Page*.kt` - **职责**: 引导页,ViewPager2动画 --- ## 二、OAuth认证模块 ### 2.1 表现层 (Presentation Layer) #### AuthFragment - **文件**: `feature/oauth/AuthFragment.kt` - **职责**: OAuth认证入口页面 #### AuthViewModel - **文件**: `feature/oauth/AuthViewModel.kt` - **职责**: 生成OAuth授权URL、处理授权码登录 ```kotlin class AuthViewModel : ViewModel() { private val authRepository = AuthRepository() fun generateAuthUrl(): String { return "https://gitcode.com/oauth/authorize?" + "client_id=${BuildConfig.CLIENT_ID}&" + "redirect_uri=${BuildConfig.REDIRECT_URI}&" + "response_type=code&scope=user_info projects" } suspend fun loginWithCode(code: String): Result { return authRepository.loginWithCode(code) } } ``` #### AuthWebviewFragment - **文件**: `feature/oauth/ui/AuthWebviewFragment.kt` - **职责**: WebView加载授权页面、拦截授权回调 ### 2.2 领域层 (Domain Layer) #### AuthRepository - **文件**: `core/repository/AuthRepository.kt` - **职责**: 认证数据管理(登录、登出、刷新Token) ```kotlin class AuthRepository { private val authApiService = RetrofitClient.getAuthApiService() private val tokenManager = TokenManager suspend fun loginWithCode(code: String): Result { return try { val response = authApiService.getAccessToken( clientId = BuildConfig.CLIENT_ID, clientSecret = BuildConfig.CLIENT_SECRET, code = code, grantType = "authorization_code", redirectUri = BuildConfig.REDIRECT_URI ) if (response.isSuccessful) { response.body()?.let { authResponse -> tokenManager.saveToken(authResponse) Result.Success(authResponse) } ?: Result.Error(Exception("Empty response body")) } else { Result.Error(Exception(response.message())) } } catch (e: Exception) { Result.Error(e) } } suspend fun refreshToken(): Result { return try { val refreshToken = tokenManager.getRefreshToken() ?: return Result.Error(Exception("No refresh token")) val response = authApiService.refreshToken( clientId = BuildConfig.CLIENT_ID, clientSecret = BuildConfig.CLIENT_SECRET, refreshToken = refreshToken, grantType = "refresh_token" ) if (response.isSuccessful) { response.body()?.let { authResponse -> tokenManager.saveToken(authResponse) Result.Success(authResponse) } ?: Result.Error(Exception("Empty response body")) } else { Result.Error(Exception(response.message())) } } catch (e: Exception) { Result.Error(e) } } suspend fun logout() { tokenManager.clearToken() } } ``` ### 2.3 数据层 (Data Layer) #### AuthApiService - **文件**: `core/data/remote/api/AuthApiService.kt` - **端点**: - `POST /oauth/token` - 获取Access Token - `POST /oauth/token` - 刷新Token - `POST /oauth/token` - 撤销Token #### TokenManager - **文件**: `core/data/local/TokenManager.kt` - **职责**: Token本地存储管理、过期检测 ```kotlin object TokenManager { private const val PREFS_NAME = "gitcode_prefs" private const val KEY_ACCESS_TOKEN = "access_token" private const val KEY_REFRESH_TOKEN = "refresh_token" private const val KEY_EXPIRES_AT = "expires_at" private lateinit var prefs: SharedPreferences fun init(context: Context) { prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) } fun saveToken(authResponse: AuthResponse) { prefs.edit().apply { putString(KEY_ACCESS_TOKEN, authResponse.accessToken) putString(KEY_REFRESH_TOKEN, authResponse.refreshToken) putLong(KEY_EXPIRES_AT, authResponse.expiresAt) apply() } } fun getToken(): String? = prefs.getString(KEY_ACCESS_TOKEN, null) fun getRefreshToken(): String? = prefs.getString(KEY_REFRESH_TOKEN, null) fun isTokenExpired(): Boolean { val expiresAt = prefs.getLong(KEY_EXPIRES_AT, 0L) return System.currentTimeMillis() >= expiresAt } fun clearToken() { prefs.edit().clear().apply() } } ``` #### AuthResponse - **文件**: `core/data/model/AuthResponse.kt` - **描述**: OAuth认证响应 ```kotlin data class AuthResponse( val accessToken: String, val refreshToken: String, val tokenType: String, val scope: String, val createdAt: Long ) { val expiresAt: Long get() = (createdAt + 7200) * 1000 // Token有效期2小时 } ``` #### TokenAuthInterceptor - **文件**: `core/data/remote/interceptor/TokenAuthInterceptor.kt` - **职责**: 自动添加Token、401错误自动刷新、并发刷新控制 ```kotlin class TokenAuthInterceptor : Interceptor { private val authRepository = AuthRepository() private val isRefreshing = AtomicBoolean(false) private var retryCount = 0 private val MAX_RETRY = 3 override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() val token = TokenManager.getToken() val authenticatedRequest = originalRequest.newBuilder() .header("Authorization", "Bearer $token") .build() val response = chain.proceed(authenticatedRequest) if (response.code == 401 && retryCount < MAX_RETRY) { response.close() return handleTokenRefresh(chain) } return response } private fun handleTokenRefresh(chain: Interceptor.Chain): Response { synchronized(this) { if (!isRefreshing.get()) { isRefreshing.set(true) runBlocking { when (authRepository.refreshToken()) { is Result.Success -> retryCount = 0 is Result.Error -> retryCount++ } } isRefreshing.set(false) } } val token = TokenManager.getToken() val newRequest = chain.request().newBuilder() .header("Authorization", "Bearer $token") .build() return chain.proceed(newRequest) } } ``` ### 2.4 认证流程 ``` 用户点击登录 ↓ 生成授权URL (AuthViewModel.generateAuthUrl()) ↓ WebView加载授权页面 (AuthWebviewFragment) ↓ 获取授权码 (重定向URL中提取) ↓ 请求Access Token (AuthRepository.loginWithCode()) ↓ 保存Token (TokenManager.saveToken()) ↓ 跳转到主页 ``` --- ## 三、热门仓库模块 ### 3.1 表现层 (Presentation Layer) #### HotrepoFragment - **文件**: `feature/home/hotrepo/HotrepoFragment.kt` - **职责**: 热门仓库容器Fragment #### HotrepoTabFragment - **文件**: `feature/home/hotrepo/HotrepoTabFragment.kt` - **职责**: 热门仓库Tab页面,展示仓库列表 #### HotrepoViewModel - **文件**: `feature/home/hotrepo/HotrepoViewModel.kt` - **职责**: 热门仓库业务逻辑,LruCache缓存管理 ```kotlin class HotrepoViewModel : ViewModel() { private val repoRepository = RepoRepository() private val cache = LruCache>(10 * 1024) // 100KB缓存 private val _reposState = MutableStateFlow>>(UiState.Loading) val reposState: StateFlow>> = _reposState fun fetchHotRepos(keyword: String, page: Int, forceRefresh: Boolean = false) { viewModelScope.launch { val cacheKey = "${keyword}_${page}" // 尝试从缓存获取 if (!forceRefresh) { cache.get(cacheKey)?.let { _reposState.value = UiState.Success(it) return@launch } } // 网络请求 _reposState.value = UiState.Loading when (val result = repoRepository.getHotRepos(keyword, page)) { is Result.Success -> { _reposState.value = UiState.Success(result.data) cache.put(cacheKey, result.data) } is Result.Error -> { _reposState.value = UiState.Error(result.exception.message ?: "Unknown error") } } } } } ``` ### 3.2 领域层 (Domain Layer) #### RepoRepository - **文件**: `core/repository/RepoRepository.kt` - **职责**: 仓库数据管理 ```kotlin class RepoRepository { private val repoApiService = RetrofitClient.getRepoApiService() suspend fun getHotRepos(keyword: String = "ai", page: Int = 1): Result> { return try { val response = repoApiService.searchRepos(q = keyword, page = page) if (response.isSuccessful) { Result.Success(response.body() ?: emptyList()) } else { Result.Error(Exception(response.message())) } } catch (e: Exception) { Result.Error(e) } } } ``` ### 3.3 数据层 (Data Layer) #### RepoApiService - **文件**: `core/data/remote/api/RepoApiService.kt` - **端点**: - `GET /search/repositories` - 搜索仓库 - `GET /user/repos` - 获取授权用户仓库 - `GET /user/starred` - 获取用户Star的仓库 - `GET /user/subscriptions` - 获取用户订阅的仓库 #### RepoResponse - **文件**: `core/data/model/RepoResponse.kt` - **描述**: 仓库信息 ```kotlin data class RepoResponse( val id: Int, val name: String, val fullName: String, val description: String?, val owner: Owner, val stars: Int, val forks: Int, val language: String?, val updatedAt: String ) { data class Owner( val id: Int, val login: String, val avatarUrl: String ) } ``` --- ## 四、个人中心模块 ### 4.1 表现层 (Presentation Layer) #### ProfileFragment - **文件**: `feature/home/profile/ProfileFragment.kt` - **职责**: 个人中心容器Fragment #### ProfileTabFragment - **文件**: `feature/home/profile/ProfileTabFragment.kt` - **职责**: 个人中心Tab页面,展示用户信息和仓库 #### ProfileViewModel - **文件**: `feature/home/profile/ProfileViewModel.kt` - **职责**: 获取用户信息、仓库列表、Star列表、订阅列表 ```kotlin class ProfileViewModel : ViewModel() { private val profileRepository = ProfileRepository() private val _userState = MutableLiveData>() val userState: LiveData> = _userState private val _reposState = MutableLiveData>>() val reposState: LiveData>> = _reposState fun fetchUserProfile() { viewModelScope.launch { when (val result = profileRepository.getAuthUserProfile()) { is Result.Success -> _userState.value = UiState.Success(result.data) is Result.Error -> _userState.value = UiState.Error(result.exception.message) } } } fun fetchUserRepos() { viewModelScope.launch { when (val result = profileRepository.getAuthUserRepos()) { is Result.Success -> _reposState.value = UiState.Success(result.data) is Result.Error -> _reposState.value = UiState.Error(result.exception.message) } } } } ``` #### EditProfileDialogFragment - **文件**: `feature/home/profile/edit/EditProfileDialogFragment.kt` - **职责**: 编辑个人资料对话框 #### EditProfileDialogViewModel - **文件**: `feature/home/profile/edit/EditProfileDialogViewModel.kt` - **职责**: 编辑个人资料、头像上传 ```kotlin class EditProfileDialogViewModel : ViewModel() { private val uploadRepository = UploadRepository() private val _uploadState = MutableStateFlow>(UiState.Idle) val uploadState: StateFlow> = _uploadState fun uploadAvatar(base64Image: String) { viewModelScope.launch { _uploadState.value = UiState.Loading when (val result = uploadRepository.uploadImage(base64Image)) { is Result.Success -> _uploadState.value = UiState.Success(Unit) is Result.Error -> _uploadState.value = UiState.Error(result.exception.message ?: "Upload failed") } } } fun updateUserProfile(profile: Profile) { viewModelScope.launch { _uploadState.value = UiState.Loading when (val result = uploadRepository.updateUserProfile(profile)) { is Result.Success -> _uploadState.value = UiState.Success(Unit) is Result.Error -> _uploadState.value = UiState.Error(result.exception.message ?: "Update failed") } } } } ``` ### 4.2 领域层 (Domain Layer) #### ProfileRepository - **文件**: `core/repository/ProfileRepository.kt` - **职责**: 用户资料管理 ```kotlin class ProfileRepository { private val userApiService = RetrofitClient.getUserApiService() private val repoApiService = RetrofitClient.getRepoApiService() suspend fun getAuthUserProfile(): Result { return try { val response = userApiService.getAuthUser() if (response.isSuccessful) { response.body()?.let { Result.Success(it) } ?: Result.Error(Exception("User not found")) } else { Result.Error(Exception(response.message())) } } catch (e: Exception) { Result.Error(e) } } suspend fun getAuthUserRepos(page: Int = 1): Result> { return try { val response = repoApiService.getAuthUserRepos(page = page) if (response.isSuccessful) { Result.Success(response.body() ?: emptyList()) } else { Result.Error(Exception(response.message())) } } catch (e: Exception) { Result.Error(e) } } suspend fun getAuthUserStarredRepos(page: Int = 1): Result> { return try { val response = repoApiService.getAuthUserStarredRepos(page = page) if (response.isSuccessful) { Result.Success(response.body() ?: emptyList()) } else { Result.Error(Exception(response.message())) } } catch (e: Exception) { Result.Error(e) } } suspend fun getAuthUserSubscriptions(page: Int = 1): Result> { return try { val response = repoApiService.getAuthUserSubscriptions(page = page) if (response.isSuccessful) { Result.Success(response.body() ?: emptyList()) } else { Result.Error(Exception(response.message())) } } catch (e: Exception) { Result.Error(e) } } } ``` #### UploadRepository - **文件**: `core/repository/UploadRepository.kt` - **职责**: 上传数据管理 ```kotlin class UploadRepository { private val profileApiService = RetrofitClient.getProfileApiService() suspend fun uploadImage(base64Image: String): Result { return try { val request = UploadImageRequest(base64Image) val response = profileApiService.uploadImage(request) if (response.isSuccessful) { Result.Success(response.body()?.avatarUrl ?: "") } else { Result.Error(Exception(response.message())) } } catch (e: Exception) { Result.Error(e) } } suspend fun updateUserProfile(profile: Profile): Result { return try { val response = profileApiService.updateUserProfile(profile) if (response.isSuccessful) { Result.Success(Unit) } else { Result.Error(Exception(response.message())) } } catch (e: Exception) { Result.Error(e) } } } ``` ### 4.3 数据层 (Data Layer) #### UserApiService - **文件**: `core/data/remote/api/UserApiService.kt` - **端点**: - `GET /user` - 获取当前用户信息 - `GET /users/{username}` - 获取指定用户信息 - `GET /search/users` - 搜索用户 - `GET /users/{username}/followers` - 获取关注者 - `GET /users/{username}/following` - 获取关注列表 - `GET /user/followers` - 获取当前用户关注者 - `GET /user/following` - 获取当前用户关注列表 #### ProfileApiService - **文件**: `core/data/remote/api/ProfileApiService.kt` - **端点**: - `POST /user/avatar` - 上传头像 - `PATCH /user` - 更新用户资料 #### UserResponse - **文件**: `core/data/model/UserResponse.kt` - **描述**: 用户信息 ```kotlin data class UserResponse( val id: Int, val login: String, val name: String?, val avatarUrl: String, val bio: String?, val email: String?, val publicRepos: Int, val followers: Int, val following: Int ) ``` #### Profile - **文件**: `core/data/model/Profile.kt` - **描述**: 用户资料更新请求 --- ## 五、搜索模块 ### 5.1 表现层 (Presentation Layer) #### SearchFragment - **文件**: `feature/search/SearchFragment.kt` - **职责**: 搜索页面,支持开发者/仓库Tab切换 #### SearchViewModel - **文件**: `feature/search/SearchViewModel.kt` - **职责**: 开发者和仓库搜索 ```kotlin class SearchViewModel : ViewModel() { private val searchRepository = SearchRepository() private val _usersResult = MutableLiveData>>() val usersResult: LiveData>> = _usersResult private val _reposResult = MutableLiveData>>() val reposResult: LiveData>> = _reposResult fun searchUsers(query: String) { viewModelScope.launch { when (val result = searchRepository.getUsersInfo(query)) { is Result.Success -> _usersResult.value = UiState.Success(result.data) is Result.Error -> _usersResult.value = UiState.Error(result.exception.message) } } } fun searchRepos(query: String) { viewModelScope.launch { when (val result = searchRepository.getReposInfo(query)) { is Result.Success -> _reposResult.value = UiState.Success(result.data) is Result.Error -> _reposResult.value = UiState.Error(result.exception.message) } } } } ``` #### DeveloperDetailDialogFragment - **文件**: `feature/search/detail/DeveloperDetailDialogFragment.kt` - **职责**: 开发者详情对话框 #### DeveloperDetailViewModel - **文件**: `feature/search/detail/DeveloperDetailViewModel.kt` - **职责**: 开发者详情业务逻辑 ### 5.2 领域层 (Domain Layer) #### SearchRepository - **文件**: `core/repository/SearchRepository.kt` - **职责**: 搜索数据管理 ```kotlin class SearchRepository { private val userApiService = RetrofitClient.getUserApiService() private val repoApiService = RetrofitClient.getRepoApiService() suspend fun getUsersInfo(query: String, page: Int = 1): Result> { return try { val response = userApiService.searchUsers(q = query, page = page) if (response.isSuccessful) { Result.Success(response.body() ?: emptyList()) } else { Result.Error(Exception(response.message())) } } catch (e: Exception) { Result.Error(e) } } suspend fun getReposInfo(query: String, page: Int = 1): Result> { return try { val response = repoApiService.searchRepos(q = query, page = page) if (response.isSuccessful) { Result.Success(response.body() ?: emptyList()) } else { Result.Error(Exception(response.message())) } } catch (e: Exception) { Result.Error(e) } } suspend fun getUserDetail(username: String): Result { return try { val response = userApiService.getUserByUsername(username) if (response.isSuccessful) { response.body()?.let { Result.Success(it) } ?: Result.Error(Exception("User not found")) } else { Result.Error(Exception(response.message())) } } catch (e: Exception) { Result.Error(e) } } } ``` --- ## 六、主页导航模块 ### 6.1 表现层 (Presentation Layer) #### HomeFragment - **文件**: `feature/home/HomeFragment.kt` - **职责**: 主页容器,管理底部导航和子导航 ```kotlin class HomeFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.bottomNav.setupWithNavController( navController = childFragmentManager.findFragmentById(R.id.nav_host_home)!!.findNavController() ) } } ``` #### 导航架构 ``` MainActivity (主导航) └── StartFragment (启动页) ├── → AuthFragment (授权页) → HomeFragment └── → HomeFragment (主页) ├── 底部导航: hotrepo | notify | profile ├── 顶部菜单: 搜索 | 设置 └── 子导航器 ├── HotrepoFragment (热门仓库) ├── NotifyFragment (消息通知) └── ProfileFragment (个人中心) ``` --- ## 七、通用基础组件 ### 7.1 表现层 (Presentation Layer) #### BaseApplication - **文件**: `base/BaseApplication.kt` - **职责**: 应用初始化、日志配置、Token管理初始化 ```kotlin class BaseApplication : Application() { override fun onCreate() { super.onCreate() Logger.addLogAdapter(object : AndroidLogAdapter() {}) TokenManager.init(this) } } ``` #### BaseFragment - **文件**: `base/BaseFragment.kt` - **职责**: 所有Fragment基类,封装ViewBinding生命周期管理 ```kotlin abstract class BaseFragment : Fragment() { private var _binding: VB? = null val binding: VB get() = _binding!! override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { _binding = inflateBinding(inflater, container, false) return binding.root } override fun onDestroyView() { super.onDestroyView() _binding = null } abstract fun inflateBinding(inflater: LayoutInflater, container: ViewGroup?, attachToParent: Boolean): VB } ``` #### WebViewFragment - **文件**: `common/ui/WebViewFragment.kt` - **职责**: 通用WebView页面 ### 7.2 数据层 (Data Layer) #### RetrofitClient - **文件**: `core/data/remote/RetrofitClient.kt` - **职责**: Retrofit单例,配置OkHttp客户端、拦截器、转换器 ```kotlin object RetrofitClient { private const val BASE_URL = "https://api.gitcode.com/api/v5/" private val okHttpClient = OkHttpClient.Builder() .addInterceptor(TokenAuthInterceptor()) .addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }) .build() private val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(MoshiConverterFactory.create()) .build() fun getAuthApiService(): AuthApiService = retrofit.create(AuthApiService::class.java) fun getUserApiService(): UserApiService = retrofit.create(UserApiService::class.java) fun getRepoApiService(): RepoApiService = retrofit.create(RepoApiService::class.java) fun getProfileApiService(): ProfileApiService = retrofit.create(ProfileApiService::class.java) } ``` --- ## 八、架构设计 ### 8.1 MVVM分层架构 ``` ┌─────────────────────────────────────────────────────────┐ │ Presentation Layer │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ Activity/ │ │ Fragment │ │ ViewModel │ │ │ │ View │←→│ │←→│ │ │ │ └────────────┘ └────────────┘ └────────────┘ │ └─────────────────────────────────────────────────────────┘ ↓ observes ┌─────────────────────────────────────────────────────────┐ │ Domain Layer │ │ ┌──────────────────────┐ │ │ │ Repository │ │ │ │ (Business Logic) │ │ │ └──────────────────────┘ │ └─────────────────────────────────────────────────────────┘ ↓ calls ┌─────────────────────────────────────────────────────────┐ │ Data Layer │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Remote Data │ │ Local Data │ │ │ │ (API) │ │ (SharedPreferences) │ │ └─────────────────┘ └─────────────────┘ │ └─────────────────────────────────────────────────────────┘ ``` ### 8.2 数据流向 #### 请求流程 ``` Fragment/Activity ↓ observes ViewModel (LiveData/StateFlow) ↓ calls Repository ↓ calls ApiService (Retrofit) ↓ intercepted by TokenAuthInterceptor (自动添加Token、处理401) ↓ OkHttp Network Request ``` #### Token刷新流程 ``` Request (401) ↓ TokenAuthInterceptor.intercept() ↓ check isRefreshing (AtomicBoolean) ↓ synchronized block ↓ AuthRepository.refreshToken() ↓ TokenManager.saveToken() ↓ retry original request with new token ``` ### 8.3 缓存策略 | 缓存类型 | 实现方式 | 应用场景 | |----------|----------|----------| | LruCache | `android.util.LruCache` | 热门仓库列表(100KB内存) | | SharedPreferences | `android.content.SharedPreferences` | Token持久化存储 | | 内存缓存 | `MutableMap` | 用户信息、仓库列表 | ### 8.4 状态管理 #### Sealed Class UI状态 ```kotlin sealed class UiState { object Idle : UiState() object Loading : UiState() data class Success(val data: T) : UiState() data class Error(val message: String) : UiState() } ``` #### ViewModel中使用 ```kotlin private val _state = MutableStateFlow>>(UiState.Loading) val state: StateFlow>> = _state // UI层观察 viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.state.collect { state -> when (state) { is UiState.Loading -> showLoading() is UiState.Success -> showData(state.data) is UiState.Error -> showError(state.message) is UiState.Idle -> {} } } } } ``` --- ## 九、技术栈 ### 9.1 核心框架 | 技术 | 版本 | 用途 | |------|------|------| | Kotlin | 1.x | 开发语言 | | Jetpack Lifecycle | - | ViewModel, LiveData | | Jetpack Navigation | 2.8.1 | 导航管理 + Safe Args | | ViewBinding | - | 视图绑定 | | Coroutines | - | 协程异步处理 | | Flow | - | 响应式数据流 | ### 9.2 网络与数据 | 技术 | 版本 | 用途 | |------|------|------| | Retrofit | 2.9.0 | 网络请求 | | OkHttp | 4.12.0 | HTTP客户端 | | Gson Converter | 2.9.0 | JSON序列化 | | Moshi Converter | 2.9.0 | 备用JSON序列化 | ### 9.3 UI与动画 | 技术 | 版本 | 用途 | |------|------|------| | Material Design | - | Material设计组件 | | Lottie | 6.6.6 | 动画支持 | | SmartRefreshLayout | 3.0.0 | 下拉刷新 | | Glide | 5.0.5 | 图片加载 | | ViewPager2 | - | 引导页滑动画 | ### 9.4 工具库 | 技术 | 版本 | 用途 | |------|------|------| | Logger | 2.2.0 | 日志工具 | | LeakCanary | 2.9.1 | 内存泄漏检测(debug) | | Room | 2.6.1 | 数据库(已配置) | | WorkManager | 2.11.0 | 后台任务(已配置) | | Android-Image-Cropper | 4.5.0 | 图片裁剪 | --- ## 十、项目结构 ``` app/src/main/java/edu/guigu/gitcode/ ├── base/ # 基础框架层 │ ├── BaseApplication.kt │ ├── BaseFragment.kt │ └── LifecylelogFragment.kt ├── core/ # 核心业务层(数据层) │ ├── data/ │ │ ├── local/ │ │ │ └── TokenManager.kt │ │ ├── model/ │ │ │ ├── AuthResponse.kt │ │ │ ├── UserResponse.kt │ │ │ ├── RepoResponse.kt │ │ │ └── Profile.kt │ │ ├── remote/ │ │ │ ├── RetrofitClient.kt │ │ │ ├── api/ │ │ │ │ ├── AuthApiService.kt │ │ │ │ ├── UserApiService.kt │ │ │ │ ├── RepoApiService.kt │ │ │ │ └── ProfileApiService.kt │ │ │ └── interceptor/ │ │ │ └── TokenAuthInterceptor.kt │ │ └── repository/ │ │ ├── AuthRepository.kt │ │ ├── SearchRepository.kt │ │ ├── ProfileRepository.kt │ │ ├── RepoRepository.kt │ │ └── UploadRepository.kt ├── feature/ # 功能模块层(表现层) │ ├── home/ │ │ ├── HomeFragment.kt │ │ ├── hotrepo/ │ │ │ ├── HotrepoFragment.kt │ │ │ ├── HotrepoTabFragment.kt │ │ │ └── HotrepoViewModel.kt │ │ ├── notify/ │ │ │ ├── NotifyFragment.kt │ │ │ └── NotifyViewModel.kt │ │ └── profile/ │ │ ├── ProfileFragment.kt │ │ ├── ProfileTabFragment.kt │ │ ├── ProfileViewModel.kt │ │ └── edit/ │ │ ├── EditProfileDialogFragment.kt │ │ └── EditProfileDialogViewModel.kt │ ├── oauth/ │ │ ├── AuthFragment.kt │ │ ├── AuthViewModel.kt │ │ └── ui/ │ │ ├── AuthloginFragment.kt │ │ └── AuthWebviewFragment.kt │ ├── search/ │ │ ├── SearchFragment.kt │ │ ├── SearchViewModel.kt │ │ ├── Bean.kt │ │ └── detail/ │ │ ├── DeveloperDetailDialogFragment.kt │ │ └── DeveloperDetailViewModel.kt │ └── settings/ │ ├── SettingFragment.kt │ └── SettingViewModel.kt ├── start/ # 启动引导模块 │ ├── StartFragment.kt │ ├── Page0Fragment.kt │ ├── Page1Fragment.kt │ └── Page2Fragment.kt ├── common/ # 公共组件 │ ├── ui/ │ │ └── WebViewFragment.kt │ └── utils/ │ ├── Base64ImageHelper.kt │ └── UriToFileHelper.kt └── MainActivity.kt # 主Activity ``` --- ## 十一、配置说明 ### 11.1 OAuth配置 在项目根目录的 `local.properties` 中配置GitCode OAuth参数: ```properties GITCODE_CLIENT_ID=your_client_id GITCODE_CLIENT_SECRET=your_client_secret GITCODE_REDIRECT_URI=gitcode://oauth/callback ``` ### 11.2 Git提交规范 本项目遵循Conventional Commits规范: ``` 类型(范围):主题 - 描述 - 描述 ``` 示例: ```bash # 新功能 git commit -m "feat(oauth): 实现 OAuth 登录功能" # 修复 bug git commit -m "fix(repo): 修复仓库详情页崩溃问题" # 文档 git commit -m "docs: 更新 README 文档" # 重构 git commit -m "refactor(network): 优化网络请求拦截器逻辑" # 测试 git commit -m "test(oauth): 添加登录流程单元测试" # 构建 git commit -m "chore: 升级 Gradle 版本" ``` --- ## 十二、项目亮点 ### 12.1 完善的认证机制 - ✅ OAuth 2.0 授权码模式 - ✅ Token自动刷新(401拦截) - ✅ 线程安全的刷新控制(AtomicBoolean) - ✅ 防止并发刷新(synchronized双重检查) - ✅ 限制重试次数,防止无限重试 ### 12.2 清晰的架构分层 - ✅ 严格遵循MVVM架构 - ✅ 表现层/领域层/数据层职责清晰 - ✅ Repository模式统一数据访问 - ✅ 单一职责原则 ### 12.3 现代化的技术栈 - ✅ Kotlin协程异步处理 - ✅ Flow响应式数据流 - ✅ LiveData/StateFlow状态管理 - ✅ Jetpack Navigation导航管理 - ✅ ViewBinding类型安全视图绑定 ### 12.4 良好的代码复用 - ✅ `BaseFragment`封装ViewBinding生命周期 - ✅ `BaseApplication`统一初始化 - ✅ Sealed Class封装UI状态 - ✅ 扩展函数简化代码 ### 12.5 性能优化 - ✅ LruCache内存缓存 - ✅ Glide图片加载与缓存 - ✅ 协程异步请求 - ✅ 防抖与节流 --- ## 十三、快速开始 ### 13.1 环境要求 - Android Studio Hedgehog (2023.1.1) 或更高版本 - JDK 11 或更高版本 - Android SDK API 28 或更高版本 - Gradle 8.x ### 13.2 克隆项目 ```bash git clone https://gitcode.com/your-repo/GitCode-Android.git cd GitCode-Android ``` ### 13.3 配置OAuth 在 `local.properties` 文件中添加: ```properties GITCODE_CLIENT_ID=your_client_id GITCODE_CLIENT_SECRET=your_client_secret GITCODE_REDIRECT_URI=gitcode://oauth/callback ``` ### 13.4 运行项目 1. 打开Android Studio 2. 打开项目 3. 等待Gradle同步完成 4. 连接Android设备或启动模拟器 5. 点击Run按钮运行 --- ## 十四、贡献指南 欢迎提交Issue和Pull Request! 1. Fork本仓库 2. 创建特性分支 (`git checkout -b feature/AmazingFeature`) 3. 提交更改 (`git commit -m 'feat: add some AmazingFeature'`) 4. 推送到分支 (`git push origin feature/AmazingFeature`) 5. 提交Pull Request --- **Made with ❤️ by GitCode Team**