Android手游SDK組件化開發(fā)詳細(xì)實戰(zhàn)指南
前言
本文檔詳細(xì)介紹如何從零開始構(gòu)建一個組件化的Android手游SDK。整個項目采用組件化架構(gòu),將SDK的各個功能模塊拆分獨立開發(fā),最終組合成一個完整的手游SDK。
效果圖
單獨模塊運行

正式包環(huán)境運行

第一章 項目整體架構(gòu)
1.1 架構(gòu)一覽

先來看一下我們項目的整體結(jié)構(gòu):
zujianmodule (根項目) ├── app # 游戲主入口模塊 ├── app-base # SDK核心基礎(chǔ)模塊 ├── app-login # 登錄模塊 ├── app-register # 注冊模塊 ├── app-center # 個人中心模塊 ├── app-changepassword # 修改密碼模塊 ├── app-update # 強(qiáng)制更新模塊 ├── lib-util # 工具類庫 ├── lib-data # 數(shù)據(jù)層庫 ├── lib-logging # 日志庫 ├── lib-permissions # 權(quán)限庫 └── lib-imgloader # 圖片加載庫
模塊分類:
- app模塊:業(yè)務(wù)功能模塊(登錄、注冊、個人中心等)
- lib模塊:公共基礎(chǔ)庫(供所有模塊復(fù)用)
1.2 模塊依賴關(guān)系
app (主模塊) └── 依賴所有 app-* 模塊 ? app-base (核心基礎(chǔ)) ├── 依賴 lib-util └── 提供:SdkCore、SdkCoreImpl、ModuleMediator、BaseActivity、回調(diào)接口 ? app-login (登錄) ├── 依賴 app-base └── 提供:LoginActivity ? app-register (注冊) ├── 依賴 app-base └── 提供:RegisterActivity ? app-center (個人中心) ├── 依賴 app-base └── 提供:CenterActivity ? app-changepassword (修改密碼) ├── 依賴 app-base └── 提供:ChangePasswordActivity ? app-update (強(qiáng)制更新) ├── 依賴 app-base └── 提供:UpdateActivity
第二章 核心技術(shù)點詳解
2.1 為什么要組件化?
傳統(tǒng)SDK開發(fā)方式:
- 所有代碼寫在一個模塊里
- 代碼耦合嚴(yán)重,難以維護(hù)
- 功能擴(kuò)展困難
- 多人協(xié)作困難
組件化優(yōu)勢:
- 模塊解耦:每個功能獨立模塊,職責(zé)單一
- 獨立運行:開發(fā)階段每個模塊可獨立運行測試
- 按需加載:發(fā)布時可按需求組合
- 團(tuán)隊協(xié)作:不同模塊可分配給不同人開發(fā)
2.2 反射式資源加載(核心黑科技)
這是本項目的核心創(chuàng)新點!傳統(tǒng)方式下,每個模塊的Activity都需要在主項目的AndroidManifest中注冊,否則找不到。但我們使用反射技術(shù)實現(xiàn)了自動發(fā)現(xiàn)!
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 {
// 通過布局名稱動態(tài)加載布局
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");
}
}
?? 好處:再也不用擔(dān)心模塊間資源ID沖突問題了!每個模塊可以有自己的
activity_login.xml!
2.3 模塊初始化機(jī)制
每個功能模塊都需要一個App類實現(xiàn)AppInitial接口,這樣主項目就能在運行時自動初始化所有模塊:
AppInitial接口:
public interface AppInitial {
void init(Application app);
}
登錄模塊的App類:
public class App implements ModuleMediator.AppInitial {
@Override
public void init(Application app) {
// 這里可以初始化登錄模塊特有的東西
// 比如配置第三方登錄SDK
}
}
ModuleMediator統(tǒng)一初始化:
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();
}
}
}
}
2.4 路由跳轉(zhuǎn)機(jī)制
類似于ARouter,但更輕量級!使用字符串常量來標(biāo)識目標(biāo)頁面:
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;
}
// 跳轉(zhuǎn)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();
}
}
}
使用示例:
// 跳轉(zhuǎn)到登錄頁
ModuleMediator.getInstance().startActivity(this,
ModuleMediator.ACTIVITY_LOGIN_CLASS);
?
// 帶參數(shù)跳轉(zhuǎn)到注冊頁
Bundle params = new Bundle();
params.putString("from", "login");
ModuleMediator.getInstance().startActivity(this,
ModuleMediator.ACTIVITY_REGISTER_CLASS, params);
第三章 核心功能實現(xiàn)
3.1 SDK核心類設(shè)計
SdkCore抽象類(定義規(guī)范):
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);
// 數(shù)據(jù)上報
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實現(xiàn)類(單例模式):
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() {
// 私有構(gòu)造函數(shù)
}
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;
}
// ... 其他方法實現(xiàn)
}
3.2 回調(diào)接口設(shè)計
所有回調(diào)繼承自基礎(chǔ)接口:
// 基礎(chǔ)回調(diào)
public interface SdkCallback {
void onSuccess(Object data);
void onFailure(int code, String msg);
}
?
// 登錄回調(diào)
public interface LoginCallback extends SdkCallback {
void onLoginSuccess(String userId, String userName, String token);
void onLoginCancel();
}
?
// 支付回調(diào)
public interface PayCallback extends SdkCallback {
void onPaySuccess(String orderId, String productId);
void onPayCancel();
void onPayPending(String msg);
}
?
// 登出回調(diào)
public interface LogoutCallback extends SdkCallback {
void onLogoutSuccess();
}
?
// 退出游戲回調(diào)
public interface ExitCallback extends SdkCallback {
void onConfirmExit();
void onCancelExit();
}
3.3 支付參數(shù)類
public class PayParams {
private String productId; // 商品ID
private String productName; // 商品名稱
private int price; // 價格(分)
private String currency; // 貨幣類型
private String orderId; // 訂單號
private String roleId; // 角色I(xiàn)D
private String roleName; // 角色名稱
private String serverId; // 服務(wù)器ID
private String serverName; // 服務(wù)器名稱
private String extData; // 擴(kuò)展數(shù)據(jù)
// Getter and Setter...
}
第四章 模塊創(chuàng)建步驟
4.1 創(chuàng)建新模塊
假設(shè)我們要創(chuàng)建一個"禮包"模塊 app-gift:
1. 在settings.gradle中添加:
include ':app-gift'
2. 創(chuàng)建build.gradle:
plugins {
id 'com.android.library'
}
?
android {
compileSdk buildVersion.compileSdk
defaultConfig {
minSdk buildVersion.minSdk
targetSdk buildVersion.targetSdk
// 開發(fā)階段可以獨立運行
if (!isRelease) {
applicationId 'com.github.jeffery.gift'
versionCode 1
versionName '1.0'
}
}
}
?
// 資源前綴,避免資源名沖突
android.resourcePrefix 'gift_'
?
dependencies {
// 依賴基礎(chǔ)模塊
api project(':app-base')
}
3. 創(chuàng)建包結(jié)構(gòu)和類:
app-gift/src/main/java/com/github/jeffery/gift/ ├── App.java # 模塊初始化類 ├── GiftActivity.java # 禮包Activity └── ...
4. 創(chuàng)建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";
4.2 資源文件規(guī)范
為避免資源沖突,每個模塊的資源都要有模塊前綴:
| 模塊 | 布局前綴 | 字符串前綴 | 顏色前綴 | 尺寸前綴 |
|---|---|---|---|---|
| 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_hint
第五章 集成到游戲項目
5.1 主項目build.gradle配置
dependencies {
implementation project(':app-base')
implementation project(':app-login')
implementation project(':app-register')
implementation project(':app-center')
implementation project(':app-changepassword')
implementation project(':app-update')
}
5.2 Application初始化
public class GameApplication extends Application {
?
@Override
public void onCreate() {
super.onCreate();
// 1. 初始化所有SDK模塊
ModuleMediator.init(this);
// 2. 初始化SDK核心
SdkCoreImpl.getInstance().init(this);
}
}
5.3 生命周期集成
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();
}
}
5.4 常用API調(diào)用
登錄:
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", "支付成功!");
}
// ...
});
數(shù)據(jù)上報:
// 簡單事件
SdkCoreImpl.getInstance().reportData("level_start", null);
?
// 帶參數(shù)事件
Map<String, Object> params = new HashMap<>();
params.put("level_id", 1);
SdkCoreImpl.getInstance().reportData("level_complete", params);
第六章 開發(fā)與發(fā)布模式
6.1 開發(fā)模式(isRelease = false)
// env.gradle isRelease = false
特點:
- 每個模塊都有獨立的applicationId
- 可以獨立安裝運行調(diào)試
- 便于團(tuán)隊成員獨立開發(fā)
6.2 發(fā)布模式(isRelease = true)
// env.gradle isRelease = true
特點:
- 所有模塊合并為一個SDK
- 只生成一個applicationId
- 減少包體積
第七章 常見問題解答
Q1: 如何添加新功能模塊?
- 創(chuàng)建新模塊(參考第四章)
- 在ModuleMediator中添加路由常量
- 在settings.gradle中include
- 在主模塊build.gradle中添加依賴
Q2: 模塊間如何通信?
通過ModuleMediator單例進(jìn)行通信,也可以直接使用SdkCoreImpl的實例方法。
Q3: 資源沖突怎么辦?
每個模塊使用獨立的資源前綴(通過android.resourcePrefix配置),加上反射式加載,不會有ID沖突。
Q4: 如何調(diào)試單個模塊?
在env.gradle中設(shè)置isRelease = false,該模塊就可以獨立安裝運行。
附錄:目錄結(jié)構(gòu)參考
項目根目錄/ ├── app.gradle # app模塊配置 ├── build.gradle # 根項目配置 ├── env.gradle # 環(huán)境變量配置 ├── settings.gradle # 模塊列表 ├── app/ # 游戲主模塊 ├── app-base/ # SDK基礎(chǔ)模塊 │ └── 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/ # 強(qiáng)制更新模塊 ├── lib-util/ # 工具庫 ├── lib-data/ # 數(shù)據(jù)庫 ├── lib-logging/ # 日志庫 ├── lib-permissions/ # 權(quán)限庫 └── lib-imgloader/ # 圖片加載庫
總結(jié)
本項目通過以下技術(shù)實現(xiàn)了一個輕量級的Android手游SDK組件化方案:
- 反射式資源加載 - 解決資源ID沖突問題
- ModuleMediator - 統(tǒng)一模塊初始化和頁面路由
- AppInitial接口 - 模塊熱插拔
- 單例模式 - 核心類全局唯一實例
- 回調(diào)接口 - 異步通信標(biāo)準(zhǔn)化
整個方案不依賴任何第三方路由框架,代碼量少,易于理解和維護(hù),非常適合中小型手游SDK開發(fā)。
到此這篇關(guān)于Android手游SDK組件化開發(fā)南的文章就介紹到這了,更多相關(guān)Android手游SDK組件化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
通過OpenGL ES混合模式縮放視頻緩沖區(qū)來適應(yīng)顯示尺寸
當(dāng)開發(fā)基于軟件模式的游戲時,通過縮放視頻緩沖區(qū)來適應(yīng)顯示尺寸是最棘手的問題之一;作為開發(fā)人員,我們必須嘗試在性能與顯示質(zhì)量之間找到最佳平衡點2012-12-12
Android實現(xiàn)手電筒電源鍵關(guān)閉功能
這篇文章主要介紹了Android實現(xiàn)手電筒電源鍵關(guān)閉功能,在打開手電筒之后,機(jī)器休眠,客戶要求點擊電源鍵,手電筒需要關(guān)閉,下面小編給大家分享實現(xiàn)代碼,需要的朋友可以參考下2017-11-11
Android開發(fā)使用Databinding實現(xiàn)關(guān)注功能mvvp
這篇文章主要為大家介紹了Android開發(fā)使用Databinding實現(xiàn)關(guān)注功能mvvp示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
解析Android中string-array數(shù)據(jù)源的簡單使用
本篇文章是對Android中string-array數(shù)據(jù)源的使用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06

