Android集成Unity的兩種方案
?Android平臺(tái)常見(jiàn)動(dòng)效
現(xiàn)在市面上的形形色色Android客戶端,為了更優(yōu)的用戶體驗(yàn),我們開(kāi)發(fā)的上游產(chǎn)品和交互往往會(huì)在界面里設(shè)計(jì)很多動(dòng)效。傳統(tǒng)的一頁(yè)頁(yè)的靜態(tài)展示頁(yè)面已經(jīng)不足以滿足用戶的審美需求了。
而動(dòng)效的分類也是花樣百出的,以播放時(shí)機(jī)來(lái)說(shuō)有點(diǎn)擊觸發(fā),打開(kāi)頁(yè)面觸發(fā),還有可跟隨手指的交互持續(xù)觸發(fā)的等等。有時(shí)候一些和數(shù)據(jù)耦合性較大的動(dòng)效甚至需要我們自己來(lái)手寫(xiě)復(fù)雜的自定義View,比如曲線圖、圖表類型。
而我 日常碰到的大部分的動(dòng)效需求,還是依賴UI設(shè)計(jì)的同時(shí)來(lái)制作提供的,像那些短時(shí)間單次的展示類動(dòng)效,往往實(shí)現(xiàn)方式比較隨意,對(duì)資源的格式要求也不太嚴(yán)苛。一般有以下幾種方案:
幀動(dòng)畫(huà)
在Android中,幀動(dòng)畫(huà)是通過(guò)Drawable動(dòng)畫(huà)實(shí)現(xiàn)的。你可以創(chuàng)建一個(gè)AnimationDrawable對(duì)象,然后在XML中定義一系列的幀(frames),每幀可以是一個(gè)Drawable資源。然后在代碼中啟動(dòng)這個(gè)動(dòng)畫(huà)。
以下是兩個(gè)簡(jiǎn)單的例子:
1. 在res/drawable目錄下創(chuàng)建一個(gè)名為frame_animation.xml的文件,并定義動(dòng)畫(huà)的幀:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@drawable/frame1" android:duration="100" /> <item android:drawable="@drawable/frame2" android:duration="100" /> <item android:drawable="@drawable/frame3" android:duration="100" /> <!-- 更多幀 --> </animation-list>
這里android:oneshot="false"表示動(dòng)畫(huà)會(huì)循環(huán)播放,如果設(shè)置為true則播放一次。android:duration表示每幀顯示的時(shí)間。
2. 在你的布局文件中(例如activity_main.xml),添加一個(gè)ImageView來(lái)展示動(dòng)畫(huà):
<ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/frame_animation" />
然后在調(diào)用處啟動(dòng)動(dòng)畫(huà):
ImageView imageView = (ImageView) findViewById(R.id.imageView); // 獲取AnimationDrawable final AnimationDrawable frameAnimation = (AnimationDrawable) imageView.getDrawable(); // 在UI線程之外啟動(dòng)動(dòng)畫(huà) imageView.post(new Runnable() { @Override public void run() { frameAnimation.start(); } });
注意確保你的每個(gè)Drawable資源的尺寸是一致的,以便在動(dòng)畫(huà)過(guò)程中保持幀的正確顯示。這樣就創(chuàng)建了一個(gè)簡(jiǎn)單的幀動(dòng)畫(huà),當(dāng)Activity加載時(shí),動(dòng)畫(huà)會(huì)自動(dòng)開(kāi)始循環(huán)播放。
PAG動(dòng)畫(huà)
pag相較于上面的幀動(dòng)畫(huà)對(duì)性能更加友好。PAG是騰訊公司自主研發(fā)的一套完整動(dòng)畫(huà)工作流解決方案。 PAG誕生于2016年,最初的原因是為了解決更為復(fù)雜的視頻編輯場(chǎng)景下動(dòng)畫(huà)渲染問(wèn)題,同時(shí)又覆蓋了UI動(dòng)畫(huà)和直播場(chǎng)景,于2022年1月在Github開(kāi)源。
其使用方法可以說(shuō)相當(dāng)簡(jiǎn)單,只需要先從github主頁(yè)確定版本,到gradle里引入依賴,
implementation 'com.tencent.tav:libpag:3.2.7.40'
然后在我們應(yīng)用的xml布局中放置pagView,沒(méi)有額外的屬性需要配置:
<org.libpag.PAGView android:id="@+id/pagview" android:layout_width="@dimen/dp_1190" android:layout_height="@dimen/dp_1110" android:layout_marginTop="@dimen/dp_290" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" />
最后在代碼里設(shè)置其文件源,循環(huán)方式,調(diào)用播放即可
@Override protected void onCreate(Bundle savedInstanceState) { LogUtils.d("AirConditioner Start"); super.onCreate(savedInstanceState); setContentView(R.layout.instance); PAGView pagView = findViewById(R.id.pagview); PAGFile pf = PAGFile.Load(getContext().getAssets(), fileName); pagView.setComposition(pf); pagView.setRepeatCount(-1); pagView.play(); }
MP4動(dòng)畫(huà)
這個(gè)單次動(dòng)效的實(shí)現(xiàn)方案是最簡(jiǎn)單的,不寫(xiě)demo演示了,直接獲取mediaplayer實(shí)例,綁定surfaceView或者TextureView,再填文件,播放視頻即可。需要關(guān)注的是surfaceView播放視頻一開(kāi)始可能會(huì)有黑屏問(wèn)題,可以用靜態(tài)圖占位。
可交互的動(dòng)效
顧名思義,這一類動(dòng)效需要能跟隨用戶的操作而實(shí)時(shí)改變表現(xiàn)形式。最常見(jiàn)的就是吸頂動(dòng)效,例如在一個(gè)列表滑動(dòng)過(guò)程中,會(huì)監(jiān)聽(tīng)列表的滑動(dòng)距離,對(duì)界面頂部或者側(cè)邊的其他View位置和透明度,顏色等做動(dòng)態(tài)設(shè)置。還有systemui的allapp界面的翻頁(yè)動(dòng)畫(huà),負(fù)一屏下拉的時(shí)候,根據(jù)距離對(duì)桌面背景做高斯模糊處理等等。
Kanzi動(dòng)效
跟手可互動(dòng)的動(dòng)效,也不得不談kanzi動(dòng)效。以下介紹來(lái)自百科與官網(wǎng):
Kanzi產(chǎn)品是行業(yè)領(lǐng)先的3D引擎和UI開(kāi)發(fā)工具,支持高效率沉浸式3D效果,跨系統(tǒng)多屏互聯(lián)并能與安卓生態(tài)完美融合,已經(jīng)成為全球主流車廠智能座艙首選的UI開(kāi)發(fā)工具和引擎。更新后的Kanzi架構(gòu)可與安卓操作系統(tǒng)、生態(tài)系統(tǒng)深度兼容。Kanzi可基于安卓的任何功能提供強(qiáng)大的圖形設(shè)計(jì)支持,確保高質(zhì)量的圖像效果。
對(duì)于Kanzi動(dòng)效的集成使用方式,因?yàn)闆](méi)有自己從頭開(kāi)始對(duì)接,我只按照順序一筆帶過(guò),有不對(duì)的地方歡迎指正。首先我們集成kanzi運(yùn)行所需的Runtime.aar,kanziJava支持庫(kù)aar,資源文件,資源列表的txt等等,還需要在gradle里寫(xiě)明不可壓縮的文件類型,以防止無(wú)法加載資源。
在使用上,我們先在XML布局中聲明,同時(shí)通過(guò)屬性填入asstes里的資源名,和資源文件綁定:
<com.rightware.kanzi.KanziTextureView android:id="@+id/tx_KanziSurfaceView" android:layout_width="@dimen/dp_2560" android:layout_height="@dimen/dp_1190" app:clearColor="@android:color/transparent" app:kzbPathList="climate.kzb" app:layout_constraintTop_toTopOf="parent" app:name="climate" app:startupPrefabUrl="kzb://climate/StartupPrefab" tools:ignore="MissingConstraints" />
在代碼里我們需要設(shè)置通信的工具類,在里面添加監(jiān)聽(tīng)器來(lái)接收和上行下行信號(hào)的交互:
// 數(shù)據(jù)接口定義 public interface AndroidNotifyListener { void notifyDataChanged(String name, String value); void dataSourceFinish(); } // 添加數(shù)據(jù)接收監(jiān)聽(tīng)和下行通信 AndroidUtils.setListeners(this); AndroidUtils.removeListeners(this); AndroidUtils.setValue(SourceData.RightMidMove_up2down, y);
Unity動(dòng)效
Unity的大名在游戲界可謂如雷貫耳,記得小時(shí)候玩的很多游戲的開(kāi)屏界面即有一個(gè)大大的Unity字樣和圖標(biāo)。
以下介紹來(lái)自百科和官網(wǎng):Unity是實(shí)時(shí)3D互動(dòng)內(nèi)容創(chuàng)作和運(yùn)營(yíng)平臺(tái)。包括游戲開(kāi)發(fā)、美術(shù)、建筑、汽車設(shè)計(jì)、影視在內(nèi)的所有創(chuàng)作者,借助Unity將創(chuàng)意變成現(xiàn)實(shí)。Unity平臺(tái)提供一整套完善的軟件解決方案,可用于創(chuàng)作、運(yùn)營(yíng)和變現(xiàn)任何實(shí)時(shí)互動(dòng)的2D和3D內(nèi)容,支持平臺(tái)包括手機(jī)、平板電腦、PC、游戲主機(jī)、增強(qiáng)現(xiàn)實(shí)和虛擬現(xiàn)實(shí)設(shè)備。Unity作為全球領(lǐng)先的 3D 引擎之一,團(tuán)結(jié)引擎可以為 3D HMI提供全棧支持。即為從概念設(shè)計(jì)到量產(chǎn)部署的整個(gè) HMI 工作流程提供創(chuàng)意咨詢、性能調(diào)優(yōu)、項(xiàng)目開(kāi)發(fā)等解決方案,從而為車載信息娛樂(lè)系統(tǒng)和智能駕駛座艙打造令人驚嘆的交互式體驗(yàn)。
其實(shí)在第一版我們項(xiàng)目集成的是Kanzi方案,其性能表現(xiàn)較Unity要差一些,關(guān)鍵是項(xiàng)目推進(jìn)的過(guò)程中,對(duì)方工程師對(duì)動(dòng)效樣式的優(yōu)化也達(dá)不到評(píng)測(cè)部門的要求,后來(lái)更新迭代我們就更換了Unity方案。而本文的重點(diǎn)也是在于Unity3D動(dòng)效的使用,案例為車載IVI系統(tǒng)空調(diào)app的風(fēng)向調(diào)節(jié),交互邏輯比上面舉的例子更加復(fù)雜,需要實(shí)時(shí)跟手,在交互熱區(qū)范圍內(nèi)需要不斷變化動(dòng)效形態(tài),并完成雙向通信,保證動(dòng)效和車載信號(hào)的一致性。
Unity集成的兩種方案
前面做了這些動(dòng)效的鋪墊,終于進(jìn)入正題了。本文暫時(shí)不深究Unity的渲染原理,只談集成和使用。
通信協(xié)議制定
第一步不是創(chuàng)建工程,而是要提前根據(jù)HMI的產(chǎn)品交互定義來(lái)指定和Unity之間的通信協(xié)議。有哪些功能是有開(kāi)關(guān)的,需要調(diào)整哪些屬性??照{(diào)app里就涉及幾個(gè)出風(fēng)口的打開(kāi)關(guān)閉,可以以0/1來(lái)區(qū)分。還有風(fēng)口的方向調(diào)節(jié),需要互傳x,y坐標(biāo)值。Android和Unity之間的是采用JSON字符串來(lái)通信的,對(duì)于JSON字符串的的打包與解包通過(guò)谷歌的Gson等三方庫(kù)來(lái)操作,相當(dāng)簡(jiǎn)單。
而且,兩方通信鏈路和Unity的集成方式還有關(guān),像下面要談到的第一種進(jìn)程隔離方案,就是通過(guò)集成全量的Unity依賴包,利用其中的JNI接口來(lái)通信的,而Client/Server架構(gòu)就是通過(guò)Android的AIDL接口來(lái)和單獨(dú)的服務(wù)端進(jìn)程通信的。另外交互的車載信號(hào)鏈路方案涉及項(xiàng)目架構(gòu)機(jī)密,此處不作描述。
進(jìn)程隔離方案-UAAL(Render As Library)
這種方式集成的話,Unity會(huì)將渲染引擎,資源文件,和Android上層的通信代碼都打包導(dǎo)出到一個(gè)aar中,其體積隨動(dòng)效的復(fù)雜程度而變化,同時(shí)會(huì)使集成方的apk包體積增加。而且項(xiàng)目里有多少方要使用Unity動(dòng)效,就需要多少份的渲染引擎。這個(gè)方案由客戶端來(lái)負(fù)責(zé)Unity控件的創(chuàng)建銷毀,顯示隱藏,一般適用一對(duì)一,通信鏈路簡(jiǎn)單的,即項(xiàng)目中可能只有一個(gè)模塊需要使用Unity動(dòng)效的情況。在多模塊需要使用Unity的情況下,進(jìn)程隔離的方案對(duì)性能的占用也比較高。
上層使用到的控件——UnityPlayer,它是一個(gè)Unity自定義的FrameLayout,里面有他們自己實(shí)現(xiàn)的一系列添加view,顯示,和渲染邏輯。資源文件均存在于Unity打的依賴包中,對(duì)外不開(kāi)放。
集成步驟
進(jìn)程隔離的集成方式如下:
第一步,將Unity提供的aar放置于libs文件夾中,并在gradle里添加其編譯引用。
implementation files('libs/UnityAnimation_0321V4.aar')
第二步,gradle中配置Unity所需的NDK版本,配置abifilters,設(shè)置要將哪些架構(gòu)的動(dòng)態(tài)庫(kù)打包到apk中,對(duì)于車機(jī)項(xiàng)目來(lái)說(shuō)只需要固定的某一種架構(gòu)即可。還有設(shè)置不壓縮的文件類型,使Unity可以順利找到資源使用。
ndkVersion "23.1.7779620" aaptOptions { noCompress = ['.tj3d', '.ress', '.resource', '.obb', '.bundle', '.tuanjieexp', 'global-metadata.so'] + tuanjieStreamingAssets.tokenize(', ') ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~" } ndk{ abiFilters 'arm64-v8a' }
注意,我們還需要在項(xiàng)目的string.xml資源文件中添加Unity所需的一條String資源,否則Unity側(cè)會(huì)空指針。
<string name="game_view_content_description">Game view</string>
第三步,將要顯示Unity動(dòng)效的頁(yè)面Activity改為繼承自UnityPlayerActivity,Unity的核心顯示控件,UnityPlayer,它的創(chuàng)建銷毀,顯示隱藏,由這個(gè)UnityPlayerActivity來(lái)統(tǒng)一管理,項(xiàng)目中集成這個(gè)Activity的子類再將mUnityPlayer通過(guò)addView添加到自己的根布局ViewGroup中當(dāng)背景即可,而且可以在xml上面繼續(xù)增加其他View控件。
第四步,封裝Unity通信工具類,Android給Unity發(fā)消息可以直接通過(guò)UnityPlayer的sendMessage靜態(tài)方法,傳入U(xiǎn)nity通信協(xié)議中指定的類名。
UnityPlayer.UnitySendMessage(OBJ_NAME, METHOD_NAME, communicateMessage)
Unity使用C#開(kāi)發(fā),其給Android上層發(fā)消息則是通過(guò)反射回調(diào)信號(hào)類里的方法實(shí)現(xiàn)的,所以我們最好將信號(hào)管理類做成單例的,并給其Unity留下一個(gè)方法或者成員,可以拿到我們類的實(shí)例,順利反射回調(diào)。我這里使用的是一個(gè)Kotlin類聲明,并對(duì)外暴露一個(gè)公開(kāi)的unityInstance成員。而這個(gè)方法onReceiveMsgFromUnity,即是Unity的反射調(diào)用,我們?cè)谄渲羞M(jìn)行信號(hào)的解析,并傳到View中去,注意這個(gè)方法不是在主線程中反射的,所以后面需要優(yōu)化一波。
object UnityMessageHelper { val unityInstance = this // Unity給Android的消息回調(diào) fun onReceiveMsgFromUnity(msg: String) { LogUtils.d(TAG, "onReceiveMsgFromUnity: $msg") if (listenerList.size > 0) { listenerList.forEach { it.onReceiveUnityMessage(msg) } } } }
信號(hào)類UnityMessageHelper的優(yōu)化
由于我們的目標(biāo)工程是空調(diào)app,在用戶調(diào)節(jié)風(fēng)向時(shí)的回調(diào)頻率相當(dāng)高,而自動(dòng)掃風(fēng)模式下,底層上傳的數(shù)據(jù)頻率也相當(dāng)高,所以不適合到主線程中操作這么多的數(shù)據(jù),我們用協(xié)程,配合Default調(diào)度器來(lái)處理這種CPU密集型的任務(wù)。兩條鏈路,用戶手指的拖動(dòng)操作時(shí),Unity反射回調(diào)的線程本身都是工作線程了,所以我們?cè)谑褂米远x的接口回調(diào)到View類的時(shí)候,使用MainScope.launch包一層,確保是到主線程更新我們的UI。而自動(dòng)掃風(fēng)模式從域控制器接收到風(fēng)口點(diǎn)擊的坐標(biāo)值時(shí),我們拿到數(shù)據(jù)后給Unity下發(fā)信號(hào),更新動(dòng)效的指向位置??梢允褂脜f(xié)程上下文切換,withContext(Dispatcher.Default)將其切到工作線程里發(fā)送給Unity。
遇到的問(wèn)題
Unity方給的aar里的基類Activity適用與絕大多數(shù)的普通應(yīng)用,但是我這里空調(diào)app的定位是一個(gè)懸浮的臨時(shí)面板,其實(shí)我的工程里壓根就沒(méi)有Activity,而是采用WindowManager來(lái)添加高層級(jí)窗口的View的形式來(lái)展示界面。
這個(gè)時(shí)候我們用不上他們定義的UnityPlayerActivity,只能使用原生Raw的UnityPlayer,自己管理其創(chuàng)建,銷毀,resume和pause。這里需要注意的是,UnityPlayer的創(chuàng)建需要傳一個(gè)Context上下文,而應(yīng)用里又沒(méi)有Activity類型的Context,故只能使用非Activity類型的Context,而且實(shí)踐中發(fā)現(xiàn),這個(gè)UnityPlayer的實(shí)例必須是我們的應(yīng)用拿到可用的窗口句柄之后,才能被成功創(chuàng)建,否則就會(huì)報(bào)錯(cuò)。
所以正確的創(chuàng)建與初始化順序是先使用WindowManager添加一個(gè)xml布局inflate來(lái)的ViewGroup,在其onAttachToWindow的方法回調(diào)之后,再創(chuàng)建UnityPlayer的實(shí)例,并添加到這個(gè)ViewGroup的布局中去,調(diào)用其resume方法。
public void initUnity() { if (mUnityPlayer == null) { LogUtils.i(TAG, "initUnity"); unityInitView = (LinearLayout) LayoutInflater.from(mContext).inflate(R.layout.layout_unity_init, null); unityInitView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(@NonNull View v) { LogUtils.w(TAG, "unityInitView onViewAttachedToWindow"); mUnityPlayer = new UnityPlayer(mContext); unityInitView.addView(mUnityPlayer); mUnityPlayer.requestFocus(); mUnityPlayer.resume(); mUnityPlayer.windowFocusChanged(true); } @Override public void onViewDetachedFromWindow(@NonNull View v) { LogUtils.w(TAG, "unityInitView onViewDetachedFromWindow"); // Unityplayer已經(jīng)成功移除,通知airView將player添加進(jìn)去 airConditionerView.addUnityToAirView(); } }); mWindowManager.addView(unityInitView, initUnityWindowParams()); } }
注意,這樣添加的UnityPlayer有一個(gè)無(wú)法解決的黑屏問(wèn)題,因?yàn)閁nity的渲染加載至少都需要4,5秒,期間我們只能在更上層的View里設(shè)置靜態(tài)背景圖覆蓋上去,等Unity加載完畢,發(fā)送ready的回調(diào)之后,我們移除掉這個(gè)占位的靜態(tài)圖,展示Unity動(dòng)效的界面。這也是進(jìn)程隔離的方案的一個(gè)很棘手的問(wèn)題。我的解決方案是在開(kāi)機(jī)的時(shí)候往屏幕外添加一個(gè)View專門來(lái)初始化加載Unity,加載完畢后,再將UnityPlayer給從里面remove掉,重新添加到實(shí)際的要展示的窗口中去,這樣打開(kāi)界面的時(shí)候可以略去加載的耗時(shí),稍微減少頁(yè)面僵直的時(shí)間。
單進(jìn)程-URAS(Render As Service)
Unity Rendering as Service(簡(jiǎn)稱URAS) 的渲染方案是團(tuán)結(jié)引擎特有的,無(wú)需在多個(gè)安卓應(yīng)用中集成多個(gè)Unity 3D player,而是后臺(tái)運(yùn)行,前端應(yīng)用可直接調(diào)用,節(jié)省系統(tǒng)資源,更適合多應(yīng)用動(dòng)效一鏡到底的設(shè)計(jì)。
相較進(jìn)程隔離方案的優(yōu)勢(shì)
這個(gè)方案是在UAAL方案的基礎(chǔ)上升級(jí)的,所以有一些前期工作是重復(fù)的,不作重復(fù)的闡述。
它是將要顯示的幾個(gè)Unity引擎都打包到同一個(gè)Server服務(wù)端去統(tǒng)一管控。其實(shí)服務(wù)端的apk打包也是拿到Unity提供的服務(wù)端AAR打進(jìn)一個(gè)空工程,內(nèi)部邏輯也隱藏到了AAR中。服務(wù)端和客戶端的通信采用我們熟知的AIDL接口來(lái)實(shí)現(xiàn)。而且這個(gè)服務(wù)端我們需要設(shè)置為persistent應(yīng)用,使其能開(kāi)機(jī)自啟,自動(dòng)執(zhí)行渲染等工作,其他應(yīng)用有顯示需求可以秒開(kāi),并且長(zhǎng)時(shí)間不顯示也不會(huì)自己回收資源了,客戶端的黑屏問(wèn)題也可以解決了。
相比于UAAL方案,客戶端需要集成的是一個(gè)體積很小的Client.aar,對(duì)于客戶端apk的體積控制是有優(yōu)勢(shì)的。
集成與使用方式
我們只需要在gradle里引入這個(gè)客戶端aar。在gradle sync之后,將遠(yuǎn)程的UnityView添加到自己的布局中去,配置好display參數(shù)(用來(lái)給服務(wù)端區(qū)分是哪個(gè)引擎的內(nèi)容),并指定服務(wù)端的包名。承載的View類型有SurfaceView和TextureView兩種,而我的應(yīng)用界面因?yàn)槭且粋€(gè)懸浮窗口,設(shè)計(jì)有進(jìn)出場(chǎng)的漸隱漸出動(dòng)效,而SurfaceView不可以線性地設(shè)置alpha動(dòng)畫(huà),所以選取TextureView來(lái)當(dāng)作容器。
<com.unity3d.renderservice.client.TuanjieView android:id="@+id/unityview" android:layout_width="match_parent" android:layout_height="match_parent" app:tuanjieDisplay="2" app:tuanjieServicePkgName="com.tuanjie.renderservice" app:tuanjieViewType="TextureView" />
剩余的代碼邏輯僅僅是服務(wù)端Service的啟動(dòng),添加服務(wù)連接的回調(diào),消息回調(diào)。由于服務(wù)端為若干個(gè)Client的公共引擎,所以連resume和pause都不需要處理,因?yàn)檫@兩個(gè)操作會(huì)對(duì)所有的客戶端都生效。我們只需要確保啟動(dòng)服務(wù),并使用正確的display即可,面板退到后臺(tái)可以使用setVisbility來(lái)控制其顯示隱藏。除此之外,我們的通信工具類,UnityMessageHelper還需要實(shí)現(xiàn)兩個(gè)接口,一個(gè)服務(wù)連接狀態(tài)接口,一個(gè)業(yè)務(wù)數(shù)據(jù)的消息回調(diào)接口,代碼如下:
object UnityMessageHelper : TuanjieRenderService.Callback, SendMessageCallback { fun initUnityService() { LogUtils.i(TAG, "initUnityService") unityRenderService = TuanjieRenderService.init(appContext,TUANJIE_PACKAGENAME).apply { enableAutoReconnect = true addCallback(this@UnityMessageHelper) addSendMessageCallback(this@UnityMessageHelper) ensureStarted() } } override fun onServiceConnected() { LogUtils.w(TAG, "onUnityRenderServiceConnected") } override fun onServiceDisconnected() { LogUtils.w(TAG, "onUnityRenderServiceDisConnected") messageScope.launch { delay(500L) initUnityService() unityRenderService.resume() } } override fun onServiceStartRenderView(p0: Int) { LogUtils.i(TAG, "onServiceStartRenderView") } override fun onClientRecvMessage(message: String?) = null // 服務(wù)端的消息回調(diào) override fun onClientRecvMessageWithNoRet(msg: String?) { // 回調(diào)消息的解析 } }
同時(shí)Android給Unity發(fā)信號(hào)的方法也從UnityPlayer的靜態(tài)方法換成了這個(gè)服務(wù)實(shí)例的方法調(diào)用:
unityRenderService.c2sSendMessage(OBJ_NAME, METHOD_NAME, communicateMessage)
接收Unity的回調(diào)消息也換成了addSendMessageCallback里設(shè)置的回調(diào)方法。
可以說(shuō)URAS方案由于其統(tǒng)一管控,一對(duì)多的特點(diǎn),在性能和客戶端的易集成性方面,是優(yōu)于UAAL方案的??梢詮募軜?gòu)層面上,聯(lián)動(dòng)更多的動(dòng)效使用模塊,實(shí)現(xiàn)一鏡到底的絲滑轉(zhuǎn)場(chǎng)。
結(jié)語(yǔ)
以上就是本文對(duì)于Android常用幾種動(dòng)效的闡述以及對(duì)兩種常見(jiàn)的Unity集成方案記錄。后續(xù)我們除了在最表面的使用層面上,還可以進(jìn)一步挖掘其原理,甚至自己使用Unity的開(kāi)發(fā)工具,自己體驗(yàn)一把動(dòng)效的制作和接入,做到全鏈路知己知彼,才可以更高效的集成Unity為自己的應(yīng)用錦上添花。
到此這篇關(guān)于Android集成Unity的兩種方案的文章就介紹到這了,更多相關(guān)Android集成Unity內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android自定義ViewGroup實(shí)現(xiàn)標(biāo)簽浮動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了Android自定義ViewGroup實(shí)現(xiàn)標(biāo)簽浮動(dòng)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-06-06解決Android Studio日志太長(zhǎng)或滾動(dòng)太快問(wèn)題
這篇文章主要介紹了解決Android Studio日志太長(zhǎng)或滾動(dòng)太快問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04OpenHarmony實(shí)現(xiàn)屏幕亮度動(dòng)態(tài)調(diào)節(jié)方法詳解
大家在拿到dayu之后,都吐槽說(shuō),會(huì)經(jīng)常熄屏,不利于調(diào)試,那么有沒(méi)有一種辦法,可以讓app不熄屏呢,答案是有的,今天我們就來(lái)揭秘一下,如何控制屏幕亮度2022-11-11FloatingActionButton增強(qiáng)版一個(gè)按鈕跳出多個(gè)按鈕第三方開(kāi)源之FloatingActionButton
這篇文章主要介紹了FloatingActionButton增強(qiáng)版一個(gè)按鈕跳出多個(gè)按鈕第三方開(kāi)源之FloatingActionButton 的相關(guān)資料,需要的朋友可以參考下2015-12-12Android6.0指紋識(shí)別開(kāi)發(fā)實(shí)例詳解
這篇文章主要介紹了Android6.0指紋識(shí)別開(kāi)發(fā)實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-04-04Android 關(guān)于“NetworkOnMainThreadException”問(wèn)題的原因分析及解決辦法
這篇文章主要介紹了Android 關(guān)于“NetworkOnMainThreadException”的相關(guān)知識(shí),本文介紹的非常詳細(xì),具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)吧2016-02-02快速了解Android?Room使用細(xì)則進(jìn)階
這篇文章主要為大家介紹了快速了解Android?Room使用細(xì)則進(jìn)階,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03