主頁 > 移動端開發 > Android系統服務(SystemService)簡介

Android系統服務(SystemService)簡介

2020-09-13 23:41:10 移動端開發

什么是SystemService

我們在Android開發程序中經常會用到各種各樣的系統管理服務,如進行視窗相關的操作會用到視窗管理服務WindowManager,進行電源相關的操作會用到電源管理服務PowerManager,還有很多其他的系統管理服務,如通知管理服務NotifacationManager、振動管理服務Vibrator、電池管理服務BatteryManager…… 這些Manager提供了很多對系統層的控制介面,對于App開發者,只需要了解這些介面的使用方式就可以方便的進行系統控制,獲得系統各個服務的資訊,而不需要了解這些介面的具體實作方式,而對于Framework開發者,則需要了解這些Manager服務的常用實作模式,維護這些Manager的介面,擴展這些介面,或者實作新的Manager,

image

一個簡單的SystemService

我們從一個簡單的系統服務Vibrator服務來看一下一個系統服務是怎樣建立的,

Vibrator服務提供的控制手機振動的介面,應用可以呼叫Vibrator的介面來讓手機產生振動,達到提醒用戶的目的,

從Android的官方檔案中可以看到Vibrator只是一個抽象類,只有4個抽象介面:

  • bstract void cancel() 取消振動
  • abstract boolean hasVibrator() 是否有振動功能
  • abstract void vibrate(long[] pattern, int repeat) 按節奏重復振動
  • abstract void vibrate(long milliseconds) 持續振動

應用中使用振動服務的方法也很簡單,如讓手機持續振動500毫秒:

Vibrator mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
mVibrator.vibrate(500);

Vibrator使用起來很簡單,我們再來看一下實作起來是不是也簡單,

從檔案中可以看到Vibrator只是定義在android.os 包里的一個抽象類,在原始碼里的位置即frameworks/base/core/java/android/os/Vibrator.java,那么應用中實際使用的是哪個實體呢?應用中使用的Vibrator實體是通過Context的一個方法getSystemService(Context.VIBRATOR_SERVICE)獲得的,而Context的實作一般都在ContextImpl中,那我們就看一下ContextImpl是怎么實作getSystemService的:

frameworks/base/core/java/android/app/ContextImpl.java

@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

frameworks/base/core/java/android/app/SystemServiceRegistry.java
(SystemServiceRegistry是 Android 6.0之后才有的,Android 6.0 之前的代碼沒有該類,下面的代碼是直接寫在ContextImpl里的)

 public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

SYSTEM_SERVICE_MAP是一個HashMap,通過我們服務的名字name字串,從這個HashMap里取出一個ServiceFetcher,再return這個ServiceFetcher的getService(),ServiceFetcher是什么?它的getService()又是什么?既然他是從SYSTEM_SERVICE_MAP這個HashMap里get出來的,那就找一找這個HashMap都put了什么,

通過搜索SystemServiceRegistry可以找到如下代碼:

private static <T> void registerService(String serviceName, Class<T> serviceClass,
        ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}

這里往SYSTEM_SERVICE_MAP里put了一對String與ServiceFetcher組成的key/value對,registerService()又是從哪里呼叫的?繼續搜索可以發現很多類似下面的代碼:

public class SystemVibrator extends Vibrator {
    ...
}

我們再從SystemVibrator看一下系統的振動控制是怎么實作的,以hasVibrator()為例,這個是查詢當前系統是否能夠振動,在SystemVibrator中它的實作如下:

public boolean hasVibrator() {
    ...
    try {
        return mService.hasVibrator();
    } catch (RemoteException e) {
    }
    ...
}

這里直接呼叫了一個mService.hasVibrator(),mService是什么?哪來的?搜索一下可以發現:

private final IVibratorService mService;
public SystemVibrator() {
    ...
    mService = IVibratorService.Stub.asInterface(
            ServiceManager.getService("vibrator"));
}

mService 是一個IVibratorService,我們先不去管IVibratorService.Stub.asInterface是怎么回事,先看一下IVibratorService是什么,搜索一下代碼發現這并不是一個java檔案,而是一個aidl檔案:

frameworks/base/core/java/android/os/IVibratorService.aidl

AIDL (Android Interface Definition Language) 是Android中的介面定義檔案,為系統提供了一種簡單跨行程通信方法,

IVibratorService 中定義了幾個介面,SystemVibrator中使用的也是這幾個介面,包括我們剛才使用的hasVibrator()

interface IVibratorService
{
    boolean hasVibrator();
    void vibrate(...);
    void vibratePattern(...);
    void cancelVibrate(IBinder token);
}

這里又只是介面定義,介面實作在哪呢?通過在frameworks/base目錄下進行grep搜索,或者在AndroidXRef搜索,可以發現IVibratorService介面的實作在frameworks/base/services/java/com/android/server/VibratorService.java

public class VibratorService extends IVibratorService.Stub

可以看到 VibratorService實作了IVibratorService定義的所有介面,并通過JNI呼叫到native層,進行更底層的實作,更底層的實作不是這篇檔案討論的內容,我們需要分析的是VibratorService怎么成為系統服務的,那么VibratorService是怎么注冊為系統服務的呢?在SystemServer里面:

VibratorService vibrator = null;
...
//實體化VibratorService并添加到ServiceManager
traceBeginAndSlog("StartVibratorService");
vibrator = new VibratorService(context);
ServiceManager.addService("vibrator", vibrator);
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
...
//通知服務系統啟動完成
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "MakeVibratorServiceReady");
try {
    vibrator.systemReady();
} catch (Throwable e) {
    reportWtf("making Vibrator Service ready", e);
}
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);

這樣在SystemVibrator里就可以通過下面的代碼連接到VibratorService,與底層的系統服務進行通信了:

IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));

mService相當于IVibratorService在應用層的一個代理,所有的實作還是在SystemServer的VibratorService里,

看代碼時可以發現registerService是在static代碼塊里靜態呼叫的,所以getSystemServcr獲得的各個Manager也都是單例的,

System Service實作流程

從上面的分析,我們可以總結出Vibrator服務的整個實作流程:

  1. 定義一個抽象類Vibrator,定義了應用中可以訪問的一些抽象方法
frameworks/base/core/java/android/os/Vibrator.java
  1. 定義具體的類SystemVibrator繼承Vibrator,實作抽象方法

frameworks/base/core/java/android/os/SystemVibrator.java

  1. 定義一個AIDL介面檔案IVibratorService,定義系統服務介面

frameworks/base/core/java/android/os/IVibratorService.aidl

  1. 定義服務VibratorService,實作IVibratorService定義的介面

frameworks/base/services/java/com/android/server/VibratorService.java

  1. 將VibratorServicey添加到系統服務

frameworks/base/services/java/com/android/server/SystemServer.java

VibratorService vibrator = null;
...
//實體化VibratorService并添加到ServiceManager
Slog.i(TAG, "Vibrator Service");
vibrator = new VibratorService(context);
ServiceManager.addService("vibrator", vibrator);
...
//通知服務系統啟動完成
try {
    vibrator.systemReady();
} catch (Throwable e) {
    reportWtf("making Vibrator Service ready", e);
}
  1. 在SystemVibrator中通過IVibratorService的代理連接到VibratorService,這樣SystemVibrator的介面實作里就可以呼叫IVibratorService的介面:

frameworks/base/core/java/android/os/SystemVibrator.java

private final IVibratorService mService;
...
public SystemVibrator() {
    ...
    mService = IVibratorService.Stub.asInterface(
            ServiceManager.getService("vibrator"));
    ...
    public boolean hasVibrator() {
        ...
        try {
            return mService.hasVibrator();
        } catch (RemoteException e) {
        }
        ...
    }
}
  1. 在Context里定義一個代表Vibrator服務的字串

frameworks/base/core/java/android/content/Context.java

public static final String VIBRATOR_SERVICE = "vibrator";
  1. 在ContextImpl里添加SystemVibrator的實體化程序

frameworks/base/core/java/android/app/ContextImpl.java

registerService(VIBRATOR_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
    return new SystemVibrator(ctx);
}});  
  1. 在應用中使用Vibrator的介面
Vibrator mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
mVibrator.vibrate(500);
  1. 為保證編譯正常,還需要將AIDL檔案添加到編譯配置里

frameworks/base/Android.mk

LOCAL_SRC_FILES += \
...
core/java/android/os/IVibratorService.aidl \

System Service 新加介面

如果我們需要實作一個新的系統服務,就可以按照上面的步驟在系統中擴展出一個新的服務,并給應用層提供出使用介面,如果想在Vibrator里添加一個新的介面,需要下面3步:

  1. 在IVibratorService添加介面;
  2. 在VibratorService添加介面的實作;
  3. 在Vibrator及SystemVibrator里擴展新的介面;

這樣應用中就可以使用Vibrator的新介面了,

應用層與 System Service 通信

上面的實作我們看到的只是從應用層通過服務代理,呼叫系統服務的介面,如果我們想反過來,將系統服務的狀態通知給應用層,該怎么做呢?

  • 方法一:使用Broadcast

我們知道使用Broadcast廣播可以實作跨行程的訊息傳遞,一些系統服務也使用了這種方法,如電池管理服務BatteryManagerService,收到底層上報的電池狀態變化資訊時,就將當前的電池狀態封裝在一個Intent里,action為android.intent.action.BATTERY_CHANGED,應用只要注冊一個對應的BroadcastReceiver就可以收到BatterManagerService發送的電池狀態資訊,

  • 方法二:使用AIDL

從上面我們可以知道,通過AIDL定義一套介面,由系統服務端實作這些介面,應用端使用一個相應的代理就可以訪問系統服務的介面,那反過來讓應用端實作AIDL介面,系統服務端使用代理呼叫應用端的介面可不可以呢?答案是YES,那么接下來的問題是怎么讓系統服務得到這個代理,我們再來看一個LocationManager的例子,

//獲得定位服務
LocationManager locationManager = 
        (LocationManager) getSystemService(Context.LOCATION_SERVICE);

//定義定位監聽器
LocationListener locationListener = new LocationListener() {
    public void onLocationChanged(Location location) {
        //監聽到位置資訊
    }
    ...
};

//注冊監聽器
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 
        0, 0, locationListener);

從上面的代碼可以看到,我們創建了一個位置監聽器LocationListener,并將這個監聽器在LocationManager里進行了注冊,當系統定位到系統的位置后,就會回呼監聽器的onLocationChanged(),將位置資訊通知給監聽器,LocationListener就是一個系統服務呼叫應用層介面的例子,我們就研究一下LocationListener的實作方式,

我們先從LocationManager怎么注冊LocationListener開始研究:
frameworks/base/location/java/android/location/LocationManager.java

private final ILocationManager mService;
...
private void requestLocationUpdates(LocationRequest request, 
        LocationListener listener, Looper looper, PendingIntent intent) {
    ...
    // wrap the listener class
    ListenerTransport transport = wrapListener(listener, looper);
    try {
        mService.requestLocationUpdates(request, transport, 
                intent, packageName);
   } catch (RemoteException e) {
       Log.e(TAG, "RemoteException", e);
   }
}

可以看到LocationListener被重新封裝成了一個ListenerTransport,然后傳遞給了ILocationManager ,從前面的分析可以猜測到這個ILocationManager應該就是LocationManagerService的一個代理,那么ListenerTransport又是什么呢?搜索LocationManager.java可以找到:

private class ListenerTransport extends ILocationListener.Stub {
    ...
    @Override
    public void onLocationChanged(Location location) {
        ...
    }
}

原來是ILocationListener.Stub的一個繼承實作,那么ILocationListener應該就是一個AIDL介面定義:
frameworks/base/location/java/android/location/ILocationListener.aidl

oneway interface ILocationListener
{
    void onLocationChanged(in Location location);
    ...
}

而在LocationManagerService里只要呼叫ILocationListener的方法就可以將訊息傳遞給應用層的監聽:

mListener.onLocationChanged(new Location(location));

實作 System Service 的注意事項

  1. 注意防止阻塞
    應用層訪問系統服務提供的介面時會有兩種情況:

一種是應用呼叫端需要等待服務實作端處理完成,回傳處理結果,這樣如果服務端發生阻塞,那么應用端也會發生阻塞,因此在實作服務端的實作時要注意不要發生阻塞,

另一種是呼叫端不需要等待服務端回傳結果,呼叫完成后直接回傳void,這樣服務端發生阻塞不會影響到應用端,這樣的單向的介面在AIDL里定義時需要添加oneway關鍵字,如:

oneway void statusBarVisibilityChanged(int visibility);

對于需要在服務端呼叫,在應用端實作的介面,考慮到系統的穩定性以及安全性,一般都會設計成上面的第二種,即AIDL里所有的介面都是單向的,如上面的ILocationListener

oneway interface ILocationListener
  1. 注意多執行緒訪問

每個系統服務在系統行程中只有一個實體,而且應用中系統服務的代理也是單例的,而且應用端的訪問,在系統行程都是使用獨立的執行緒進行回應,所以訪問同一個系統服務的介面時必然會出現多個執行緒或者多個行程同時訪問的情況,為保證系統服務的執行緒安全,需要對系統服務的行程進行多執行緒訪問的保護,目前主要有兩種實作執行緒安全的方法:

一種是通過同步鎖機制,鎖住一個物件實體(一般是這個服務物件本身),這樣這個服務同一時間只能回應一個訪問請求,如LocationManagerService里:

public boolean callStatusChangedLocked(...) {
    ...
    synchronized (this) {
    ...
    }
}

另一種方法就是使用Handler機制,這種服務一般會創建一個單獨的執行緒,當有應用端訪問請求到來時會向服務執行緒的Handler里發送一個Message,利用單執行緒順序執行的特性,保證所有的訪問都按順序進行處理,但這種方法只適合單向的訪問,不適合需要回傳的雙向訪問,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/28359.html

標籤:Android

上一篇:Flutter 拖拽排序組件 ReorderableListView

下一篇:Android按鈕單擊事件的五種實作方式

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more