欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android集成Unity的兩種方案

 更新時間:2024年05月06日 08:40:15   作者:Stephen10086  
現(xiàn)在市面上的形形色色Android客戶端,為了更優(yōu)的用戶體驗,我們開發(fā)的上游產(chǎn)品和交互往往會在界面里設(shè)計很多動效,傳統(tǒng)的一頁頁的靜態(tài)展示頁面已經(jīng)不足以滿足用戶的審美需求了,本文將給大家分享Android集成Unity的兩種方案,感興趣的朋友可以參考下

?Android平臺常見動效

現(xiàn)在市面上的形形色色Android客戶端,為了更優(yōu)的用戶體驗,我們開發(fā)的上游產(chǎn)品和交互往往會在界面里設(shè)計很多動效。傳統(tǒng)的一頁頁的靜態(tài)展示頁面已經(jīng)不足以滿足用戶的審美需求了。

而動效的分類也是花樣百出的,以播放時機(jī)來說有點擊觸發(fā),打開頁面觸發(fā),還有可跟隨手指的交互持續(xù)觸發(fā)的等等。有時候一些和數(shù)據(jù)耦合性較大的動效甚至需要我們自己來手寫復(fù)雜的自定義View,比如曲線圖、圖表類型。

而我 日常碰到的大部分的動效需求,還是依賴UI設(shè)計的同時來制作提供的,像那些短時間單次的展示類動效,往往實現(xiàn)方式比較隨意,對資源的格式要求也不太嚴(yán)苛。一般有以下幾種方案:

幀動畫

在Android中,幀動畫是通過Drawable動畫實現(xiàn)的。你可以創(chuàng)建一個AnimationDrawable對象,然后在XML中定義一系列的幀(frames),每幀可以是一個Drawable資源。然后在代碼中啟動這個動畫。

以下是兩個簡單的例子:

1. 在res/drawable目錄下創(chuàng)建一個名為frame_animation.xml的文件,并定義動畫的幀:

<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"表示動畫會循環(huán)播放,如果設(shè)置為true則播放一次。android:duration表示每幀顯示的時間。

2. 在你的布局文件中(例如activity_main.xml),添加一個ImageView來展示動畫:

<ImageView

android:id="@+id/imageView"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:src="@drawable/frame_animation" />

然后在調(diào)用處啟動動畫:

ImageView imageView = (ImageView) findViewById(R.id.imageView);

// 獲取AnimationDrawable

final AnimationDrawable frameAnimation = (AnimationDrawable) imageView.getDrawable();

// 在UI線程之外啟動動畫

imageView.post(new Runnable() {

    @Override

    public void run() {

        frameAnimation.start();

    }

});

注意確保你的每個Drawable資源的尺寸是一致的,以便在動畫過程中保持幀的正確顯示。這樣就創(chuàng)建了一個簡單的幀動畫,當(dāng)Activity加載時,動畫會自動開始循環(huán)播放。

PAG動畫

pag相較于上面的幀動畫對性能更加友好。PAG是騰訊公司自主研發(fā)的一套完整動畫工作流解決方案。 PAG誕生于2016年,最初的原因是為了解決更為復(fù)雜的視頻編輯場景下動畫渲染問題,同時又覆蓋了UI動畫和直播場景,于2022年1月在Github開源。

其使用方法可以說相當(dāng)簡單,只需要先從github主頁確定版本,到gradle里引入依賴,

implementation 'com.tencent.tav:libpag:3.2.7.40'

然后在我們應(yīng)用的xml布局中放置pagView,沒有額外的屬性需要配置:

 <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動畫

這個單次動效的實現(xiàn)方案是最簡單的,不寫demo演示了,直接獲取mediaplayer實例,綁定surfaceView或者TextureView,再填文件,播放視頻即可。需要關(guān)注的是surfaceView播放視頻一開始可能會有黑屏問題,可以用靜態(tài)圖占位。

可交互的動效

顧名思義,這一類動效需要能跟隨用戶的操作而實時改變表現(xiàn)形式。最常見的就是吸頂動效,例如在一個列表滑動過程中,會監(jiān)聽列表的滑動距離,對界面頂部或者側(cè)邊的其他View位置和透明度,顏色等做動態(tài)設(shè)置。還有systemui的allapp界面的翻頁動畫,負(fù)一屏下拉的時候,根據(jù)距離對桌面背景做高斯模糊處理等等。

Kanzi動效

跟手可互動的動效,也不得不談kanzi動效。以下介紹來自百科與官網(wǎng):

Kanzi產(chǎn)品是行業(yè)領(lǐng)先的3D引擎和UI開發(fā)工具,支持高效率沉浸式3D效果,跨系統(tǒng)多屏互聯(lián)并能與安卓生態(tài)完美融合,已經(jīng)成為全球主流車廠智能座艙首選的UI開發(fā)工具和引擎。更新后的Kanzi架構(gòu)可與安卓操作系統(tǒng)、生態(tài)系統(tǒng)深度兼容。Kanzi可基于安卓的任何功能提供強(qiáng)大的圖形設(shè)計支持,確保高質(zhì)量的圖像效果。

對于Kanzi動效的集成使用方式,因為沒有自己從頭開始對接,我只按照順序一筆帶過,有不對的地方歡迎指正。首先我們集成kanzi運行所需的Runtime.aar,kanziJava支持庫aar,資源文件,資源列表的txt等等,還需要在gradle里寫明不可壓縮的文件類型,以防止無法加載資源。

在使用上,我們先在XML布局中聲明,同時通過屬性填入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)聽器來接收和上行下行信號的交互:

// 數(shù)據(jù)接口定義
public interface AndroidNotifyListener {
    void notifyDataChanged(String name, String value);

    void dataSourceFinish();
}

// 添加數(shù)據(jù)接收監(jiān)聽和下行通信
AndroidUtils.setListeners(this);
AndroidUtils.removeListeners(this);
AndroidUtils.setValue(SourceData.RightMidMove_up2down, y);

Unity動效

Unity的大名在游戲界可謂如雷貫耳,記得小時候玩的很多游戲的開屏界面即有一個大大的Unity字樣和圖標(biāo)。

以下介紹來自百科和官網(wǎng):Unity是實時3D互動內(nèi)容創(chuàng)作和運營平臺。包括游戲開發(fā)、美術(shù)、建筑、汽車設(shè)計、影視在內(nèi)的所有創(chuàng)作者,借助Unity將創(chuàng)意變成現(xiàn)實。Unity平臺提供一整套完善的軟件解決方案,可用于創(chuàng)作、運營和變現(xiàn)任何實時互動的2D和3D內(nèi)容,支持平臺包括手機(jī)、平板電腦、PC、游戲主機(jī)、增強(qiáng)現(xiàn)實和虛擬現(xiàn)實設(shè)備。Unity作為全球領(lǐng)先的 3D 引擎之一,團(tuán)結(jié)引擎可以為 3D HMI提供全棧支持。即為從概念設(shè)計到量產(chǎn)部署的整個 HMI 工作流程提供創(chuàng)意咨詢、性能調(diào)優(yōu)、項目開發(fā)等解決方案,從而為車載信息娛樂系統(tǒng)和智能駕駛座艙打造令人驚嘆的交互式體驗。

其實在第一版我們項目集成的是Kanzi方案,其性能表現(xiàn)較Unity要差一些,關(guān)鍵是項目推進(jìn)的過程中,對方工程師對動效樣式的優(yōu)化也達(dá)不到評測部門的要求,后來更新迭代我們就更換了Unity方案。而本文的重點也是在于Unity3D動效的使用,案例為車載IVI系統(tǒng)空調(diào)app的風(fēng)向調(diào)節(jié),交互邏輯比上面舉的例子更加復(fù)雜,需要實時跟手,在交互熱區(qū)范圍內(nèi)需要不斷變化動效形態(tài),并完成雙向通信,保證動效和車載信號的一致性。

Unity集成的兩種方案

前面做了這些動效的鋪墊,終于進(jìn)入正題了。本文暫時不深究Unity的渲染原理,只談集成和使用。

通信協(xié)議制定

第一步不是創(chuàng)建工程,而是要提前根據(jù)HMI的產(chǎn)品交互定義來指定和Unity之間的通信協(xié)議。有哪些功能是有開關(guān)的,需要調(diào)整哪些屬性??照{(diào)app里就涉及幾個出風(fēng)口的打開關(guān)閉,可以以0/1來區(qū)分。還有風(fēng)口的方向調(diào)節(jié),需要互傳x,y坐標(biāo)值。Android和Unity之間的是采用JSON字符串來通信的,對于JSON字符串的的打包與解包通過谷歌的Gson等三方庫來操作,相當(dāng)簡單。

而且,兩方通信鏈路和Unity的集成方式還有關(guān),像下面要談到的第一種進(jìn)程隔離方案,就是通過集成全量的Unity依賴包,利用其中的JNI接口來通信的,而Client/Server架構(gòu)就是通過Android的AIDL接口來和單獨的服務(wù)端進(jìn)程通信的。另外交互的車載信號鏈路方案涉及項目架構(gòu)機(jī)密,此處不作描述。

進(jìn)程隔離方案-UAAL(Render As Library)

這種方式集成的話,Unity會將渲染引擎,資源文件,和Android上層的通信代碼都打包導(dǎo)出到一個aar中,其體積隨動效的復(fù)雜程度而變化,同時會使集成方的apk包體積增加。而且項目里有多少方要使用Unity動效,就需要多少份的渲染引擎。這個方案由客戶端來負(fù)責(zé)Unity控件的創(chuàng)建銷毀,顯示隱藏,一般適用一對一,通信鏈路簡單的,即項目中可能只有一個模塊需要使用Unity動效的情況。在多模塊需要使用Unity的情況下,進(jìn)程隔離的方案對性能的占用也比較高。

上層使用到的控件——UnityPlayer,它是一個Unity自定義的FrameLayout,里面有他們自己實現(xiàn)的一系列添加view,顯示,和渲染邏輯。資源文件均存在于Unity打的依賴包中,對外不開放。

集成步驟

進(jìn)程隔離的集成方式如下:

第一步,將Unity提供的aar放置于libs文件夾中,并在gradle里添加其編譯引用。

implementation files('libs/UnityAnimation_0321V4.aar')

第二步,gradle中配置Unity所需的NDK版本,配置abifilters,設(shè)置要將哪些架構(gòu)的動態(tài)庫打包到apk中,對于車機(jī)項目來說只需要固定的某一種架構(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'
}


注意,我們還需要在項目的string.xml資源文件中添加Unity所需的一條String資源,否則Unity側(cè)會空指針。

 <string name="game_view_content_description">Game view</string>

第三步,將要顯示Unity動效的頁面Activity改為繼承自UnityPlayerActivity,Unity的核心顯示控件,UnityPlayer,它的創(chuàng)建銷毀,顯示隱藏,由這個UnityPlayerActivity來統(tǒng)一管理,項目中集成這個Activity的子類再將mUnityPlayer通過addView添加到自己的根布局ViewGroup中當(dāng)背景即可,而且可以在xml上面繼續(xù)增加其他View控件。

第四步,封裝Unity通信工具類,Android給Unity發(fā)消息可以直接通過UnityPlayer的sendMessage靜態(tài)方法,傳入Unity通信協(xié)議中指定的類名。

 UnityPlayer.UnitySendMessage(OBJ_NAME, METHOD_NAME, communicateMessage)

Unity使用C#開發(fā),其給Android上層發(fā)消息則是通過反射回調(diào)信號類里的方法實現(xiàn)的,所以我們最好將信號管理類做成單例的,并給其Unity留下一個方法或者成員,可以拿到我們類的實例,順利反射回調(diào)。我這里使用的是一個Kotlin類聲明,并對外暴露一個公開的unityInstance成員。而這個方法onReceiveMsgFromUnity,即是Unity的反射調(diào)用,我們在其中進(jìn)行信號的解析,并傳到View中去,注意這個方法不是在主線程中反射的,所以后面需要優(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)
            }
        }
    }
}

信號類UnityMessageHelper的優(yōu)化

由于我們的目標(biāo)工程是空調(diào)app,在用戶調(diào)節(jié)風(fēng)向時的回調(diào)頻率相當(dāng)高,而自動掃風(fēng)模式下,底層上傳的數(shù)據(jù)頻率也相當(dāng)高,所以不適合到主線程中操作這么多的數(shù)據(jù),我們用協(xié)程,配合Default調(diào)度器來處理這種CPU密集型的任務(wù)。兩條鏈路,用戶手指的拖動操作時,Unity反射回調(diào)的線程本身都是工作線程了,所以我們在使用自定義的接口回調(diào)到View類的時候,使用MainScope.launch包一層,確保是到主線程更新我們的UI。而自動掃風(fēng)模式從域控制器接收到風(fēng)口點擊的坐標(biāo)值時,我們拿到數(shù)據(jù)后給Unity下發(fā)信號,更新動效的指向位置??梢允褂脜f(xié)程上下文切換,withContext(Dispatcher.Default)將其切到工作線程里發(fā)送給Unity。

遇到的問題

Unity方給的aar里的基類Activity適用與絕大多數(shù)的普通應(yīng)用,但是我這里空調(diào)app的定位是一個懸浮的臨時面板,其實我的工程里壓根就沒有Activity,而是采用WindowManager來添加高層級窗口的View的形式來展示界面。

這個時候我們用不上他們定義的UnityPlayerActivity,只能使用原生Raw的UnityPlayer,自己管理其創(chuàng)建,銷毀,resume和pause。這里需要注意的是,UnityPlayer的創(chuàng)建需要傳一個Context上下文,而應(yīng)用里又沒有Activity類型的Context,故只能使用非Activity類型的Context,而且實踐中發(fā)現(xiàn),這個UnityPlayer的實例必須是我們的應(yīng)用拿到可用的窗口句柄之后,才能被成功創(chuàng)建,否則就會報錯。

所以正確的創(chuàng)建與初始化順序是先使用WindowManager添加一個xml布局inflate來的ViewGroup,在其onAttachToWindow的方法回調(diào)之后,再創(chuàng)建UnityPlayer的實例,并添加到這個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有一個無法解決的黑屏問題,因為Unity的渲染加載至少都需要4,5秒,期間我們只能在更上層的View里設(shè)置靜態(tài)背景圖覆蓋上去,等Unity加載完畢,發(fā)送ready的回調(diào)之后,我們移除掉這個占位的靜態(tài)圖,展示Unity動效的界面。這也是進(jìn)程隔離的方案的一個很棘手的問題。我的解決方案是在開機(jī)的時候往屏幕外添加一個View專門來初始化加載Unity,加載完畢后,再將UnityPlayer給從里面remove掉,重新添加到實際的要展示的窗口中去,這樣打開界面的時候可以略去加載的耗時,稍微減少頁面僵直的時間。

單進(jìn)程-URAS(Render As Service)

Unity Rendering as Service(簡稱URAS) 的渲染方案是團(tuán)結(jié)引擎特有的,無需在多個安卓應(yīng)用中集成多個Unity 3D player,而是后臺運行,前端應(yīng)用可直接調(diào)用,節(jié)省系統(tǒng)資源,更適合多應(yīng)用動效一鏡到底的設(shè)計。

相較進(jìn)程隔離方案的優(yōu)勢

這個方案是在UAAL方案的基礎(chǔ)上升級的,所以有一些前期工作是重復(fù)的,不作重復(fù)的闡述。

它是將要顯示的幾個Unity引擎都打包到同一個Server服務(wù)端去統(tǒng)一管控。其實服務(wù)端的apk打包也是拿到Unity提供的服務(wù)端AAR打進(jìn)一個空工程,內(nèi)部邏輯也隱藏到了AAR中。服務(wù)端和客戶端的通信采用我們熟知的AIDL接口來實現(xiàn)。而且這個服務(wù)端我們需要設(shè)置為persistent應(yīng)用,使其能開機(jī)自啟,自動執(zhí)行渲染等工作,其他應(yīng)用有顯示需求可以秒開,并且長時間不顯示也不會自己回收資源了,客戶端的黑屏問題也可以解決了。

相比于UAAL方案,客戶端需要集成的是一個體積很小的Client.aar,對于客戶端apk的體積控制是有優(yōu)勢的。

集成與使用方式

我們只需要在gradle里引入這個客戶端aar。在gradle sync之后,將遠(yuǎn)程的UnityView添加到自己的布局中去,配置好display參數(shù)(用來給服務(wù)端區(qū)分是哪個引擎的內(nèi)容),并指定服務(wù)端的包名。承載的View類型有SurfaceView和TextureView兩種,而我的應(yīng)用界面因為是一個懸浮窗口,設(shè)計有進(jìn)出場的漸隱漸出動效,而SurfaceView不可以線性地設(shè)置alpha動畫,所以選取TextureView來當(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的啟動,添加服務(wù)連接的回調(diào),消息回調(diào)。由于服務(wù)端為若干個Client的公共引擎,所以連resume和pause都不需要處理,因為這兩個操作會對所有的客戶端都生效。我們只需要確保啟動服務(wù),并使用正確的display即可,面板退到后臺可以使用setVisbility來控制其顯示隱藏。除此之外,我們的通信工具類,UnityMessageHelper還需要實現(xiàn)兩個接口,一個服務(wù)連接狀態(tài)接口,一個業(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)消息的解析

    }

}

同時Android給Unity發(fā)信號的方法也從UnityPlayer的靜態(tài)方法換成了這個服務(wù)實例的方法調(diào)用:

  unityRenderService.c2sSendMessage(OBJ_NAME, METHOD_NAME, communicateMessage)

接收Unity的回調(diào)消息也換成了addSendMessageCallback里設(shè)置的回調(diào)方法。

可以說URAS方案由于其統(tǒng)一管控,一對多的特點,在性能和客戶端的易集成性方面,是優(yōu)于UAAL方案的??梢詮募軜?gòu)層面上,聯(lián)動更多的動效使用模塊,實現(xiàn)一鏡到底的絲滑轉(zhuǎn)場。

結(jié)語

以上就是本文對于Android常用幾種動效的闡述以及對兩種常見的Unity集成方案記錄。后續(xù)我們除了在最表面的使用層面上,還可以進(jìn)一步挖掘其原理,甚至自己使用Unity的開發(fā)工具,自己體驗一把動效的制作和接入,做到全鏈路知己知彼,才可以更高效的集成Unity為自己的應(yīng)用錦上添花。

到此這篇關(guān)于Android集成Unity的兩種方案的文章就介紹到這了,更多相關(guān)Android集成Unity內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論