平时做技术实践时,很多问题不是概念不会,而是细节没串起来。拿“Android手游SDK组件化开发详细实践指南”来说,它看着像小点,放到项目里常会牵出环境、配置、兼容性和维护成本。下面按实际采用顺序,把思路、关键写法和容易踩坑的地方讲清楚,便于大家直接对照操作。
落到代码里,本文档详细说明如何从零开始构建一个组件化的Android手游SDK。整个项目采用组件化架构,将SDK的各个功能模块拆分独立开发,最后组合成一个完整的手游SDK。



先来看一下我们项目的整体结构:
zujianmodule (根项目)
├── app # 游戏主入口模块
├── app-base # SDK核心基础模块
├── app-login # 登录模块
├── app-register # 注册模块
├── app-center # 个人中心模块
├── app-changepassword # 修改密码模块
├── app-update # 强制更新模块
├── lib-util # 工具类库
├── lib-data # 数据层库
├── lib-logging # 日志库
├── lib-permissions # 权限库
└── lib-imgloader # 图片加载库
模块分类:
app (主模块)
└── 依赖所有 app-* 模块
app-base (核心基础)
├── 依赖 lib-util
└── 提供:SdkCore、SdkCoreImpl、ModuleMediator、BaseActivity、回调接口
app-login (登录)
├── 依赖 app-base
└── 提供:LoginActivity
app-register (注册)
├── 依赖 app-base
└── 提供:RegisterActivity
app-center (个人中心)
├── 依赖 app-base
└── 提供:CenterActivity
app-changepassword (修改密码)
├── 依赖 app-base
└── 提供:ChangePasswordActivity
app-update (强制更新)
├── 依赖 app-base
└── 提供:UpdateActivity
传统SDK开发方式:
组件化优势:
这是本项目的核心创新点从实现思路看,!传统方式下,每个模块的Activity都需在主项目的AndroidManifest中注册,否则找不到。但我们采用反射技术实现了自动发现!
TestResourceUtil 工具类:
// 反射获取布局资源
public static int getLayoutId(Context context, String resName) {
return context.getResources().getIdentifier(resName, "layout", context.getPackageName());
}
// 反射获取字符串资源
public static int getStringId(Context context, String resName) {
return context.getResources().getIdentifier(resName, "string", context.getPackageName());
}
// 反射获取Drawable资源
public static int getDrawableId(Context context, String resName) {
return context.getResources().getIdentifier(resName, "drawable", context.getPackageName());
}
// 反射获取ID
public static int getId(Context context, String resName) {
return context.getResources().getIdentifier(resName, "id", context.getPackageName());
}
在BaseActivity中采用:
public abstract class BaseActivity extends Activity {
// 通过布局名称动态加载布局
public void setContentView(String layoutId) {
// 传入 "activity_login" 而不是 R.layout.activity_login
super.setContentView(TestResourceUtil.getLayoutId(mContext, layoutId));
}
// 通过控件ID名称查找控件
public View findViewById(String viewId) {
// 传入 "btn_login" 而不是 R.id.btn_login
return super.findViewById(TestResourceUtil.getId(mContext, viewId));
}
}
Activity中采用:
public class LoginActivity extends BaseActivity {
@Override
protected void loadViewLayout() {
// 直接写字符串,框架自动找资源!
setContentView("activity_login");
}
@Override
protected void findViewById() {
// 控件也是字符串形式
Button btnLogin = findViewById("btn_login");
}
}
好处:再也不用担心模块间资源ID冲突问题了!每个模块能够有自己的
activity_login.xml!
每个功能模块都需一个App类实现AppInitial接口,这样主项目就能在运行时自动初始化所有模块:
AppInitial接口:
public interface AppInitial {
void init(Application app);
}
登录模块的App类:
public class App implements ModuleMediator.AppInitial {
@Override
public void init(Application app) {
// 这里可以初始化登录模块特有的东西
// 比如配置第三方登录SDK
}
}
ModuleMediator统一初始化:
public class ModuleMediator {
// 每个模块的App类全限定名
private static final String APP_LOGIN_CLASS = "com.github.jeffery.login.App";
private static final String APP_CENTER_CLASS = "com.github.jeffery.center.App";
private static final String APP_REGISTER_CLASS = "com.github.jeffery.register.App";
private static final String APP_CHANGEPASSWORD_CLASS = "com.github.jeffery.changepassword.App";
private static final String APP_UPDATE_CLASS = "com.github.jeffery.update.App";
// 一次性初始化所有模块
public static void init(Application app) {
String[] appClasses = {
APP_LOGIN_CLASS,
APP_CENTER_CLASS,
APP_REGISTER_CLASS,
APP_CHANGEPASSWORD_CLASS,
APP_UPDATE_CLASS
};
for (String claName : appClasses) {
try {
Class<?> clz = Class.forName(claName);
AppInitial obj = (AppInitial) clz.newInstance();
obj.init(app);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
类似于ARouter,但更轻量级!采用字符串常量来标识目标页面:
public class ModuleMediator {
// 页面路由常量
public static final String ACTIVITY_LOGIN_CLASS = "com.github.jeffery.login.LoginActivity";
public static final String ACTIVITY_CENTER_CLASS = "com.github.jeffery.center.CenterActivity";
public static final String ACTIVITY_REGISTER_CLASS = "com.github.jeffery.register.RegisterActivity";
public static final String ACTIVITY_CHANGEPASSWORD_CLASS = "com.github.jeffery.changepassword.ChangePasswordActivity";
public static final String ACTIVITY_UPDATE_CLASS = "com.github.jeffery.update.UpdateActivity";
// 单例模式
private static volatile ModuleMediator instance;
public static ModuleMediator getInstance() {
if (instance == null) {
synchronized (ModuleMediator.class) {
if (instance == null) {
instance = new ModuleMediator();
}
}
}
return instance;
}
// 跳转Activity
public void startActivity(Activity activity, String className) {
startActivity(activity, className, null);
}
public void startActivity(Activity activity, String className, Bundle bundle) {
try {
Class<?> clazz = Class.forName(className);
Intent intent = new Intent(activity, clazz);
if (bundle != null) {
intent.putExtras(bundle);
}
activity.startActivity(intent);
} catch (ClassNotFoundException e) {
e.printStackTrace();
Toast.makeText(activity, "找不到" + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
}
采用示例:
// 跳转到登录页
ModuleMediator.getInstance().startActivity(this,
ModuleMediator.ACTIVITY_LOGIN_CLASS);
// 带参数跳转到注册页
Bundle params = new Bundle();
params.putString("from", "login");
ModuleMediator.getInstance().startActivity(this,
ModuleMediator.ACTIVITY_REGISTER_CLASS, params);
SdkCore抽象类(定义规范):
public abstract class SdkCore {
// 初始化
public abstract void init(Context context);
// 登录
public abstract void login(LoginCallback callback);
// 支付
public abstract void pay(PayParams params, PayCallback callback);
// 登出
public abstract void logout(LogoutCallback callback);
// 退出游戏
public abstract void exitGame(ExitCallback callback);
// 数据上报
public abstract void reportData(String eventId, Object params);
// 生命周期
public abstract void onCreate();
public abstract void onStart();
public abstract void onResume();
public abstract void onPause();
public abstract void onStop();
public abstract void onDestroy();
}
SdkCoreImpl实现类(单例模式):
public class SdkCoreImpl extends SdkCore {
private static volatile SdkCoreImpl instance;
private Context mContext;
private boolean isInitialized = false;
private String currentUserId;
private String currentUserName;
private String currentToken;
private SdkCoreImpl() {
// 私有构造函数
}
public static SdkCoreImpl getInstance() {
if (instance == null) {
synchronized (SdkCoreImpl.class) {
if (instance == null) {
instance = new SdkCoreImpl();
}
}
}
return instance;
}
@Override
public void init(Context context) {
this.mContext = context.getApplicationContext();
this.isInitialized = true;
}
// ... 其他方法实现
}
所有回调继承自基础接口:
// 基础回调
public interface SdkCallback {
void onSuccess(Object data);
void onFailure(int code, String msg);
}
// 登录回调
public interface LoginCallback extends SdkCallback {
void onLoginSuccess(String userId, String userName, String token);
void onLoginCancel();
}
// 支付回调
public interface PayCallback extends SdkCallback {
void onPaySuccess(String orderId, String productId);
void onPayCancel();
void onPayPending(String msg);
}
// 登出回调
public interface LogoutCallback extends SdkCallback {
void onLogoutSuccess();
}
// 退出游戏回调
public interface ExitCallback extends SdkCallback {
void onConfirmExit();
void onCancelExit();
}
public class PayParams {
private String productId; // 商品ID
private String productName; // 商品名称
private int price; // 价格(分)
private String currency; // 货币类型
private String orderId; // 订单号
private String roleId; // 角色ID
private String roleName; // 角色名称
private String serverId; // 服务器ID
private String serverName; // 服务器名称
private String extData; // 扩展数据
// Getter and Setter...
}
假设我们要新建一个"礼包"模块 app-gift:
1. 在settings.gradle中添加:
include ':app-gift'
2. 新建build.gradle:
plugins {
id 'com.android.library'
}
android {
compileSdk buildVersion.compileSdk
defaultConfig {
minSdk buildVersion.minSdk
targetSdk buildVersion.targetSdk
// 开发阶段可以独立运行
if (!isRelease) {
applicationId 'com.github.jeffery.gift'
versionCode 1
versionName '1.0'
}
}
}
// 资源前缀,避免资源名冲突
android.resourcePrefix 'gift_'
dependencies {
// 依赖基础模块
api project(':app-base')
}
3. 新建包结构和类:
app-gift/src/main/java/com/github/jeffery/gift/
├── App.java # 模块初始化类
├── GiftActivity.java # 礼包Activity
└── ...
4. 新建App类:
package com.github.jeffery.gift;
import android.app.Application;
import com.github.jeffery.utils.ModuleMediator;
public class App implements ModuleMediator.AppInitial {
@Override
public void init(Application app) {
// 初始化礼包模块
}
}
5. 在ModuleMediator中添加路由:
public static final String ACTIVITY_GIFT_CLASS = "com.github.jeffery.gift.GiftActivity";
为避免资源冲突,每个模块的资源都要有模块前缀:
| 模块 | 布局前缀 | 字符串前缀 | 颜色前缀 | 尺寸前缀 |
|---|---|---|---|---|
| app-login | login_ | login_ | login_ | login_ |
| app-register | register_ | register_ | register_ | register_ |
| app-center | center_ | center_ | center_ | center_ |
| app-gift | gift_ | gift_ | gift_ | gift_ |
示例:
activity_login.xml → login_activity.xmlbtn_login → login_btn_submit@string/login_hint → @string/login_hintdependencies {
implementation project(':app-base')
implementation project(':app-login')
implementation project(':app-register')
implementation project(':app-center')
implementation project(':app-changepassword')
implementation project(':app-update')
}
public class GameApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 1. 初始化所有SDK模块
ModuleMediator.init(this);
// 2. 初始化SDK核心
SdkCoreImpl.getInstance().init(this);
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SdkCoreImpl.getInstance().onCreate();
}
@Override
protected void onStart() {
super.onStart();
SdkCoreImpl.getInstance().onStart();
}
@Override
protected void onResume() {
super.onResume();
SdkCoreImpl.getInstance().onResume();
}
@Override
protected void onPause() {
super.onPause();
SdkCoreImpl.getInstance().onPause();
}
@Override
protected void onStop() {
super.onStop();
SdkCoreImpl.getInstance().onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
SdkCoreImpl.getInstance().onDestroy();
}
}
登录:
SdkCoreImpl.getInstance().login(new LoginCallback() {
@Override
public void onLoginSuccess(String userId, String userName, String token) {
Log.d("Login", "登录成功!userId=" + userId);
}
@Override
public void onLoginCancel() {
Log.d("Login", "用户取消登录");
}
@Override
public void onSuccess(Object data) {}
@Override
public void onFailure(int code, String msg) {
Log.e("Login", "登录失败:" + msg);
}
});
支付:
PayParams params = new PayParams();
params.setProductId("com.game.gems_100");
params.setProductName("100钻石");
params.setPrice(100);
SdkCoreImpl.getInstance().pay(params, new PayCallback() {
@Override
public void onPaySuccess(String orderId, String productId) {
Log.d("Pay", "支付成功!");
}
// ...
});
数据上报:
// 简单事件
SdkCoreImpl.getInstance().reportData("level_start", null);
// 带参数事件
Map<String, Object> params = new HashMap<>();
params.put("level_id", 1);
SdkCoreImpl.getInstance().reportData("level_complete", params);
// env.gradle
isRelease = false
特点:
// env.gradle
isRelease = true
特点:
落到代码里,借助ModuleMediator单例进行通信,也能够直接采用SdkCoreImpl的实例方法。
实际处理时,每个模块采用独立的资源前缀(借助android.resourcePrefix设置),加上反射式加载,不会有ID冲突。
在env.gradle中设置isRelease = false,该模块就能够独立安装运行。
项目根目录/
├── app.gradle # app模块配置
├── build.gradle # 根项目配置
├── env.gradle # 环境变量配置
├── settings.gradle # 模块列表
├── app/ # 游戏主模块
├── app-base/ # SDK基础模块
│ └── src/main/java/com/github/jeffery/utils/
│ ├── SdkCore.java
│ ├── SdkCoreImpl.java
│ ├── ModuleMediator.java
│ ├── BaseActivity.java
│ ├── LoginCallback.java
│ ├── PayCallback.java
│ └── ...
├── app-login/ # 登录模块
├── app-register/ # 注册模块
├── app-center/ # 个人中心模块
├── app-changepassword/ # 修改密码模块
├── app-update/ # 强制更新模块
├── lib-util/ # 工具库
├── lib-data/ # 数据库
├── lib-logging/ # 日志库
├── lib-permissions/ # 权限库
└── lib-imgloader/ # 图片加载库
本项目借助以下技术实现了一个轻量级的Android手游SDK组件化方案:
理解这一步时,整个方案不依赖任何第三方路由框架,代少,易于理解和维护,很适合中小型手游SDK开发。
到此这篇关于Android手游SDK组件化开发南的文章就介绍到这了,更多相关Android手游SDK组件化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多兼容脚本之家!
.NET i18n 原理、实现方式一个 i18n 框架的过程完整指南
小米路由器4红灯闪烁修复方法(小米路由器4红灯闪烁如何修复)
用AI做供应链优化:从需求预测到库存分配的端到端工程方案实战
从 PHP 到 AI + Golang,程序员自救转型手记(五十九):前后端自定义后台入口实现
为什么 ACC Core 不能和 OpenAPI、MCP 或 gRPC 绑死?
小米cr6606刷机教程(小米cr6606如何刷机)