99国产精品欲av蜜臀,可以直接免费观看的AV网站,gogogo高清免费完整版,啊灬啊灬啊灬免费毛片

網(wǎng)易首頁 > 網(wǎng)易號 > 正文 申請入駐

Unity Connect 小游戲宿主接入記

0
分享至

"Hello, world!" "Hello, 小游戲宿主!" 本文簡要記錄了 Unity Connect 作為小游戲宿主的第一塊試驗田,從 SDK 接入、體驗優(yōu)化,到平臺能力接入的全過程。如果文中有任何錯誤,歡迎指出與交流,期待與你一起共建更加繁榮的小游戲宿主生態(tài)!

一、小游戲宿主的第一塊試驗田

有沒有小伙伴已經(jīng)發(fā)現(xiàn)了?最近 Unity Connect 首頁底部的導航欄悄悄多了一個小手柄按鈕,點進去就能進入全新的「Unity 小游戲中心」啦 !

在這里,你可以用十來分鐘來一局緊張刺激的卡牌肉鴿,也可以當個島主悠哉地建設自己的專屬島嶼,甚至還能施展魔法,培養(yǎng)一位未來的龍騎法師。不管是午休時間,還是 上班摸魚 ,都多了一個放松的好去處!

當然,我們在 Connect 上加入小游戲中心,不只是為了讓大家在忙碌工作之余多一個“快樂角落”。更重要的是——Unity Connect 是我們接入「小游戲宿主」的第一塊試驗田。

通過這次接入,我們希望向開發(fā)者們展示:小游戲宿主不僅兼容多種游戲引擎,還能為移動 App 提供流暢、輕量的游戲運行環(huán)境。未來,我們也期待更多移動應用和小游戲開發(fā)者關注并使用這一能力,一起共建 Unity 小游戲生態(tài)!

這篇文章就簡單介紹和分享小游戲宿主的前世今生以及 Connect 接入小游戲宿主的過程吧,如果有謬誤請幫忙指出!

二、走進小游戲與宿主的世界

在小游戲的世界里,「宿主」其實就是小游戲的“家”——它是一個平臺或容器,負責加載、運行和管理小游戲,還會提供各種運行時能力和平臺服務,確保小游戲能順利啟動、順暢運行。而運行在這個“家”里的小游戲,我們通常叫它「宿主小游戲」,也常被稱為「Instant Game」。

小游戲因為“輕量化、即點即玩”的特性,自 2017 年問世以來就一路高歌猛進:2019 年初,開發(fā)者數(shù)量突破 10 萬;同年 5 月,用戶規(guī)模破億;到了 2021 年,年流水破千萬的小游戲產(chǎn)品已經(jīng)超過 50 款!游戲類型也從最初的超休閑,逐漸拓展到了中重度玩法,比如 MMO、策略類等,越來越豐富。

各大平臺自然也不甘落后,紛紛打造自己的小游戲生態(tài),推出小程序/小游戲運行環(huán)境,力求在自家 App 里集成功能、留住用戶、提升體驗。

不過,并不是所有平臺型公司都已經(jīng)有了現(xiàn)成的宿主解決方案。有些是還沒來得及做,有些是想更高效地把其他平臺的小游戲“搬”到自己的 App 上。這時,一個靈活、易用的宿主方案就顯得尤為重要——于是,Unity 小游戲宿主應運而生!

事實上,Unity 小游戲宿主是融合了客戶端 SDK、服務端 API 以及管理后臺的一體化綜合性小游戲運行平臺,平臺開發(fā)者能夠在管理后臺創(chuàng)建宿主應用,上傳小游戲,建立關聯(lián)關系,通過服務端 API 獲取游戲列表,小游戲開發(fā)者則能通過集成了小游戲宿主的應用調(diào)試小游戲。

三、簡單但重要的從 0 到 1

Connect 接入小游戲宿主的流程可參考官方文檔:

https://minihost.tuanjie.cn/help/docs/welcome

小游戲運行環(huán)境可參考:

https://developers.weixin.qq.com/minigame/dev/guide/best-practice/game-engine.html

這里我們以 Android 平臺為例,和大家一起快速過一遍整個接入過程。整體可以分為兩個步驟:

  1. 在宿主管理平臺創(chuàng)建應用并上傳小游戲

  2. 客戶端集成宿主小游戲 SDK

感興趣的小伙伴也可以跟著我們一步步操作,創(chuàng)建一個屬于自己的宿主應用哦!

3.1 創(chuàng)建宿主應用 & 小游戲

參考文檔:https://minihost.tuanjie.cn/help/docs/quickstart/create-host-app

在正式接入客戶端 SDK 之前,我們需要先去宿主管理控制臺新建一個“宿主應用”。這一步主要是為了配置 Bundle ID,并獲取對應的 App Key 和 App Secret,這兩個參數(shù)后面在初始化 SDK 時會用到。

接下來,我們需要為這個宿主應用創(chuàng)建一些小游戲。在管理控制臺的「小游戲」頁面中,我們可以先創(chuàng)建一個最簡單的示例游戲,比如 Spinning Cube,并將它關聯(lián)到剛才創(chuàng)建的宿主應用上。

小游戲創(chuàng)建好之后,我們還需要上傳一個對應的游戲包。這個游戲包可以直接通過 Unity 提供的新增的 Build Target 導出。為了方便大家調(diào)試,我們也提供了一個現(xiàn)成的 Spinning Cube 游戲包 。

Spinning Cube 游戲包:

https://minihost.cdn.tuanjie.cn/release/game-pkg/spinning-cube-20250418.zip

上傳時,控制臺會自動檢查游戲包是否符合規(guī)范。上傳成功后,我們需要發(fā)布并上線這個游戲包作為其最新的線上版本,別忘了回到小游戲列表中確認這個小游戲已經(jīng)成功啟用,并且記住它的 Game ID (注意是 Game ID 不是游戲包 ID),在后續(xù)啟動游戲時會用到!

到這里,在控制臺上的準備工作就完成啦 !

3.2 客戶端集成宿主 SDK

參考文檔:https://minihost.tuanjie.cn/help/docs/sdk/android/quick-integrate

客戶端接入小游戲的步驟也非常簡單,首先,從小游戲宿主官方下載頁獲取最新版的 Android SDK 壓縮包。

小游戲宿主 SDK 下載:https://minihost.tuanjie.cn/download

里面包含兩個 AAR 文件:

  • host-runtime-dynamic.aar(動態(tài)加載包)

  • host-runtime-static.aar(靜態(tài)加載包)

這兩個包的主要區(qū)別在于:動態(tài)包支持按需下載小游戲運行環(huán)境,更加靈活;而靜態(tài)包則是將運行環(huán)境直接集成進 App,適合快速接入,所以我們這里先使用靜態(tài)包。

接下來,按照官方文檔的說明,把宿主 SDK 和它依賴的其他 SDK 一起添加到項目的 build.gradle 文件中的 dependencies 里。

dependencies{  
//Host SDK 
implementationfiles('host-runtime-static.aar')

//dependencies for host SDK
implementationlibs.okhttp
implementationlibs.mmkv
implementationlibs.gson
implementationlibs.java.websocket
implementationlibs.camera.core
implementationlibs.camera.camera2
implementationlibs.camera.lifecycle
implementationlibs.camera.video
implementationlibs.camera.view
implementationlibs.camera.extensions
implementationlibs.tbssdk
}

# libs.versions.toml 
[versions]
okhttp = "3.12.13"
mmkv = "1.3.2"
gson = "2.10.1"
javaWebsocket = "1.5.1"
cameraCamera2 = "1.2.3"
tbssdk = "44286"

[libraries]
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
mmkv = { module = "com.tencent:mmkv", version.ref = "mmkv" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
java-websocket = { module = "org.java-websocket:Java-WebSocket", version.ref = "javaWebsocket" }
camera-core = { module = "androidx.camera:camera-core", version.ref = "cameraCamera2" }
camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCamera2" }
camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraCamera2" }
camera-video = { module = "androidx.camera:camera-video", version.ref = "cameraCamera2" }
camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraCamera2" }
camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "cameraCamera2" }
tbssdk = { module = "com.tencent.tbs:tbssdk", version.ref = "tbssdk" }

配置好依賴之后,我們就可以開始寫代碼啦。首先,新建一個簡單的布局和對應的 Activity。在布局中,暫時只需要一個用于承載游戲的容器視圖就夠了。并且我們可以給 Connect 中的某個按鈕綁定點擊事件跳轉到這個 Activity 用于調(diào)試。

"1.0" encoding="utf-8"?>

    android:id="@+id/gameContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
FrameLayout>

在宿主 SDK 中,啟動一個小游戲大致包括以下五個步驟:

  1. initialize

  2. createGameHandle

  3. setGameStartOptions

  4. start

  5. play

每個步驟都有其特定的作用,我們可以在每個步驟的成功回調(diào)中觸發(fā)下一個步驟,這樣整個流程就能順利串起來。

第一步,在 Activity 的 onCreate 中調(diào)用 TJHostHandle.initialize 來初始化宿主 SDK。在這一步中,我們需要傳入控制臺上創(chuàng)建宿主應用時生成的 App Key 和 App Secret,用于驗證接入身份。

同時,我們還可以傳入一個游戲容器,這樣宿主 SDK 的加載頁、彈窗等 UI 元素就會被限制在這個容器內(nèi),避免覆蓋整個界面。

初始化成功后,我們可以通過 TJHostHandle.getInstance() 獲取 SDK 的單例,后續(xù)的所有操作都通過它來完成。

初始化成功之后,我們能夠獲取 TJHostHandle 單例,后續(xù)的控制 API 都通過這個單例調(diào)用。

FrameLayout gameContainer;
privatestatic final String _TAG _= "[GameActivity]";
private final Handler handler = new Handler(Looper._getMainLooper_());
private TJHostHandle hostHandle;

@Override
publicvoidonCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout._activity_game_);
    gameContainer = findViewById(R.id._gameContainer_);

    TJHostHandle._initialize_(this, "**APP Key**", "**APP Secret**", gameContainer, new OnTJHostHandleInitializeListener() {
        @Override
publicvoidonSuccess(TJHostHandle hostHandle) {
            GameActivity.this.hostHandle = hostHandle;
            handler.post(() -> createGameHandle());
        }

        @Override
publicvoidonFailure(Throwable throwable) {
            Log._e_(_TAG_, "TJHostHandle.initialize failed: " + throwable.getMessage());
        }
    });
}

?接著,調(diào)用 createGameHandle 創(chuàng)建游戲句柄。這個過程支持通過 Bundle 傳入一些初始化參數(shù),比如 HTTP 緩存目錄等,具體參數(shù)可以參考文檔。

宿主管理文檔:

https://minihost.tuanjie.cn/help/docs/api/android/management#%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E

private void createGameHandle(){  
    hostHandle.createGameHandle(this, new Bundle(), new OnTJHostCreateHandleListener() {
        @Override
        public void onSuccess(TJHostHandle result){
            setGameStartOptions();
        }

        @Override
        public void onFailure(Throwable throwable){
            Log._e_(_TAG_, "Host createGameHandle failed", throwable);
        }
    });
}

? 然后,我們需要設置游戲的啟動參數(shù)。啟動一個小游戲至少需要兩個關鍵參數(shù):

  • LAUNCH_KEY:小游戲的啟動參數(shù)(可以是小游戲的 Game ID,具體定義參考文檔)

  • USER_ID:用戶 ID,用于區(qū)分不同用戶的數(shù)據(jù),以及記錄用戶啟動游戲的歷史

我們把前面創(chuàng)建的 Spinning Cube 的 Game ID 填入 LAUNCH_KEY,宿主 SDK 就會根據(jù)它去獲取對應的游戲包下載地址及類型。USER_ID 可以先隨便填一個測試用的值,后續(xù)接入正式用戶體系時再替換。

除此之外,setGameStartOptions 還支持一些可選參數(shù),比如是否展示展示更多按鈕、是否開啟調(diào)試模式等,具體可參考 文檔 。

小游戲管理:

https://minihost.tuanjie.cn/help/docs/api/android/minigame-management#%E6%B8%B8%E6%88%8F%E5%90%AF%E5%8A%A8%E5%8F%82%E6%95%B0-launch_key-%E8%AF%B4%E6%98%8E

privatevoidsetGameStartOptions(){  
    Bundle bundle = new Bundle();
    bundle.putString(TJRuntimeConstants._LAUNCH_KEY_, "Game ID");
    bundle.putString(TJRuntimeConstants._USER_ID_, "User ID");
    hostHandle.setGameStartOptions(bundle, new OnTJHostHandleListener() {
        @Override
publicvoid onSuccess() {
            start();
        }

        @Override
publicvoid onFailure(Throwable throwable) {
            Log._e_(_TAG_, "Host set game start options failure", throwable);
        } 
    });
}

參數(shù)設置好之后,調(diào)用 start 開始加載游戲資源。加載成功后,我們就可以將游戲界面添加到之前準備好的容器視圖中,并請求焦點。

private void start(){ 
    hostHandle.start(_TAG_, new OnTJHostHandleListener() {
        @Override
        public void onSuccess(){
            View gameView = hostHandle.getGameView();
            if (gameContainer.indexOfChild(gameView) == -1) {
                gameContainer.addView(gameView);
            }

            gameView.requestFocus();
            play();
        }

        @Override
        public void onFailure(Throwable throwable){
            Log._e_(_TAG_, "Host start game failure", throwable);
        }
    });
}

最后,調(diào)用 play 啟動游戲,小游戲就正式跑起來啦 !

privatevoidplay(){ 
    // Resume game
    hostHandle.play(new OnTJHostHandleListener() {
        @Override
        public void onSuccess(){
            Log._i_(_TAG_, "Host play game success");
        }

        @Override
        public void onFailure(Throwable throwable){
            Log._e_(_TAG_, "Host play game failure", throwable);
        }
    });
}

整個流程雖然看起來有些步驟,但實際上操作起來還是比較順暢的,只要前期配置好,后續(xù)想接入更多小游戲也很簡單,只需要在控制臺創(chuàng)建新的小游戲并且上傳對應的游戲包,啟動游戲時設置不同的 LAUNCH_KEY 即可輕松地實現(xiàn)橫向拓展!

四、讓宿主更聰明的一點點魔法 ?

在小游戲成功跑起來之后,我們并沒有就此收工。畢竟,“能跑”只是第一步,“跑得好”才是我們真正的目標。于是,我們開始通過宿主 SDK 提供的能力給宿主應用施加一些“魔法加成”,讓它變得更聰明更貼心。

這一階段的優(yōu)化,主要圍繞小游戲宿主生態(tài)中的三類角色展開:

  • 宿主應用的使用者

  • 小游戲的開發(fā)者

  • 宿主應用的開發(fā)者

4.1宿主應用使用者

對于宿主應用使用者(最終用戶)來說,我們的目標是:讓感興趣的用戶可以方便地發(fā)現(xiàn)并啟動想玩的游戲,同時確保小游戲的引入不會影響到 APP 原有的穩(wěn)定性和功能;而對于不感興趣的用戶,我們希望盡可能降低宿主 SDK 的存在感,做到“無感接入”。

4.1.1 獲取游戲列表:讓用戶看到能玩什么

參考文檔:https://minihost.tuanjie.cn/help/docs/sdk/server-integrate

在接入小游戲宿主之后,我們要面對的第一個問題就是:怎么把可玩的小游戲清晰地展示給用戶?畢竟,游戲再好玩,用戶找不到也是白搭。

好消息是,宿主平臺已經(jīng)為我們準備好了相關的服務端 API,專門用來獲取當前宿主應用下已關聯(lián)的小游戲列表:

curl --location 'https://minihost.tuanjie.cn/api/game/list?appId=**AppID**' \
--header 'Authorization: Bearer **ServiceToken**'

這個接口需要兩個參數(shù):

  • AppID:宿主應用的 ID

  • ServiceToken:服務端訪問令牌

它們和前面提到的App Key/App Secret類似,都可以在宿主控制臺中獲取。

接口返回的內(nèi)容包含了所有已發(fā)布小游戲的元信息,其中的 launchKey 字段就是啟動游戲時要用到的關鍵參數(shù)LAUNCH_KEY

[
    { 
        "id": "67fe09f272ef630b77c67089",
        "appId": "67fe05f972ef630b77c663b3",
        "name": "SpinningCube",
        "gameType": "weixinminigame",
        "tags": [
            "Cube"
        ],
        "iconUrl": 
        "https://minihost.cdn.tuanjie.cn/game_app/67bab0495795e8aac375737e/app_icon/cube.9c353a166f724813b0b2f5742bc1a19f.png",
        "briefIntro": "A Spinning Cube",
        "launchKey": "67fe09f272ef630b77c67089"
    }
]

雖然我們可以直接在客戶端請求這個 API 并展示游戲列表,但這樣做靈活性有限,比如想要實現(xiàn):

  • 搜索 / 關鍵詞過濾

  • 自定義排序(按熱度、更新時間等)

  • 分頁加載

  • 添加自定義字段(如“推薦標簽”、“開發(fā)者信息”等)

這些功能在客戶端處理起來就比較麻煩了。

所以,我們更推薦的做法是:

  1. 由 Connect 的服務端定期或按需調(diào)用宿主平臺的 API,拉取并緩存游戲列表

  2. 然后由 Connect 自己的服務端封裝一個更適合客戶端使用的 API(結構更簡潔、字段更定制)

  3. 客戶端只需請求這個自定義 API,即可獲取游戲列表并展示在 UI 中

當用戶點擊“啟動”按鈕時,只需要將對應游戲的 launchKey 作為參數(shù)傳入宿主 SDK,即可啟動該小游戲。通過這一套流程,我們不僅讓用戶能快速看到可玩的游戲,也為后續(xù)的個性化推薦、運營配置等功能打下了基礎。畢竟,游戲列表是入口,入口體驗好,用戶才有可能留下來玩更多

4.1.2 游玩歷史:讓用戶不錯過每一場精彩

參考文檔:https://minihost.tuanjie.cn/help/docs/sdk/android/advanced-usage-%E6%9C%80%E8%BF%91%E6%B8%B8%E7%8E%A9%E5%88%97%E8%A1%A8

隨著宿主應用中接入的小游戲越來越多,我們也意識到:用戶可能需要快速找回之前玩過的游戲。于是,我們加入了「游玩歷史」功能。宿主平臺也提供了相關 API,支持通過 userId(與設置游戲啟動參數(shù)時的 USER_ID 一致)獲取用戶的歷史記錄。例如:

publicstatic List

  getUserHistory(Context context, String userId)  // e.g. List historyList = TJHostHandle.getUserHistory(context, "user_id"); 

返回的歷史記錄是一個 HistoryModel 列表,每個 HistoryModel 包含了啟動游戲的詳細信息,字段說明如下:

publicclass HistoryModel { 
String id;            // Game ID
String versionId;     // Game Version ID
String gameType;      // Game Type
String name;          // Game Name
    List

 tags;    // Game Tags String iconUrl;       // Game Icon Url String briefIntro;    // Game Brief Introduction String launchKey;     // Game Launch Key, **Primary Key** boolean dev;          // Is Development Version String userId;        // User ID     long lastLaunchTime;  // Last Launched Time } 

需要注意的是,同一個小游戲可能會出現(xiàn)多個版本(比如用戶通過掃碼體驗了某個測試包),這時返回的 launchKey 會包含具體的版本信息。通過該 launchKey 啟動游戲時,宿主 SDK 會自動選擇對應版本的游戲包進行加載,確保用戶體驗一致。

? 更進一步:自定義存儲游玩歷史

當然,如果你希望自行管理這些歷史記錄,宿主 SDK 還支持通過注入自定義的 HistorySaver 接口來自定義存儲邏輯。

public void setHistorySaver(HistorySaver saver)

public interface HistorySaver {
    void save(HistoryModel history);
}

我們可以在宿主應用里實現(xiàn)一個 HistorySaver,在 save 方法中將用戶的游玩記錄上傳到自己的服務端數(shù)據(jù)庫中。這樣,不僅可以實現(xiàn)跨設備同步,也能為后續(xù)的個性化推薦打好基礎。

4.1.3 多進程 & 多任務棧:小游戲也能“開多個窗口”

參考文檔:https://minihost.tuanjie.cn/help/docs/sdk/android/quick-integrate#31-%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%9A%E8%BF%9B%E7%A8%8B%E5%90%AF%E5%8A%A8%E5%99%A8

有時候,用戶希望能在宿主應用主界面和小游戲之間自由切換——畢竟上班摸魚的時候,反應要快,可不能被發(fā)現(xiàn)了。另外,如果某個游戲包出現(xiàn)了 bug 導致崩潰或卡死,我們也不希望影響到整個宿主 App 的穩(wěn)定性。

為了解決這些問題,我們采用了多進程 + 多任務棧的方案,讓每個小游戲都能在獨立的“窗口”中運行,互不干擾、互不拖累,資源也能各自獨立管理。

最簡單的實現(xiàn)方式是我們可以在 AndroidManifest.xml 中為游戲 Activity 指定一個獨立的進程名和任務棧名,讓它運行在自己的空間里:

".host.GameActivity"
    android:screenOrientation="portrait"
    android:configChanges="keyboard|orientation|screenSize|keyboardHidden|navigation|screenLayout"
    android:launchMode="singleTask"
    android:process=":hostProc"
    android:taskAffinity="com.unity3d.unityconnect.host.affinity">

如果我們希望支持多個小游戲同時運行,只需要多聲明幾個繼承自 GameActivity 的子類,并在 Manifest 中為它們配置不同的進程名和任務棧名:

public class GemeActivity1 extends GameActivity {} 
public class GemeActivity2 extends GameActivity {}

這樣,在運行時我們就可以通過 startActivity 啟動不同的游戲 Activity,實現(xiàn)多個小游戲并行運行,各自獨立互不干擾。

如果游戲已經(jīng)在后臺,再次點擊還能“喚醒”嗎?

當然可以!為了更方便地管理這些多進程的小游戲,宿主 SDK 提供了一個實用的工具類:MultiProcessLauncher。它專門用來解決以下幾個問題:

  • 啟動新的小游戲時,自動頂?shù)糇钤鐔拥呐f游戲

  • 如果游戲已經(jīng)在后臺,點擊時自動拉到前臺

  • 管理多個游戲容器的生命周期和狀態(tài)

它主要包含兩個接口:

  • configContainer:注冊所有可用的游戲容器(Activity + 進程)

  • launch:通過 launchKey 啟動或喚醒游戲

還包含一個默認的生命周期監(jiān)視器實現(xiàn) MultiProcessLauncher.MiniGameLifeCycle 用于監(jiān)測小游戲狀態(tài)。通過它們我們能直接實現(xiàn)各小游戲進程的管理。

我們先需要在游戲 Activity onCreate 中添加生命周期監(jiān)視器,從而使得多進程監(jiān)視器能夠動態(tài)檢測到游戲 Activity 的各項生命周期回調(diào)。

privatevoidinitLifeCycleObserver() { 
    String launchKey = getIntent().getStringExtra("launchKey");
    gameLifeCycle = new MultiProcessLauncher.MiniGameLifeCycle(this, launchKey);
    getLifecycle().addObserver(gameLifeCycle);
}

假設我們希望最多支持兩個小游戲同時運行,那么可以在 AndroidManifest 中聲明兩個容器,并使用 configContainer 將它們注冊進多進程啟動器:

privatevoidinitMultiProcessLauncher(){ 
    Map

 map = new HashMap<>();     map.put(":hostProc1", GameActivity1.class);     map.put(":hostProc2", GameActivity2.class);     MultiProcessLauncher.configContainer(map); } 

在用戶點擊某個游戲時,調(diào)用 launch 方法并傳入 launchKey,啟動器會自動判斷:

  • 如果游戲已經(jīng)在后臺,會將其拉到前臺

  • 如果沒有在后臺,但容器已滿,則頂?shù)糇钤鐔拥哪莻€游戲


MultiProcessLauncher.launch(context, "launchKey", intent -> {
    intent.putExtra("launchKey", launchkey);
// And other params if needed
});

此外,Android 10 后 WebView 在多進程中的使用做了限制,每個進程只能訪問自己的網(wǎng)絡數(shù)據(jù),因此需要為每個進程制定數(shù)據(jù)存儲目錄。我們可以在游戲 Activity 初始化時,簡單使用進程名作為數(shù)據(jù)目錄名稱以避免報錯。

WebView.setDataDirectorySuffix("processName");

通過這種方式,我們不僅實現(xiàn)了小游戲之間的并行運行,也大大提升了宿主應用的穩(wěn)定性和靈活性。用戶可以像在瀏覽器中切換標簽頁一樣,在多個小游戲間自由切換,真正實現(xiàn)“多開不是夢”。

4.1.4 支持動態(tài)加載:精簡 APP 體積的魔法

參考文檔:https://minihost.tuanjie.cn/help/docs/sdk/android/quick-integrate#32-%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD-so-%E5%BA%93

前文提到,宿主 SDK 提供了兩種版本:靜態(tài)版和動態(tài)版。

靜態(tài)版的優(yōu)勢在于接入簡單、運行穩(wěn)定,并且能一定程度上保證 SDK 的完整性和安全性。但它也有一個明顯的短板——體積大。打包后 AAR 文件將近 40MB,對于宿主 App 來說,這可能會顯著增加安裝包大小,尤其對新用戶或對小游戲不感興趣的用戶來說,這部分體積是“沉重”的負擔 。

為了解決這個問題,Connect 選擇使用了宿主 SDK 的動態(tài)版本。它的核心優(yōu)勢是輕量,AAR 包體不到 1MB。小游戲運行環(huán)境不再一開始就集成進 App,而是在真正需要的時候“按需加載”。

比如,當用戶點擊進入「游戲中心」時,我們再觸發(fā)動態(tài)加載邏輯,下載小游戲運行所需的依賴。

因此宿主 SDK 提供了 prepare 方法,用于動態(tài)加載運行環(huán)境:

TJHost._prepare_(this, new OnLoadSharedLibraryListener() {
    @Override
    publicvoidonSuccess(String s){ 
        l_ibPath _= s;
        _isTJHostHandleInitialized _= true;
    }

     @Override
     public void onDownloading(int progress){
          // update UI
    }

     @Override
     public void onFailure(Throwable throwable){
         // retry or toast
    }
});

這個接口支持加載進度回調(diào),可以配合 UI 展示加載動畫或進度條,提升用戶體驗。

加載成功后,我們會拿到一個 libPath,也就是運行環(huán)境的本地路徑。接下來,在啟動游戲的 Activity 中,我們只需要在游戲 Activity onCreate 的時候調(diào)用以下接口,將這個路徑注入到運行環(huán)境中即可:

boolean result = TJHost._installNativeLibraryPath_(getApplicationContext(), l_ibPath_);

一旦注入成功,小游戲就可以像靜態(tài)版一樣正常運行,用戶幾乎感知不到差異。通過動態(tài)加載機制,我們成功地將小游戲運行環(huán)境“從包里搬到了云上”,不僅減輕了宿主 App 的初始體積負擔,也為后續(xù)更靈活的按需加載、分模塊更新打下了基礎。

4.2 小游戲開發(fā)者

對于小游戲開發(fā)者來說,完整走一遍“創(chuàng)建宿主應用 → 創(chuàng)建宿主小游戲 → 上傳游戲包 → 發(fā)布”這一整套流程,雖然是正式上線所必需的,但如果只是想快速驗證一個小游戲包的運行效果,流程就顯得有些繁瑣了。

尤其是在開發(fā)階段,我們更希望有一種便捷的方式,能快速啟動某個小游戲包進行調(diào)試,甚至還能將這個方式分享給其他開發(fā)者或測試人員一起使用。同時,如果能配合一些調(diào)試工具,那開發(fā)效率自然也能大大提升。

4.2.1 掃碼啟動小游戲:一鍵體驗更高效

參考文檔:https://minihost.tuanjie.cn/help/docs/quickstart/create-game#3-%E6%89%AB%E7%A0%81%E4%BD%93%E9%AA%8C%E6%B8%B8%E6%88%8F

為了解決這個問題,我們首先想到的就是——掃碼啟動。

在宿主控制臺中,每一個小游戲的線上版本,以及每一個上傳的游戲包版本,都會自動生成一個二維碼,支持掃碼體驗:

宿主應用通過這個二維碼啟動游戲的方式也非常簡單:

  1. 掃描二維碼,獲取其中的字符串內(nèi)容(這是一個特殊格式的 launchKey)

  2. 將該字符串作為游戲啟動參數(shù)中的 LAUNCH_KEY 傳入宿主 SDK

  3. 宿主 SDK 會自動識別該 launchKey 對應的游戲包信息,下載對應版本并啟動游戲

宿主應用通過這個二維碼啟動游戲的方式也非常簡單,只需要掃描獲取二維碼的對應的字符串,并且設置為游戲啟動參數(shù)中的 LAUNCH_KEY 即可。

這個二維碼啟動方式的最大優(yōu)勢在于——沒有組織權限限制。也就是說,即使你的宿主應用與小游戲不屬于同一個組織,也可以通過掃碼啟動游戲,非常適合在開發(fā)、測試階段跨團隊協(xié)作使用。

此外,通過掃碼啟動的游戲,其游玩歷史記錄中的 dev 字段會被標記為 true,表示這是一個開發(fā)版本。同時,如果你多次掃碼體驗同一個小游戲的不同版本(比如測試包、灰度包等),系統(tǒng)也會為每個版本記錄一條獨立的歷史記錄,因為此時的 launchKey 會攜帶具體的版本信息。

掃碼啟動不僅讓小游戲開發(fā)者調(diào)試更高效,也為測試人員、產(chǎn)品經(jīng)理等非技術角色提供了一個無需打包、無需配置就能「一鍵體驗」的便捷入口。再配合宿主 SDK 提供的調(diào)試工具,整個開發(fā)流程也就更加順暢了。

4.2.2 小游戲 C# 代碼調(diào)試支持:讓問題排查更高效

在開發(fā)過程中,遇到小游戲中出現(xiàn)一些不符合預期的行為是常有的事。雖然我們可以通過游戲內(nèi)的日志來初步判斷問題的大致范圍,但要精準定位問題根源,很多開發(fā)者還是希望能夠通過調(diào)試工具直接查看代碼運行邏輯。

不過,與在 Unity 編輯器中直接運行游戲不同,在宿主環(huán)境中運行小游戲時,我們無法像平常那樣使用 IDE(如 Rider 或 Visual Studio)直接啟動調(diào)試模式。這就對調(diào)試過程提出了新的挑戰(zhàn)。

為了幫助開發(fā)者更高效地排查問題,宿主 SDK 提供了一個便捷的調(diào)試入口,允許宿主應用在合適的時機(比如用戶點擊某個按鈕時)打開調(diào)試面板:

hostHandle.showDebugMenu();

調(diào)試面板中包含了查看小游戲運行過程中的性能數(shù)據(jù)、啟用 C# 代碼等多個實用功能,

當點擊「C# 調(diào)試」按鈕后,宿主 SDK 會自動啟動一個遠程調(diào)試服務。此時,我們可以在 Rider 中通過 Attach to Process 的方式,連接到正在運行的小游戲進程,從而實現(xiàn)在項目代碼中設置斷點、逐步調(diào)試等功能。

通過這個調(diào)試面板,開發(fā)者不僅可以快速了解小游戲的運行狀態(tài),還能像在本地調(diào)試一樣深入排查問題邏輯,大大提升了開發(fā)和測試效率。

4.3宿主應用開發(fā)者

對于宿主應用的開發(fā)者來說,無論是為了打造“超級 App”而引入宿主 SDK,還是計劃用宿主方案替代原有的小游戲平臺,最關鍵的問題始終是:小游戲接入之后,實際表現(xiàn)到底如何?

這里的“表現(xiàn)”不僅僅是訪問量、啟動次數(shù)這些表層數(shù)據(jù),更重要的是小游戲在運行過程中的性能表現(xiàn)、穩(wěn)定性、用戶留存等核心指標。這些數(shù)據(jù)直接關系到:

  • 小游戲是否值得繼續(xù)推廣

  • 哪些內(nèi)容更受用戶歡迎

  • 如何有針對性地進行優(yōu)化和迭代

通過對這些數(shù)據(jù)的深入分析,我們可以更科學地評估宿主小游戲的整體質(zhì)量和用戶接受度,從而做出更明智的產(chǎn)品決策。

4.3.1 小游戲數(shù)據(jù)看板:一目了然的表現(xiàn)評估

為了解決上述問題,宿主控制臺提供了專門的數(shù)據(jù)分析頁面,幫助我們快速掌握小游戲的關鍵表現(xiàn)指標。

在這個頁面中,我們可以按時間維度篩選數(shù)據(jù),查看每個小游戲的:

  • 性能表現(xiàn)(如加載耗時、幀率等)

  • 用戶訪問情況(如啟動次數(shù)、活躍用戶數(shù))

  • 留存情況(次日、7 日留存等)

這些數(shù)據(jù)不僅幫助我們與小游戲開發(fā)者一起定位性能瓶頸、優(yōu)化用戶體驗,也為后續(xù)的推廣策略和買量投放提供了重要參考。

數(shù)據(jù)是最好的反饋機制。通過宿主控制臺的數(shù)據(jù)看板,我們可以從“感覺不錯”轉向“數(shù)據(jù)說話”,讓小游戲生態(tài)的優(yōu)化更加精準、可持續(xù)。

上述這一系列優(yōu)化改動,雖然看起來只是“錦上添花”,但它們讓宿主應用的體驗更加完整,也讓我們在產(chǎn)品層面有了更多可發(fā)揮的空間!

五、樂高積木式的平臺能力接入

小游戲跑起來之后,我們的工作并沒有結束。下一步,就是思考如何把宿主應用的“平臺能力”開放給小游戲使用。

在運行過程中,小游戲可能會調(diào)用一些平臺能力的 JS 接口,比如登錄、廣告、支付等。由于宿主 SDK 本身并不內(nèi)置這些功能(例如賬號體系或廣告服務),所以這些接口的具體實現(xiàn)就需要由宿主應用來完成。

那問題來了:如何像搭樂高積木一樣,按需接入這些模塊化的能力,并通過統(tǒng)一的方式暴露給小游戲使用呢?

5.1接入平臺登錄能力

參考文檔:https://minihost.tuanjie.cn/help/docs/sdk/android/platform-integrate#1-%E7%99%BB%E5%BD%95-sdk

首先,我們來看看宿主小游戲的登錄流程:

當小游戲啟動時,通常會調(diào)用平臺的登錄接口(如微信中的 wx.login,在宿主環(huán)境下對應的是 tj.login)。這個調(diào)用會提示宿主應用拉起登錄浮層,引導用戶完成登錄。

登錄成功后,宿主應用會返回一個臨時憑證 code,小游戲客戶端將該 code 傳給自己的服務端,服務端再通過宿主提供的 Code2Session 接口換取用戶的登錄態(tài)信息(Session)。這個 Session 中包含了用戶在當前小游戲中的唯一標識 OpenID,游戲可以通過這個 OpenID 來讀取玩家的云存檔或本地數(shù)據(jù)。

所以,宿主應用如果想支持登錄能力,需要完成兩件事:

  1. 在客戶端實現(xiàn) tj.login 這個 JS API,讓小游戲能拿到 code

  2. 在服務端實現(xiàn) Code2Session 接口,供小游戲服務端換取 Session

5.1.1 宿主應用實現(xiàn) JS API:把 Native 能力暴露給 JS

有些小伙伴可能會疑惑:宿主是用 Java/Kotlin 開發(fā)的,小游戲是運行在 JS 環(huán)境中的,怎么把 Native 能力暴露給 JS 呢?

別擔心,宿主 SDK 已經(jīng)為我們準備好了 橋接接口 ,支持從 JS 調(diào)用 Native 方法 :

tj.customCommand(Object object, string methodName, ...); 
tj.customCommandSync(Object object, String methodName, ...);

橋接接口:https://minihost.tuanjie.cn/help/docs/api/android/custom-command#js-%E5%B1%82%E8%B0%83%E7%94%A8-java-%E5%B1%82%E6%8E%A5%E5%8F%A3

那么下一個想到的問題是,在宿主應用內(nèi)如何介入小游戲所運行在的 JS 環(huán)境呢?我們可以通過啟動參數(shù) BUILTIN_CUSTOM_SCRIPTS_PATH 向小游戲環(huán)境中注入自定義 JS 文件。例如:

bundle.putString(TJConstants._BUILTIN_CUSTOM_SCRIPTS_PATH_, "customScripts");

這樣,宿主應用就會加載 assets/customScripts 目錄下的 JS 文件,并注入到小游戲的運行環(huán)境中。

那么有了上面這些信息,我們知道在宿主應用內(nèi)實現(xiàn)一個 JS API 可以通過在啟動游戲時注入一些 JS 文件,文件中定義了小游戲需要調(diào)用的 JS API 如 tj.login 的方式實現(xiàn),而這些 JS API 的最終實現(xiàn)則可以通過 customCommand 接口將其從 JS 層轉移到 Native 層。

那么讓我們來大展身手吧!我們可以在 assets/customScripts 目錄下創(chuàng)建一個 auth.js 文件,定義 tj.login 方法。它的核心邏輯是調(diào)用 customCommand 接口,轉發(fā)到 Native 層的 loginUnity 方法:

tj.login = function (obj) { 
    tj.customCommand({
        success: function(res) {
            let resObj = JSON.parse(res);
            obj.success(resObj);
        },
        fail: function(res) {
            let apiRes = {
                errMsg: res.errorMsg,
                errno: res.errorCode
            };
            obj.fail(apiRes);

        },
        complete: function() {
            obj.complete();
        }
    }, "loginUnity");
};

在 Java 端,我們需要在 setGameStartOptions 之后,設置一個自定義命令監(jiān)聽,處理來自 JS 的 loginUnity 調(diào)用:

hostHandle.setCustomCommandListener("loginUnity", (customCommandHandle, bundle) -> {
    try { 
        String code = LoginSDK.getInstance().getUserCode();

        JSONObject loginRes = new JSONObject();
        loginRes.put("code", code);

        customCommandHandle.pushResult(loginRes.toString());
        customCommandHandle.success();
    } catch (RemoteException e) {
        customCommandHandle.fail(1001, "Failed to connect Login Service: " + e.getMessage());
    } catch (Exception e) {
        customCommandHandle.fail(1002, "Failed to serialize login result: " + e.getMessage());
    }
});

完成以上配置后,小游戲就可以通過調(diào)用 tj.login 獲取宿主應用返回的 code,完成登錄流程。整個過程就像拼裝樂高一樣:JS 和 Native 各自實現(xiàn)一塊,通過宿主 SDK 提供的橋梁拼接在一起,既靈活又高效。

5.1.2 宿主應用服務端配置:Code 換 Session 的“最后一公里”

參考文檔:https://minihost.tuanjie.cn/help/docs/sdk/server-integrate#jscode2session

前面我們已經(jīng)完成了客戶端的登錄流程:小游戲通過調(diào)用 tj.login 拿到了登錄憑證 code。接下來,就輪到服務端登場啦!

小游戲客戶端會把這個 code 傳給自己的游戲服務器,而游戲服務器則需要通過宿主應用的服務端接口,將 code 換成包含用戶身份信息的 Session(比如 openId、session_key 等)。這一步就相當于登錄流程的“最后一公里”。

為了讓這個流程順利跑通,我們需要做兩件小事:

  1. 在宿主應用的服務端實現(xiàn)一個 Code2Session 接口,負責接收 code 并返回用戶的 Session 信息。

  2. 在宿主控制臺中,配置這個接口的地址,也就是“平臺方接入 URL”。

配置完成后,宿主平臺會自動將小游戲傳來的 code 轉發(fā)給你配置的接口,并將返回的 Session 信息傳回給小游戲服務端。

通過這一步,小游戲的登錄流程就真正打通了:從宿主應用拉起登錄 → 拿到 Code → 宿主應用服務端換取 Session → 獲取用戶身份,是不是感覺已經(jīng)可以愉快地登錄了?

登錄能力的接入,主要是為了確保每個需要用戶身份的小游戲都能順利運行。而除了登錄之外,另一個我們非常關注的平臺能力就是——廣告。

作為小游戲變現(xiàn)的重要手段,廣告能力的接入自然是優(yōu)先級很高的一項工作。無論是激勵視頻、插屏廣告,還是 Banner 展示,合理的廣告接入不僅能帶來收入,也能為小游戲生態(tài)帶來更強的可持續(xù)性。

具體的廣告能力實現(xiàn)方式可以參考宿主 SDK 的官方文檔,里面對接入流程、回調(diào)處理等內(nèi)容都有詳細說明。這里我們就不展開贅述啦,感興趣的小伙伴可以自行查閱。

六、我們的下一站

從 Connect 成為小游戲宿主的第一塊“試驗田”開始,我們一步步搭起了這片數(shù)字田地的基礎設施:小游戲能跑起來了,宿主能變得更聰明了,平臺能力也像樂高一樣拼裝上了。

但這只是開始。

未來,我們還將持續(xù)優(yōu)化宿主 SDK 的接入體驗,降低平臺能力擴展的門檻,提供更豐富的調(diào)試工具、更靈活的運營能力、更智能的數(shù)據(jù)分析支持。我們也在探索更多引擎的兼容性、更廣泛的平臺適配,以及更輕量的運行時架構,力求讓每一個 App 都能輕松擁有自己的小游戲生態(tài)。

當然,一個生態(tài)的繁榮,永遠不是靠一個宿主應用就能完成的。我們希望有更多開發(fā)者、內(nèi)容創(chuàng)作者、平臺伙伴加入我們,一起耕耘、一起試驗、一起收獲——把這片“宿主田地”種得更大、更好、更有生命力。

畢竟,在這個賽博世界里,我們都是數(shù)字田地里的“農(nóng)夫”——用代碼種下希望,用創(chuàng)意收獲樂趣。未來的宿主生態(tài),就等我們一起開墾、一起灌溉、一起收成。

廣闊天地,大有可為。我們的下一站,就在前方。

Unity 官方微信

第一時間了解Unity引擎動向,學習進階開發(fā)技能

每一個“點贊”、“在看”,都是我們前進的動力

特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺“網(wǎng)易號”用戶上傳并發(fā)布,本平臺僅提供信息存儲服務。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相關推薦
熱點推薦
國足新帥人選浮出水面:莫德里奇恩師,年薪40萬!各方面條件完美

國足新帥人選浮出水面:莫德里奇恩師,年薪40萬!各方面條件完美

國足風云
2025-07-23 14:47:59
陳佩斯新片全場零笑聲?點映場觀眾為何集體沉默!

陳佩斯新片全場零笑聲?點映場觀眾為何集體沉默!

情感大頭說說
2025-07-23 14:23:09
突然爆雷!剛剛,全線大跌!

突然爆雷!剛剛,全線大跌!

券商中國
2025-07-23 20:48:27
林彪和九大開國元帥關系如何?

林彪和九大開國元帥關系如何?

河山歷史
2025-07-19 11:52:52
“社會性死亡!”撲小孩杜賓狗主人放出狠話,正臉卻被扒要涼涼了

“社會性死亡!”撲小孩杜賓狗主人放出狠話,正臉卻被扒要涼涼了

鋭娛之樂
2025-07-22 18:19:15
贏麻了,陳佩斯哭了,票房300倍逆襲,朱時茂百萬投資可掙了不少

贏麻了,陳佩斯哭了,票房300倍逆襲,朱時茂百萬投資可掙了不少

陳意小可愛
2025-07-21 17:50:45
全運會期間,粵A實現(xiàn)“單雙限行”,那電雞呢?

全運會期間,粵A實現(xiàn)“單雙限行”,那電雞呢?

樓市前線
2025-07-23 18:21:54
煥然一新的紅魔?阿莫林的夢想11人,在姆貝烏莫之后還有三筆轉會

煥然一新的紅魔?阿莫林的夢想11人,在姆貝烏莫之后還有三筆轉會

夜白侃球
2025-07-23 20:11:29
2-0奪冠!中國隊終于贏了,10年,等了整整10年,這一刻等得太久

2-0奪冠!中國隊終于贏了,10年,等了整整10年,這一刻等得太久

粵語經(jīng)典歌單
2025-07-22 18:59:35
徹底傻眼了!宗澤后深夜發(fā)長文情緒失控,詛咒誹謗他老母親的網(wǎng)友

徹底傻眼了!宗澤后深夜發(fā)長文情緒失控,詛咒誹謗他老母親的網(wǎng)友

美美談情感
2025-07-23 02:20:36
聽我句勸,大家不要倒在黎明前!

聽我句勸,大家不要倒在黎明前!

人格志
2025-07-23 21:09:59
曝廣東女子出軌多人,關系混亂轟動全碣石:8頁詳情更炸裂!

曝廣東女子出軌多人,關系混亂轟動全碣石:8頁詳情更炸裂!

農(nóng)村情感故事
2025-07-23 19:01:51
58歲金星在法國巴黎吃火鍋,越老越像男人,身上有了油膩感

58歲金星在法國巴黎吃火鍋,越老越像男人,身上有了油膩感

鄉(xiāng)野小珥
2025-07-21 00:34:37
韋世豪:感謝全國友好球迷給我力量,輸天津后該把別的事拋在腦后

韋世豪:感謝全國友好球迷給我力量,輸天津后該把別的事拋在腦后

雷速體育
2025-07-24 00:15:34
洗牌!今年中考,廣州最狠黑馬,是它!

洗牌!今年中考,廣州最狠黑馬,是它!

廣州PLUS
2025-07-23 22:52:53
韋德力挺科比,他是歷史前三,沒水準的人,才會把科比放在第11

韋德力挺科比,他是歷史前三,沒水準的人,才會把科比放在第11

阿雄侃籃球
2025-07-24 00:00:38
“黃毛男孩”曬北大通知書,結果專業(yè)被群嘲:畢業(yè)了也是送外賣

“黃毛男孩”曬北大通知書,結果專業(yè)被群嘲:畢業(yè)了也是送外賣

大魚簡科
2025-07-23 19:32:07
再下一城,菲律賓也簽署協(xié)議,對美完全開放、美國收19%的稅

再下一城,菲律賓也簽署協(xié)議,對美完全開放、美國收19%的稅

邵旭峰域
2025-07-23 09:17:40
俄軍炸了波蘭工廠,烏方呼吁波蘭有權反擊,北約不敢聲張要冷處理

俄軍炸了波蘭工廠,烏方呼吁波蘭有權反擊,北約不敢聲張要冷處理

歷史求知所
2025-07-22 18:30:06
李小龍打遍天下無敵手?妻子琳達晚年透露:丈夫其實很怕一種對手

李小龍打遍天下無敵手?妻子琳達晚年透露:丈夫其實很怕一種對手

以絕望揮劍
2025-07-14 22:00:14
2025-07-24 01:00:49
Unity incentive-icons
Unity
Unity中國官方帳戶
2331文章數(shù) 6722關注度
往期回顧 全部

游戲要聞

夢幻西游【如夢似幻】25日開服,這三個福利信息你了解了么?

頭條要聞

印度、孟加拉關切雅魯藏布江下游水電站工程 中方回應

頭條要聞

印度、孟加拉關切雅魯藏布江下游水電站工程 中方回應

體育要聞

英格蘭最紅球星 也是加勒比島國驕傲

娛樂要聞

汪峰森林北同游日本 各帶各娃互不耽誤

財經(jīng)要聞

律師解析娃哈哈遺產(chǎn)案:遺囑是最大變數(shù)

科技要聞

別自嗨了!XREAL徐馳:AI眼鏡只有5歲智商

汽車要聞

德系大招放盡 場地極限測試全新奧迪A5L

態(tài)度原創(chuàng)

健康
教育
手機
房產(chǎn)
公開課

呼吸科專家破解呼吸道九大謠言!

教育要聞

撿漏王誕生!黑龍江一考生389分上211鄭州大學,讓人羨慕

手機要聞

iOS 26 Beta 4更新,液態(tài)玻璃又調(diào)整

房產(chǎn)要聞

海南自由貿(mào)易港全島封關,2025年12月18日正式啟動!

公開課

李玫瑾:為什么性格比能力更重要?

無障礙瀏覽 進入關懷版 主站蜘蛛池模板: 寿宁县| 长岛县| 万荣县| 曲麻莱县| 盐津县| 饶阳县| 新蔡县| 安泽县| 武汉市| 曲松县| 岢岚县| 门头沟区| 桐柏县| 昭平县| 邻水| 芦溪县| 大竹县| 淮滨县| 麻城市| 北流市| 赤水市| 布拖县| 新津县| 灵川县| 韶山市| 定远县| 洮南市| 克什克腾旗| 化德县| 卫辉市| 射阳县| 阳城县| 昭苏县| 噶尔县| 许昌市| 珠海市| 周宁县| 科尔| 延安市| 洪江市| 池州市|