移动开发专家 (Mobile Developer)
移动开发专家是 Claude Code 的移动应用开发专家,精通 iOS、Android 和跨平台移动应用开发。
核心职责
主要能力
- 跨平台开发: React Native、Flutter、Xamarin 应用开发
- 原生开发: iOS (Swift/Objective-C) 和 Android (Kotlin/Java)
- UI/UX 实现: 移动端界面设计和用户体验优化
- 性能优化: 内存管理、电池优化、网络优化
专业领域
- 移动应用架构设计
- 原生功能集成
- 应用商店发布
- 移动端安全实现
使用场景
何时使用移动开发专家
适合的场景
bash
# 跨平台应用开发
"使用 React Native 开发电商应用"
# 原生功能集成
"集成相机、GPS 和推送通知功能"
# 性能优化
"优化 Flutter 应用启动速度和内存使用"
# 应用发布
"准备 iOS App Store 和 Google Play 发布"不适合的场景
bash
# 后端开发 (应使用执行器)
"开发 REST API 服务"
# Web 开发 (应使用前端专家)
"创建响应式网站"
# 数据分析 (应使用数据分析师)
"分析用户行为数据"移动开发能力
1. React Native 开发
项目架构设计
typescript
// App.tsx - 主应用组件
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { StatusBar } from 'expo-status-bar';
import { store, persistor } from './src/store/store';
import AuthNavigator from './src/navigation/AuthNavigator';
import MainNavigator from './src/navigation/MainNavigator';
import LoadingScreen from './src/components/LoadingScreen';
import { useAppSelector } from './src/hooks/redux';
const Stack = createNativeStackNavigator();
function AppContent() {
const { isAuthenticated, isLoading } = useAppSelector(state => state.auth);
if (isLoading) {
return <LoadingScreen />;
}
return (
<NavigationContainer>
<StatusBar style="auto" />
{isAuthenticated ? <MainNavigator /> : <AuthNavigator />}
</NavigationContainer>
);
}
export default function App() {
return (
<Provider store={store}>
<PersistGate loading={<LoadingScreen />} persistor={persistor}>
<AppContent />
</PersistGate>
</Provider>
);
}状态管理 (Redux Toolkit)
typescript
// store/store.ts
import { configureStore } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { combineReducers } from '@reduxjs/toolkit';
import authSlice from './slices/authSlice';
import userSlice from './slices/userSlice';
import productsSlice from './slices/productsSlice';
import cartSlice from './slices/cartSlice';
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: ['auth', 'cart'], // 只持久化这些状态
};
const rootReducer = combineReducers({
auth: authSlice,
user: userSlice,
products: productsSlice,
cart: cartSlice,
});
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'],
},
}),
});
export const persistor = persistStore(store);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// store/slices/authSlice.ts
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { authAPI } from '../../services/api';
interface User {
id: string;
email: string;
name: string;
avatar?: string;
}
interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
}
const initialState: AuthState = {
user: null,
token: null,
isAuthenticated: false,
isLoading: false,
error: null,
};
// 异步操作
export const loginUser = createAsyncThunk(
'auth/loginUser',
async (credentials: { email: string; password: string }, { rejectWithValue }) => {
try {
const response = await authAPI.login(credentials);
return response.data;
} catch (error: any) {
return rejectWithValue(error.response?.data?.message || '登录失败');
}
}
);
export const registerUser = createAsyncThunk(
'auth/registerUser',
async (userData: { email: string; password: string; name: string }, { rejectWithValue }) => {
try {
const response = await authAPI.register(userData);
return response.data;
} catch (error: any) {
return rejectWithValue(error.response?.data?.message || '注册失败');
}
}
);
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
logout: (state) => {
state.user = null;
state.token = null;
state.isAuthenticated = false;
state.error = null;
},
clearError: (state) => {
state.error = null;
},
},
extraReducers: (builder) => {
builder
// 登录
.addCase(loginUser.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(loginUser.fulfilled, (state, action) => {
state.isLoading = false;
state.user = action.payload.user;
state.token = action.payload.token;
state.isAuthenticated = true;
})
.addCase(loginUser.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload as string;
})
// 注册
.addCase(registerUser.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(registerUser.fulfilled, (state, action) => {
state.isLoading = false;
state.user = action.payload.user;
state.token = action.payload.token;
state.isAuthenticated = true;
})
.addCase(registerUser.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload as string;
});
},
});
export const { logout, clearError } = authSlice.actions;
export default authSlice.reducer;自定义组件和 Hooks
typescript
// components/CustomButton.tsx
import React from 'react';
import {
TouchableOpacity,
Text,
StyleSheet,
ViewStyle,
TextStyle,
ActivityIndicator
} from 'react-native';
import { useTheme } from '../contexts/ThemeContext';
interface CustomButtonProps {
title: string;
onPress: () => void;
variant?: 'primary' | 'secondary' | 'outline';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
loading?: boolean;
style?: ViewStyle;
textStyle?: TextStyle;
}
export const CustomButton: React.FC<CustomButtonProps> = ({
title,
onPress,
variant = 'primary',
size = 'medium',
disabled = false,
loading = false,
style,
textStyle,
}) => {
const { theme } = useTheme();
const getButtonStyle = (): ViewStyle => {
const baseStyle: ViewStyle = {
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
};
// 尺寸样式
const sizeStyles = {
small: { paddingVertical: 8, paddingHorizontal: 16 },
medium: { paddingVertical: 12, paddingHorizontal: 24 },
large: { paddingVertical: 16, paddingHorizontal: 32 },
};
// 变体样式
const variantStyles = {
primary: {
backgroundColor: disabled ? theme.colors.disabled : theme.colors.primary,
},
secondary: {
backgroundColor: disabled ? theme.colors.disabled : theme.colors.secondary,
},
outline: {
backgroundColor: 'transparent',
borderWidth: 1,
borderColor: disabled ? theme.colors.disabled : theme.colors.primary,
},
};
return {
...baseStyle,
...sizeStyles[size],
...variantStyles[variant],
};
};
const getTextStyle = (): TextStyle => {
const baseStyle: TextStyle = {
fontWeight: '600',
fontSize: size === 'small' ? 14 : size === 'medium' ? 16 : 18,
};
const variantTextStyles = {
primary: { color: theme.colors.onPrimary },
secondary: { color: theme.colors.onSecondary },
outline: { color: disabled ? theme.colors.disabled : theme.colors.primary },
};
return {
...baseStyle,
...variantTextStyles[variant],
};
};
return (
<TouchableOpacity
style={[getButtonStyle(), style]}
onPress={onPress}
disabled={disabled || loading}
activeOpacity={0.7}
>
{loading ? (
<ActivityIndicator
size="small"
color={variant === 'outline' ? theme.colors.primary : theme.colors.onPrimary}
/>
) : (
<Text style={[getTextStyle(), textStyle]}>{title}</Text>
)}
</TouchableOpacity>
);
};
// hooks/useApi.ts
import { useState, useEffect } from 'react';
import { useAppSelector } from './redux';
interface ApiState<T> {
data: T | null;
loading: boolean;
error: string | null;
}
export function useApi<T>(
apiCall: () => Promise<T>,
dependencies: any[] = []
): ApiState<T> & { refetch: () => Promise<void> } {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const result = await apiCall();
setData(result);
} catch (err: any) {
setError(err.message || '请求失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, dependencies);
return {
data,
loading,
error,
refetch: fetchData,
};
}
// hooks/useNetworkStatus.ts
import { useState, useEffect } from 'react';
import NetInfo from '@react-native-netinfo/netinfo';
export const useNetworkStatus = () => {
const [isConnected, setIsConnected] = useState<boolean | null>(null);
const [connectionType, setConnectionType] = useState<string | null>(null);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
setIsConnected(state.isConnected);
setConnectionType(state.type);
});
return unsubscribe;
}, []);
return { isConnected, connectionType };
};原生功能集成
typescript
// services/CameraService.ts
import { launchCamera, launchImageLibrary, MediaType } from 'react-native-image-picker';
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
import { Platform } from 'react-native';
export class CameraService {
static async requestCameraPermission(): Promise<boolean> {
try {
const permission = Platform.OS === 'ios'
? PERMISSIONS.IOS.CAMERA
: PERMISSIONS.ANDROID.CAMERA;
const result = await request(permission);
return result === RESULTS.GRANTED;
} catch (error) {
console.error('相机权限请求失败:', error);
return false;
}
}
static async takePhoto(): Promise<string | null> {
const hasPermission = await this.requestCameraPermission();
if (!hasPermission) {
throw new Error('相机权限被拒绝');
}
return new Promise((resolve, reject) => {
launchCamera(
{
mediaType: 'photo' as MediaType,
quality: 0.8,
maxWidth: 1024,
maxHeight: 1024,
},
(response) => {
if (response.didCancel) {
resolve(null);
} else if (response.errorMessage) {
reject(new Error(response.errorMessage));
} else if (response.assets && response.assets[0]) {
resolve(response.assets[0].uri || null);
} else {
resolve(null);
}
}
);
});
}
static async pickFromLibrary(): Promise<string | null> {
return new Promise((resolve, reject) => {
launchImageLibrary(
{
mediaType: 'photo' as MediaType,
quality: 0.8,
maxWidth: 1024,
maxHeight: 1024,
},
(response) => {
if (response.didCancel) {
resolve(null);
} else if (response.errorMessage) {
reject(new Error(response.errorMessage));
} else if (response.assets && response.assets[0]) {
resolve(response.assets[0].uri || null);
} else {
resolve(null);
}
}
);
});
}
}
// services/LocationService.ts
import Geolocation from '@react-native-community/geolocation';
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
import { Platform } from 'react-native';
interface Location {
latitude: number;
longitude: number;
accuracy: number;
}
export class LocationService {
static async requestLocationPermission(): Promise<boolean> {
try {
const permission = Platform.OS === 'ios'
? PERMISSIONS.IOS.LOCATION_WHEN_IN_USE
: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION;
const result = await request(permission);
return result === RESULTS.GRANTED;
} catch (error) {
console.error('位置权限请求失败:', error);
return false;
}
}
static async getCurrentLocation(): Promise<Location> {
const hasPermission = await this.requestLocationPermission();
if (!hasPermission) {
throw new Error('位置权限被拒绝');
}
return new Promise((resolve, reject) => {
Geolocation.getCurrentPosition(
(position) => {
resolve({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
});
},
(error) => {
reject(new Error(`获取位置失败: ${error.message}`));
},
{
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 10000,
}
);
});
}
static watchLocation(callback: (location: Location) => void): number {
return Geolocation.watchPosition(
(position) => {
callback({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
});
},
(error) => {
console.error('位置监听错误:', error);
},
{
enableHighAccuracy: true,
distanceFilter: 10,
}
);
}
static clearWatch(watchId: number): void {
Geolocation.clearWatch(watchId);
}
}
// services/NotificationService.ts
import PushNotification from 'react-native-push-notification';
import PushNotificationIOS from '@react-native-community/push-notification-ios';
import { Platform } from 'react-native';
export class NotificationService {
static configure() {
PushNotification.configure({
onRegister: function (token) {
console.log('推送令牌:', token);
// 发送令牌到服务器
},
onNotification: function (notification) {
console.log('收到通知:', notification);
if (Platform.OS === 'ios') {
notification.finish(PushNotificationIOS.FetchResult.NoData);
}
},
onAction: function (notification) {
console.log('通知动作:', notification.action);
},
onRegistrationError: function(err) {
console.error('推送注册错误:', err.message);
},
permissions: {
alert: true,
badge: true,
sound: true,
},
popInitialNotification: true,
requestPermissions: Platform.OS === 'ios',
});
// 创建默认频道 (Android)
if (Platform.OS === 'android') {
PushNotification.createChannel(
{
channelId: 'default-channel',
channelName: '默认通知',
channelDescription: '应用默认通知频道',
soundName: 'default',
importance: 4,
vibrate: true,
},
(created) => console.log(`通知频道创建: ${created}`)
);
}
}
static localNotification(title: string, message: string, data?: any) {
PushNotification.localNotification({
title,
message,
data,
channelId: 'default-channel',
});
}
static scheduleNotification(title: string, message: string, date: Date) {
PushNotification.localNotificationSchedule({
title,
message,
date,
channelId: 'default-channel',
});
}
static cancelAllNotifications() {
PushNotification.cancelAllLocalNotifications();
}
static setBadgeCount(count: number) {
if (Platform.OS === 'ios') {
PushNotificationIOS.setApplicationIconBadgeNumber(count);
}
}
}2. Flutter 开发
应用架构 (BLoC Pattern)
dart
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart';
import 'app/app.dart';
import 'app/app_bloc_observer.dart';
import 'repositories/repositories.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 配置 HydratedBloc 存储
HydratedBloc.storage = await HydratedStorage.build(
storageDirectory: await getApplicationDocumentsDirectory(),
);
// 设置 BLoC 观察者
Bloc.observer = AppBlocObserver();
// 初始化仓库
final authRepository = AuthRepository();
final userRepository = UserRepository();
final productsRepository = ProductsRepository();
runApp(
MultiRepositoryProvider(
providers: [
RepositoryProvider.value(value: authRepository),
RepositoryProvider.value(value: userRepository),
RepositoryProvider.value(value: productsRepository),
],
child: App(),
),
);
}
// app/app.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../auth/bloc/auth_bloc.dart';
import '../home/view/home_page.dart';
import '../login/view/login_page.dart';
import '../repositories/repositories.dart';
import '../theme/app_theme.dart';
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => AuthBloc(
authRepository: context.read<AuthRepository>(),
)..add(AuthStatusRequested()),
child: AppView(),
);
}
}
class AppView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
home: BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
switch (state.status) {
case AuthStatus.authenticated:
return HomePage();
case AuthStatus.unauthenticated:
return LoginPage();
case AuthStatus.unknown:
default:
return SplashPage();
}
},
),
);
}
}
// auth/bloc/auth_bloc.dart
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import '../../models/models.dart';
import '../../repositories/repositories.dart';
part 'auth_event.dart';
part 'auth_state.dart';
class AuthBloc extends HydratedBloc<AuthEvent, AuthState> {
final AuthRepository _authRepository;
AuthBloc({required AuthRepository authRepository})
: _authRepository = authRepository,
super(const AuthState.unknown()) {
on<AuthStatusRequested>(_onAuthStatusRequested);
on<AuthLoginRequested>(_onAuthLoginRequested);
on<AuthLogoutRequested>(_onAuthLogoutRequested);
}
void _onAuthStatusRequested(
AuthStatusRequested event,
Emitter<AuthState> emit,
) async {
final user = await _authRepository.getCurrentUser();
if (user != null) {
emit(AuthState.authenticated(user));
} else {
emit(const AuthState.unauthenticated());
}
}
void _onAuthLoginRequested(
AuthLoginRequested event,
Emitter<AuthState> emit,
) async {
emit(state.copyWith(status: AuthStatus.loading));
try {
final user = await _authRepository.login(
email: event.email,
password: event.password,
);
emit(AuthState.authenticated(user));
} catch (error) {
emit(AuthState.unauthenticated(error: error.toString()));
}
}
void _onAuthLogoutRequested(
AuthLogoutRequested event,
Emitter<AuthState> emit,
) async {
await _authRepository.logout();
emit(const AuthState.unauthenticated());
}
@override
AuthState? fromJson(Map<String, dynamic> json) {
try {
final status = AuthStatus.values.firstWhere(
(status) => status.toString() == json['status'] as String,
);
if (status == AuthStatus.authenticated) {
return AuthState.authenticated(
User.fromJson(json['user'] as Map<String, dynamic>),
);
}
return AuthState.unauthenticated(
error: json['error'] as String?,
);
} catch (_) {
return null;
}
}
@override
Map<String, dynamic>? toJson(AuthState state) {
return {
'status': state.status.toString(),
'user': state.user?.toJson(),
'error': state.error,
};
}
}
// 自定义组件
// widgets/custom_button.dart
import 'package:flutter/material.dart';
enum ButtonVariant { primary, secondary, outline }
enum ButtonSize { small, medium, large }
class CustomButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
final ButtonVariant variant;
final ButtonSize size;
final bool isLoading;
final Widget? icon;
const CustomButton({
Key? key,
required this.text,
this.onPressed,
this.variant = ButtonVariant.primary,
this.size = ButtonSize.medium,
this.isLoading = false,
this.icon,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return SizedBox(
height: _getButtonHeight(),
child: ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: _getButtonStyle(theme),
child: isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
_getTextColor(theme),
),
),
)
: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
icon!,
const SizedBox(width: 8),
],
Text(
text,
style: _getTextStyle(theme),
),
],
),
),
);
}
double _getButtonHeight() {
switch (size) {
case ButtonSize.small:
return 32;
case ButtonSize.medium:
return 44;
case ButtonSize.large:
return 56;
}
}
ButtonStyle _getButtonStyle(ThemeData theme) {
Color backgroundColor;
Color borderColor = Colors.transparent;
switch (variant) {
case ButtonVariant.primary:
backgroundColor = theme.primaryColor;
break;
case ButtonVariant.secondary:
backgroundColor = theme.colorScheme.secondary;
break;
case ButtonVariant.outline:
backgroundColor = Colors.transparent;
borderColor = theme.primaryColor;
break;
}
return ElevatedButton.styleFrom(
backgroundColor: backgroundColor,
foregroundColor: _getTextColor(theme),
side: variant == ButtonVariant.outline
? BorderSide(color: borderColor)
: null,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: variant == ButtonVariant.outline ? 0 : 2,
);
}
TextStyle _getTextStyle(ThemeData theme) {
double fontSize;
switch (size) {
case ButtonSize.small:
fontSize = 14;
break;
case ButtonSize.medium:
fontSize = 16;
break;
case ButtonSize.large:
fontSize = 18;
break;
}
return TextStyle(
fontSize: fontSize,
fontWeight: FontWeight.w600,
color: _getTextColor(theme),
);
}
Color _getTextColor(ThemeData theme) {
switch (variant) {
case ButtonVariant.primary:
return theme.colorScheme.onPrimary;
case ButtonVariant.secondary:
return theme.colorScheme.onSecondary;
case ButtonVariant.outline:
return theme.primaryColor;
}
}
}
// 性能优化的列表组件
// widgets/optimized_list_view.dart
import 'package:flutter/material.dart';
class OptimizedListView<T> extends StatelessWidget {
final List<T> items;
final Widget Function(BuildContext, T, int) itemBuilder;
final Future<void> Function()? onRefresh;
final VoidCallback? onLoadMore;
final bool hasMore;
final bool isLoading;
const OptimizedListView({
Key? key,
required this.items,
required this.itemBuilder,
this.onRefresh,
this.onLoadMore,
this.hasMore = false,
this.isLoading = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Widget listView = ListView.builder(
itemCount: items.length + (hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index >= items.length) {
// 加载更多指示器
if (onLoadMore != null && !isLoading) {
// 在下一个事件循环中调用 onLoadMore
WidgetsBinding.instance.addPostFrameCallback((_) {
onLoadMore!();
});
}
return const Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: CircularProgressIndicator(),
),
);
}
return itemBuilder(context, items[index], index);
},
// 性能优化设置
cacheExtent: 1000, // 预加载范围
physics: const AlwaysScrollableScrollPhysics(),
);
if (onRefresh != null) {
return RefreshIndicator(
onRefresh: onRefresh!,
child: listView,
);
}
return listView;
}
}原生集成 (Platform Channels)
dart
// services/native_service.dart
import 'package:flutter/services.dart';
class NativeService {
static const MethodChannel _channel = MethodChannel('com.example.app/native');
// 获取设备信息
static Future<Map<String, dynamic>> getDeviceInfo() async {
try {
final result = await _channel.invokeMethod('getDeviceInfo');
return Map<String, dynamic>.from(result);
} on PlatformException catch (e) {
throw Exception('获取设备信息失败: ${e.message}');
}
}
// 调用原生相机
static Future<String?> openNativeCamera() async {
try {
final result = await _channel.invokeMethod('openCamera');
return result as String?;
} on PlatformException catch (e) {
throw Exception('打开相机失败: ${e.message}');
}
}
// 设置应用图标角标 (iOS)
static Future<void> setBadgeCount(int count) async {
try {
await _channel.invokeMethod('setBadgeCount', {'count': count});
} on PlatformException catch (e) {
print('设置角标失败: ${e.message}');
}
}
}
// services/secure_storage_service.dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureStorageService {
static const _storage = FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
),
iOptions: IOSOptions(
accessibility: IOSAccessibility.biometryCurrentSet,
),
);
static Future<void> store(String key, String value) async {
await _storage.write(key: key, value: value);
}
static Future<String?> read(String key) async {
return await _storage.read(key: key);
}
static Future<void> delete(String key) async {
await _storage.delete(key: key);
}
static Future<void> deleteAll() async {
await _storage.deleteAll();
}
// 存储认证令牌
static Future<void> storeAuthToken(String token) async {
await store('auth_token', token);
}
static Future<String?> getAuthToken() async {
return await read('auth_token');
}
static Future<void> clearAuthToken() async {
await delete('auth_token');
}
}3. iOS 原生开发 (Swift)
应用架构 (MVVM + Combine)
swift
// Models/User.swift
import Foundation
struct User: Codable, Identifiable {
let id: String
let email: String
let name: String
let avatar: String?
let createdAt: Date
enum CodingKeys: String, CodingKey {
case id, email, name, avatar
case createdAt = "created_at"
}
}
struct LoginRequest: Codable {
let email: String
let password: String
}
struct LoginResponse: Codable {
let user: User
let token: String
}
// Services/NetworkService.swift
import Foundation
import Combine
class NetworkService {
static let shared = NetworkService()
private let session = URLSession.shared
private let baseURL = "https://api.example.com"
private init() {}
private func request<T: Codable>(
endpoint: String,
method: HTTPMethod = .GET,
body: Data? = nil,
type: T.Type
) -> AnyPublisher<T, Error> {
guard let url = URL(string: baseURL + endpoint) else {
return Fail(error: NetworkError.invalidURL)
.eraseToAnyPublisher()
}
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
// 添加认证头
if let token = UserDefaults.standard.string(forKey: "auth_token") {
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
if let body = body {
request.httpBody = body
}
return session.dataTaskPublisher(for: request)
.map(\.data)
.decode(type: type, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
func login(email: String, password: String) -> AnyPublisher<LoginResponse, Error> {
let loginRequest = LoginRequest(email: email, password: password)
let body = try? JSONEncoder().encode(loginRequest)
return request(
endpoint: "/auth/login",
method: .POST,
body: body,
type: LoginResponse.self
)
}
func fetchUser(id: String) -> AnyPublisher<User, Error> {
return request(
endpoint: "/users/\(id)",
method: .GET,
type: User.self
)
}
}
enum HTTPMethod: String {
case GET = "GET"
case POST = "POST"
case PUT = "PUT"
case DELETE = "DELETE"
}
enum NetworkError: Error {
case invalidURL
case noData
case decodingError
}
// ViewModels/LoginViewModel.swift
import Foundation
import Combine
class LoginViewModel: ObservableObject {
@Published var email = ""
@Published var password = ""
@Published var isLoading = false
@Published var errorMessage: String?
@Published var isLoggedIn = false
private var cancellables = Set<AnyCancellable>()
private let networkService = NetworkService.shared
var isLoginButtonEnabled: Bool {
!email.isEmpty && !password.isEmpty && !isLoading
}
func login() {
guard !email.isEmpty && !password.isEmpty else {
errorMessage = "请填写邮箱和密码"
return
}
isLoading = true
errorMessage = nil
networkService.login(email: email, password: password)
.sink(
receiveCompletion: { [weak self] completion in
self?.isLoading = false
if case .failure(let error) = completion {
self?.errorMessage = error.localizedDescription
}
},
receiveValue: { [weak self] response in
// 保存认证令牌
UserDefaults.standard.set(response.token, forKey: "auth_token")
// 保存用户信息
if let userData = try? JSONEncoder().encode(response.user) {
UserDefaults.standard.set(userData, forKey: "user_data")
}
self?.isLoggedIn = true
}
)
.store(in: &cancellables)
}
func logout() {
UserDefaults.standard.removeObject(forKey: "auth_token")
UserDefaults.standard.removeObject(forKey: "user_data")
isLoggedIn = false
email = ""
password = ""
}
}
// Views/LoginView.swift
import SwiftUI
struct LoginView: View {
@StateObject private var viewModel = LoginViewModel()
var body: some View {
NavigationView {
VStack(spacing: 20) {
// 标题
Text("欢迎回来")
.font(.largeTitle)
.fontWeight(.bold)
VStack(spacing: 16) {
// 邮箱输入框
CustomTextField(
text: $viewModel.email,
placeholder: "邮箱",
keyboardType: .emailAddress
)
// 密码输入框
CustomTextField(
text: $viewModel.password,
placeholder: "密码",
isSecure: true
)
// 错误消息
if let errorMessage = viewModel.errorMessage {
Text(errorMessage)
.foregroundColor(.red)
.font(.caption)
}
// 登录按钮
CustomButton(
title: "登录",
isLoading: viewModel.isLoading,
isEnabled: viewModel.isLoginButtonEnabled
) {
viewModel.login()
}
}
.padding(.horizontal, 24)
Spacer()
}
.padding()
.navigationBarHidden(true)
}
.fullScreenCover(isPresented: $viewModel.isLoggedIn) {
MainTabView()
}
}
}
// CustomViews/CustomTextField.swift
import SwiftUI
struct CustomTextField: View {
@Binding var text: String
let placeholder: String
let keyboardType: UIKeyboardType
let isSecure: Bool
init(
text: Binding<String>,
placeholder: String,
keyboardType: UIKeyboardType = .default,
isSecure: Bool = false
) {
self._text = text
self.placeholder = placeholder
self.keyboardType = keyboardType
self.isSecure = isSecure
}
var body: some View {
Group {
if isSecure {
SecureField(placeholder, text: $text)
} else {
TextField(placeholder, text: $text)
.keyboardType(keyboardType)
.autocapitalization(.none)
.disableAutocorrection(true)
}
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray.opacity(0.3), lineWidth: 1)
)
}
}
struct CustomButton: View {
let title: String
let isLoading: Bool
let isEnabled: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
HStack {
if isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.scaleEffect(0.8)
} else {
Text(title)
.fontWeight(.semibold)
}
}
.frame(maxWidth: .infinity)
.frame(height: 50)
.background(isEnabled ? Color.blue : Color.gray)
.foregroundColor(.white)
.cornerRadius(10)
}
.disabled(!isEnabled || isLoading)
}
}4. Android 原生开发 (Kotlin)
MVVM 架构
kotlin
// data/models/User.kt
data class User(
val id: String,
val email: String,
val name: String,
val avatar: String? = null,
val createdAt: String
)
data class LoginRequest(
val email: String,
val password: String
)
data class LoginResponse(
val user: User,
val token: String
)
// data/repository/AuthRepository.kt
import retrofit2.Response
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AuthRepository @Inject constructor(
private val apiService: ApiService,
private val tokenManager: TokenManager
) {
suspend fun login(email: String, password: String): Result<LoginResponse> {
return try {
val response = apiService.login(LoginRequest(email, password))
if (response.isSuccessful) {
response.body()?.let { loginResponse ->
tokenManager.saveToken(loginResponse.token)
Result.success(loginResponse)
} ?: Result.failure(Exception("登录响应为空"))
} else {
Result.failure(Exception("登录失败: ${response.message()}"))
}
} catch (e: Exception) {
Result.failure(e)
}
}
suspend fun getCurrentUser(): User? {
return try {
val response = apiService.getCurrentUser()
if (response.isSuccessful) {
response.body()
} else {
null
}
} catch (e: Exception) {
null
}
}
fun logout() {
tokenManager.clearToken()
}
fun isLoggedIn(): Boolean {
return tokenManager.getToken() != null
}
}
// ui/login/LoginViewModel.kt
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
data class LoginUiState(
val isLoading: Boolean = false,
val isLoggedIn: Boolean = false,
val errorMessage: String? = null
)
@HiltViewModel
class LoginViewModel @Inject constructor(
private val authRepository: AuthRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(LoginUiState())
val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()
val email = MutableStateFlow("")
val password = MutableStateFlow("")
init {
// 检查是否已登录
_uiState.value = _uiState.value.copy(
isLoggedIn = authRepository.isLoggedIn()
)
}
fun login() {
if (email.value.isEmpty() || password.value.isEmpty()) {
_uiState.value = _uiState.value.copy(
errorMessage = "请填写邮箱和密码"
)
return
}
viewModelScope.launch {
_uiState.value = _uiState.value.copy(
isLoading = true,
errorMessage = null
)
authRepository.login(email.value, password.value)
.onSuccess {
_uiState.value = _uiState.value.copy(
isLoading = false,
isLoggedIn = true
)
}
.onFailure { exception ->
_uiState.value = _uiState.value.copy(
isLoading = false,
errorMessage = exception.message
)
}
}
}
fun logout() {
authRepository.logout()
_uiState.value = _uiState.value.copy(isLoggedIn = false)
email.value = ""
password.value = ""
}
fun clearError() {
_uiState.value = _uiState.value.copy(errorMessage = null)
}
}
// ui/login/LoginActivity.kt
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class LoginActivity : ComponentActivity() {
private val viewModel: LoginViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme {
LoginScreen(viewModel = viewModel)
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LoginScreen(viewModel: LoginViewModel) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val email by viewModel.email.collectAsStateWithLifecycle()
val password by viewModel.password.collectAsStateWithLifecycle()
val context = LocalContext.current
// 监听登录状态变化
LaunchedEffect(uiState.isLoggedIn) {
if (uiState.isLoggedIn) {
// 跳转到主页
context.startActivity(Intent(context, MainActivity::class.java))
(context as? Activity)?.finish()
}
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
// 标题
Text(
text = "欢迎回来",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(bottom = 32.dp)
)
// 邮箱输入框
OutlinedTextField(
value = email,
onValueChange = viewModel.email::value::set,
label = { Text("邮箱") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
Spacer(modifier = Modifier.height(16.dp))
// 密码输入框
OutlinedTextField(
value = password,
onValueChange = viewModel.password::value::set,
label = { Text("密码") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
// 错误消息
uiState.errorMessage?.let { error ->
Text(
text = error,
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(top = 8.dp)
)
}
Spacer(modifier = Modifier.height(24.dp))
// 登录按钮
Button(
onClick = viewModel::login,
enabled = email.isNotEmpty() && password.isNotEmpty() && !uiState.isLoading,
modifier = Modifier.fillMaxWidth()
) {
if (uiState.isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(16.dp),
strokeWidth = 2.dp
)
} else {
Text("登录")
}
}
}
}5. 性能优化
通用性能优化策略
typescript
// React Native 性能优化
// utils/PerformanceOptimizer.ts
import { InteractionManager, Platform } from 'react-native';
export class PerformanceOptimizer {
// 延迟执行非关键任务
static delayForInteraction<T>(task: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
InteractionManager.runAfterInteractions(() => {
task().then(resolve).catch(reject);
});
});
}
// 内存监控
static monitorMemoryUsage() {
if (__DEV__) {
setInterval(() => {
if (Platform.OS === 'android') {
// Android 内存监控
console.log('Memory usage:', performance.memory);
}
}, 5000);
}
}
// 图片优化
static getOptimizedImageUri(uri: string, width: number, height: number): string {
// 使用 CDN 服务进行图片压缩
return `${uri}?w=${width}&h=${height}&q=80&f=webp`;
}
// 网络请求缓存
private static cache = new Map<string, { data: any; timestamp: number }>();
static async cachedFetch(url: string, options: any = {}, cacheTime = 300000): Promise<any> {
const cacheKey = `${url}_${JSON.stringify(options)}`;
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < cacheTime) {
return cached.data;
}
const response = await fetch(url, options);
const data = await response.json();
this.cache.set(cacheKey, {
data,
timestamp: Date.now()
});
return data;
}
}
// components/OptimizedImage.tsx
import React, { useState, useCallback } from 'react';
import { Image, ImageStyle, ViewStyle } from 'react-native';
import FastImage from 'react-native-fast-image';
interface OptimizedImageProps {
source: { uri: string };
style?: ImageStyle & ViewStyle;
width: number;
height: number;
placeholder?: string;
onLoad?: () => void;
onError?: () => void;
}
export const OptimizedImage: React.FC<OptimizedImageProps> = ({
source,
style,
width,
height,
placeholder,
onLoad,
onError,
}) => {
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
const optimizedUri = PerformanceOptimizer.getOptimizedImageUri(
source.uri,
width * 2, // Retina support
height * 2
);
const handleLoad = useCallback(() => {
setIsLoading(false);
onLoad?.();
}, [onLoad]);
const handleError = useCallback(() => {
setIsLoading(false);
setHasError(true);
onError?.();
}, [onError]);
if (hasError && placeholder) {
return (
<FastImage
source={{ uri: placeholder }}
style={[{ width, height }, style]}
resizeMode={FastImage.resizeMode.cover}
/>
);
}
return (
<FastImage
source={{
uri: optimizedUri,
priority: FastImage.priority.normal,
cache: FastImage.cacheControl.immutable,
}}
style={[{ width, height }, style]}
onLoad={handleLoad}
onError={handleError}
resizeMode={FastImage.resizeMode.cover}
/>
);
};使用技巧
1. 明确平台需求
bash
# 跨平台开发
"使用 React Native 开发同时支持 iOS 和 Android 的社交应用"
# 原生开发
"开发 iOS 应用,需要集成 ARKit 和深度相机功能"2. 性能要求
bash
# 启动时间
"应用启动时间需要在 3 秒内"
# 内存使用
"在低端设备上运行流畅,内存使用不超过 150MB"3. 功能需求
bash
# 原生功能
"需要集成推送通知、地理位置、相机、生物识别"
# 离线支持
"支持离线模式,数据同步功能"最佳实践
1. 架构设计
- 使用 MVVM 或 Redux 管理状态
- 分层架构:UI、业务逻辑、数据层
- 依赖注入提高可测试性
2. 性能优化
- 延迟加载和代码分割
- 图片压缩和缓存
- 内存泄漏监控
3. 用户体验
- 响应式设计适配不同屏幕
- 流畅的动画和转场
- 离线功能和网络容错
4. 安全考虑
- 数据加密存储
- 网络传输安全
- 代码混淆和反调试
相关资源
- UI/UX设计师指南 - 移动端界面设计
- DevOps 工程师 - 移动应用 CI/CD
- 执行器实战教程 - 代码实现
- 移动开发最佳实践 - 深入学习
移动开发专家 - 让应用触手可及