1. 项目概述:当UI适配遇上设计模式
在客户端开发领域,UI适配一直是个令人头疼的"钉子户"问题。不同设备尺寸、分辨率、横竖屏状态以及主题切换需求,常常让开发者陷入if-else的泥潭。去年我们团队重构电商App时,就遇到了这样的困境:原有代码中充斥着大量形如if (screenWidth < 1080) {...}的条件判断,每次新增设备支持都需要修改十余处逻辑。
经过多次迭代,我们最终采用策略模式+抽象工厂的组合拳,将适配逻辑的复杂度从O(n)降到O(1)。这种架构不仅支撑了后续20多种设备的无缝适配,还让主题切换功能的开发周期从2周缩短到3天。下面我就拆解这个方案的设计思路和实现细节。
2. 核心设计思路解析
2.1 策略模式:动态切换适配算法
策略模式的本质是定义算法族,将每个算法封装起来,使它们可以互相替换。在UI适配场景中,不同设备的布局规则就是不同的算法实现。
我们定义了LayoutStrategy接口:
java复制public interface LayoutStrategy {
ViewGroup applyLayout(ViewGroup rootView);
int calculateSpacing();
float adjustFontSize(float baseSize);
}
然后为不同设备实现具体策略:
java复制// 手机竖屏策略
public class PhonePortraitStrategy implements LayoutStrategy {
@Override
public ViewGroup applyLayout(ViewGroup rootView) {
// 单列布局实现
LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
return layout;
}
}
// 平板横屏策略
public class TabletLandscapeStrategy implements LayoutStrategy {
@Override
public ViewGroup applyLayout(ViewGroup rootView) {
// 网格布局实现
GridLayout layout = new GridLayout(context);
layout.setColumnCount(3);
return layout;
}
}
2.2 抽象工厂:统一创建适配组件
抽象工厂模式提供接口来创建相关或依赖对象的家族,而不需要明确指定具体类。我们将适配相关的UI组件抽象为产品族:
java复制public interface UIComponentFactory {
Button createButton();
TextView createTextView();
ImageView createImageView();
}
// 手机组件工厂
public class PhoneComponentFactory implements UIComponentFactory {
@Override
public Button createButton() {
Button btn = new Button(context);
btn.setMinWidth(dpToPx(120)); // 手机端最小宽度
return btn;
}
}
// 平板组件工厂
public class TabletComponentFactory implements UIComponentFactory {
@Override
public Button createButton() {
Button btn = new Button(context);
btn.setMinWidth(dpToPx(200)); // 平板端最小宽度
return btn;
}
}
2.3 模式联动的架构设计
两种模式的协作关系如下图所示(伪代码表示):
code复制┌───────────────────────┐ ┌───────────────────────┐
│ Client │ │ LayoutContext │
│ │ │ │
│ +setStrategy() │<>---->│ +executeStrategy() │
│ +createUI() │ └───────────────────────┘
│ │ ▲
└──────────┬────────────┘ │
│ │
▼ │
┌───────────────────────┐ ┌───────────────────────┐
│ AbstractUIFactory │ │ LayoutStrategy │
│ │ │ │
│ +createButton() │ │ +applyLayout() │
│ +createTextView() │ │ +calculateSpacing() │
└───────────────────────┘ └───────────────────────┘
▲ ▲
│ │
┌───────────────────────┐ ┌───────────────────────┐
│ ConcreteUIFactory │ │ ConcreteStrategy │
│ (Phone/Tablet) │ │ (Portrait/Landscape) │
└───────────────────────┘ └───────────────────────┘
关键协作流程:
- 设备检测模块识别当前设备特征
- 策略工厂根据特征创建对应的LayoutStrategy
- 组件工厂根据特征创建对应的UIComponentFactory
- Context持有当前策略实例,执行布局算法
- 所有UI组件通过抽象工厂接口创建
3. 实现细节与核心代码
3.1 环境感知与策略选择
设备特征检测是关键的第一步,我们封装了DeviceProfile类:
java复制public class DeviceProfile {
private static final int PHONE_MAX_WIDTH_DP = 600;
private static final int TV_MIN_WIDTH_DP = 720;
public static DeviceType detectDeviceType() {
Configuration config = context.getResources().getConfiguration();
int screenWidthDp = config.screenWidthDp;
if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
if (screenWidthDp >= TV_MIN_WIDTH_DP) return DeviceType.TV;
return DeviceType.TABLET;
} else {
if (screenWidthDp <= PHONE_MAX_WIDTH_DP) return DeviceType.PHONE;
return DeviceType.TABLET;
}
}
}
3.2 策略上下文实现
LayoutContext是策略模式的核心枢纽:
java复制public class LayoutContext {
private LayoutStrategy strategy;
public void setStrategy(LayoutStrategy strategy) {
this.strategy = strategy;
}
public ViewGroup applyCurrentStrategy(ViewGroup root) {
if (strategy == null) {
throw new IllegalStateException("Strategy not initialized");
}
return strategy.applyLayout(root);
}
public float getAdjustedFontSize(float baseSize) {
return strategy.adjustFontSize(baseSize);
}
}
3.3 抽象工厂的依赖注入
通过Dagger实现工厂实例的依赖注入:
java复制@Module
public class UIModule {
@Provides
@Singleton
UIComponentFactory provideComponentFactory(DeviceProfile profile) {
switch (profile.getDeviceType()) {
case PHONE: return new PhoneComponentFactory();
case TABLET: return new TabletComponentFactory();
case TV: return new TVComponentFactory();
default: throw new IllegalArgumentException("Unsupported device");
}
}
}
3.4 动态切换的实现
处理配置变更时的策略更新:
java复制public class MainActivity extends AppCompatActivity {
private LayoutContext layoutContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initLayoutStrategy();
// ...
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
initLayoutStrategy(); // 重新初始化策略
recreate(); // 重建Activity
}
private void initLayoutStrategy() {
DeviceType type = DeviceProfile.detectDeviceType();
LayoutStrategy strategy = StrategyFactory.create(type);
layoutContext.setStrategy(strategy);
}
}
4. 性能优化与特殊处理
4.1 策略缓存机制
频繁创建策略实例会影响性能,我们引入策略缓存:
java复制public class StrategyCache {
private static final Map<DeviceType, LayoutStrategy> cache = new ConcurrentHashMap<>();
public static LayoutStrategy getStrategy(DeviceType type) {
return cache.computeIfAbsent(type, key -> {
switch (key) {
case PHONE: return new PhonePortraitStrategy();
case TABLET:
return isPortrait() ? new TabletPortraitStrategy()
: new TabletLandscapeStrategy();
default: throw new IllegalArgumentException();
}
});
}
}
4.2 内存泄漏防护
策略对象可能持有Activity引用,需要特殊处理:
java复制public abstract class WeakRefStrategy implements LayoutStrategy {
private WeakReference<Context> contextRef;
public WeakRefStrategy(Context context) {
this.contextRef = new WeakReference<>(context);
}
protected Context getContext() {
Context ctx = contextRef.get();
if (ctx == null) {
throw new IllegalStateException("Context reclaimed");
}
return ctx;
}
}
4.3 主题切换的扩展实现
支持动态主题只需扩展策略接口:
java复制public interface ThemedStrategy extends LayoutStrategy {
void applyTheme(ColorScheme scheme);
}
public class DarkModeStrategy implements ThemedStrategy {
@Override
public void applyTheme(ColorScheme scheme) {
// 应用深色主题配置
}
}
5. 实测效果与对比数据
我们在电商App中进行了AB测试:
| 指标 | 旧方案 | 新方案 | 提升 |
|---|---|---|---|
| 代码行数 | 4200 | 1800 | 57%↓ |
| 新增设备适配耗时 | 8h | 1.5h | 81%↓ |
| 主题切换响应时间 | 320ms | 80ms | 75%↓ |
| 内存占用 | 45MB | 38MB | 15%↓ |
关键提升点:
- 代码复用率从35%提升到72%
- 新设备适配只需新增策略类,无需修改现有逻辑
- 主题切换变为热更新,无需重启Activity
6. 常见问题与解决方案
6.1 策略爆炸问题
当设备类型过多时,可能出现类膨胀。我们的解决方案:
- 使用组合代替继承:将公共逻辑提取到基类
- 采用策略模板方法:
java复制public abstract class BaseLayoutStrategy implements LayoutStrategy {
// 公共算法步骤
public final ViewGroup applyLayout(ViewGroup root) {
preLayout(root);
doLayout(root);
return postLayout(root);
}
protected abstract void doLayout(ViewGroup root);
}
6.2 多策略协调问题
复杂界面可能需要多个策略协同工作。我们引入策略链:
java复制public class ChainedStrategy implements LayoutStrategy {
private List<LayoutStrategy> strategies;
@Override
public ViewGroup applyLayout(ViewGroup root) {
ViewGroup result = root;
for (LayoutStrategy strategy : strategies) {
result = strategy.applyLayout(result);
}
return result;
}
}
6.3 安卓版本兼容处理
不同安卓版本的UI行为差异需要特殊处理:
java复制public class VersionAwareStrategy extends DecoratorStrategy {
@Override
public ViewGroup applyLayout(ViewGroup root) {
ViewGroup result = super.applyLayout(root);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// 特殊处理
}
return result;
}
}
7. 扩展应用场景
7.1 跨平台方案适配
这套架构可以扩展到Flutter等跨平台框架:
dart复制abstract class LayoutStrategy {
Widget buildLayout(List<Widget> children);
}
class CupertinoStrategy implements LayoutStrategy {
@override
Widget buildLayout(List<Widget> children) {
return CupertinoPageScaffold(
child: ListView(children: children),
);
}
}
7.2 服务端UI渲染
服务端渲染时根据UserAgent选择策略:
java复制@GetMapping("/page")
public String renderPage(@RequestHeader("User-Agent") String ua) {
DeviceType type = DeviceDetector.detect(ua);
LayoutStrategy strategy = StrategyFactory.create(type);
return strategy.render(model);
}
7.3 游戏UI系统
在Unity中实现类似的架构:
csharp复制public interface IUILayoutStrategy {
GameObject Arrange(GameObject root);
}
public class MobileLayout : IUILayoutStrategy {
public GameObject Arrange(GameObject root) {
// 移动端布局逻辑
}
}
8. 架构演进建议
对于更复杂的场景,可以考虑以下优化方向:
- 策略自动发现机制:
java复制@Service
public class StrategyRegistry {
@Autowired
private Map<String, LayoutStrategy> strategies;
public LayoutStrategy getStrategy(String type) {
return strategies.get(type + "Strategy");
}
}
- 策略的热更新:
java复制public class HotSwapStrategy implements LayoutStrategy {
private volatile LayoutStrategy delegate;
public void updateDelegate(LayoutStrategy newStrategy) {
this.delegate = newStrategy;
}
}
- 策略的机器学习优化:
python复制class MLStrategy:
def predict_layout(self, user_behavior):
model = load_model('layout_model.h5')
return model.predict(user_behavior)
