主頁 > 移動端開發 > 壁紙服務的啟動程序

壁紙服務的啟動程序

2021-01-25 11:29:52 移動端開發

文章目錄

    • 壁紙基礎
    • 壁紙服務的兩種啟動場景
      • 非首次重啟壁紙服務啟動流程
      • 手動切換時壁紙服務的啟動流程
    • 壁紙服務啟動程序
      • 1.校驗是否是壁紙服務
      • 2.系結壁紙服務
      • 3.引擎的創建和初始化
    • 壁紙服務的啟動流程總結

壁紙基礎

android中的壁紙分為動態壁紙和靜態壁紙兩種,兩種型別的壁紙都以Service的型別運行在系統后臺,

  • 靜態壁紙:僅以圖片的形式進行展示
    對于靜態壁紙,可以使用WallpaperManager中的getDrawable()等介面獲取到當前的bitmap影像,
  • 動態壁紙:顯示的內容為動態的內容,同時可以對用戶的操作做出回應
    對于動態壁紙的實時影像,是沒辦法通過android中原生的介面獲取到,需要獲取到動態壁紙的影像得自己修改原始碼,

壁紙實作時涉及的幾個主要的類:

  • WallpaperService及其內部類Engine:壁紙在WallpaperService這個服務中運行,當需要實作自己的壁紙時,繼承和實作這個類,是首先需要做的,Engine是WallpaperService中的一個內部類,實作了壁紙服務視窗的創建以及Surface的維護,同時Engine內部類還提供了onVisibilityChanged(),onCommand()等回呼方法,用于可見狀態變化和用戶觸摸事件等,Engine類因此也是壁紙實作的核心類,實作和重寫其介面在開發中也相當重要,
  • WallpaperManagerService和WallpaperManager:WallpaperManagerService用于管理壁紙的運行與切換,并通過WallpaperManager對外界提供操作壁紙的介面,
  • WindowMangerService:該類用于計算壁紙視窗的Z序,可見性以及為壁紙視窗應用影片,

壁紙服務的兩種啟動場景

非首次重啟壁紙服務啟動流程

SystemService行程啟動時,會啟動各種系統服務,在該類的startOtherServices()方法中會首先拉起
WallpaperManagerService,通過該類,WallpaperService后面才得以啟動,

            if (context.getResources().getBoolean(R.bool.config_enableWallpaperService)) {
                t.traceBegin("StartWallpaperManagerService");
                mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS);
                t.traceEnd();
            } else {
                Slog.i(TAG, "Wallpaper service disabled by config");
            }

WallpaperManagerService啟動之后systemReady()方法中會通過loadSettingsLocked()方法加載用戶設定過的壁紙資訊,然后監聽用戶切換用戶switchUser(),切換用戶時,switchWallpaper()會呼叫bindWallpaperComponentLocked()方法拉起對應的壁紙服務,

手動切換時壁紙服務的啟動流程

手動切換壁紙服務時需要通過WallpaperManager.getIWallpaperManager().setWallpaperComponent()方法完成,我們在這個介面中傳入壁紙服務對應的ComponentName,getIWallpaperManager回傳的是WallpaperManagerService的Bp(binder proxy binder代理)端,在WallpaperManagerService端,我們可以查看到setWallpaperComponent的具體實作,

private void setWallpaperComponent(ComponentName name, int userId) {
        ...
        /* 首先呼叫該方法的時候回去校驗權限,該權限定義在frameworks/base/core/res/AndroidManifest.xml,
            <permission android:name="android.permission.SET_WALLPAPER_COMPONENT"
        android:protectionLevel="signature|privileged" />
        查看protectionLevel,只有是特權應用或者系統簽名的應用才能獲取到這個系統權限,所以普通的應用是沒有辦法進行壁紙設定的
        */
        checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);

        int which = FLAG_SYSTEM;
        boolean shouldNotifyColors = false;
        WallpaperData wallpaper;

        synchronized (mLock) {
            Slog.v(TAG, "setWallpaperComponent name=" + name);
            /*
              此處會先通過當前的用戶ID獲取到與該用戶相關的壁紙資訊,WallpaperManagerService支持多用戶機制,用戶的資訊在mWallpaperMap中存盤,每一個用戶對應一個WallpaperData,WallpaperData存盤壁紙相關資訊
			*/
            wallpaper = mWallpaperMap.get(userId);
            if (wallpaper == null) {
                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
            }
      		 ...
      		    // 在這里真正會去拉起對應的WallPaperService
                if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {
              ...
    }

setWallpaperComponent最終也是通過bindWallpaperComponentLocked拉起壁紙服務

壁紙服務啟動程序

1.校驗是否是壁紙服務

bindWallpaperComponentLocked()方法將會啟動該ComponentName所指定的WallpaperService,在啟動的時候首先會進行校驗,以確定待拉起的服務是一個壁紙服務,

    private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
            boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
      	...
            int serviceUserId = wallpaper.userId;
            ServiceInfo si = mIPackageManager.getServiceInfo(componentName,
                    PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId);
            if (si == null) {
                // The wallpaper component we're trying to use doesn't exist
                Slog.w(TAG, "Attempted wallpaper " + componentName + " is unavailable");
                return false;
            }
            /*
            第一個校驗:  
			啟動的時候首先會校驗這個壁紙服務是否宣告權限為BIND_WALLPAPER權限,
              該權限的定義同樣也在fwk/base/core/res/manifest.xml
                  <permission android:name="android.permission.BIND_WALLPAPER"
        android:protectionLevel="signature|privileged" />
			  該權限也是系統級別的,防止三方應用切換壁紙,
			*/
            if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) {
                String msg = "Selected service does not have "
                        + android.Manifest.permission.BIND_WALLPAPER
                        + ": " + componentName;
                if (fromUser) {
                    throw new SecurityException(msg);
                }
                Slog.w(TAG, msg);
                return false;
            }

            WallpaperInfo wi = null;

            Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
            if (componentName != null && !componentName.equals(mImageWallpaper)) {
                // Make sure the selected service is actually a wallpaper service.
                			/*
			   第二個校驗:
			   這個檢查來校驗服務是否宣告了android.service.wallpaper.WallpaperService這個action,如果這個服務沒有宣告這個action的話那么,ris中就不會含有這個component資訊,
			*/
                List<ResolveInfo> ris =
                        mIPackageManager.queryIntentServices(intent,
                                intent.resolveTypeIfNeeded(mContext.getContentResolver()),
                                PackageManager.GET_META_DATA, serviceUserId).getList();
                for (int i=0; i<ris.size(); i++) {
                    ServiceInfo rsi = ris.get(i).serviceInfo;
                    if (rsi.name.equals(si.name) &&
                            rsi.packageName.equals(si.packageName)) {
                        try {
                        /*
                          第三個檢查:
                          獲取名為android.service.wallpaper中的meta-data資訊,該meta-data資訊中提供了縮略圖,開發者,簡單的描述等,會將這些資訊轉換成WallpaperInfo
                        */
                            wi = new WallpaperInfo(mContext, ris.get(i));
                        } catch (XmlPullParserException e) {
                            if (fromUser) {
                                throw new IllegalArgumentException(e);
                            }
                            Slog.w(TAG, e);
                            return false;
                        } catch (IOException e) {
                            if (fromUser) {
                                throw new IllegalArgumentException(e);
                            }
                            Slog.w(TAG, e);
                            return false;
                        }
                        break;
                    }
                }
                if (wi == null) {
                    String msg = "Selected service is not a wallpaper: "
                            + componentName;
                    if (fromUser) {
                        throw new SecurityException(msg);
                    }
                    Slog.w(TAG, msg);
                    return false;
                }
            }

			// 當壁紙服務支持在ambient模式下進行繪制的時候,需要檢查是否有AMBIENT_WALLPAPER權限,
            if (wi != null && wi.supportsAmbientMode()) {
                final int hasPrivilege = mIPackageManager.checkPermission(
                        android.Manifest.permission.AMBIENT_WALLPAPER, wi.getPackageName(),
                        serviceUserId);
                if (hasPrivilege != PackageManager.PERMISSION_GRANTED) {
                    String msg = "Selected service does not have "
                            + android.Manifest.permission.AMBIENT_WALLPAPER
                            + ": " + componentName;
                    if (fromUser) {
                        throw new SecurityException(msg);
                    }
                    Slog.w(TAG, msg);
                    return false;
                }
            }
            // 檢驗完畢,這里才會開始bind 壁紙服務,如果校驗失敗的話,會回傳false
			...
    }

上面的校驗可以看出一共校驗了三個條件:

  1. 啟動的時候首先會校驗這個壁紙服務是否宣告權限為BIND_WALLPAPER權限, 該權限的定義在fwk/base/core/res/manifest.xml
    < permission android:name=“android.permission.BIND_WALLPAPER”
    android:protectionLevel=“signature|privileged” />
    該權限也是系統級別的,防止三方應用切換壁紙,
  2. 這個檢查來校驗服務是否宣告了android.service.wallpaper.WallpaperService這個action,如果這個服務沒有宣告這個action的話那么,ris中就不會含有這個component資訊,
  3. 獲取名為android.service.wallpaper中的meta-data資訊,該meta-data資訊中提供了縮略圖,開發者,簡單的描述等,會將這些資訊轉換成WallpaperInfo,

2.系結壁紙服務

壁紙服務的校驗滿足后,開始啟動和系結目標服務:

    private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
            boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
	// 校驗服務是否符合要求結束后,開始著手啟動服務
	...
	//1. 創建一個WallpaperConnection,該物件可以監聽和WallpaperService之間的連接狀態,同時歸物件繼承了IWallpaperConnection.Stub,這樣該物件有擁有了跨行程通信的能力,當服務系結成功后,onServiceConnected()方法呼叫中,WallpaperConnection實力會被發送到WallpaperService,該實體可以用于WallpaperService想WallpaperManagerService進行通信的橋梁,
	intent.setComponent(componentName);
    intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
                    com.android.internal.R.string.wallpaper_binding_label);
    ...
    /* 2. 這里啟動指定的壁紙服務,服務啟動后,壁紙還沒有辦法進行顯示,還需要WallpaperConnection.onServiceConnected中進行相應的處理*/
    if (!mContext.bindServiceAsUser(intent, newConn,Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI| Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE | 
    Context.BIND_INCLUDE_CAPABILITIES,new UserHandle(serviceUserId))) {
}

	/*3. 新的壁紙服務啟動之后,就開始銷毀舊服務*/
	if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null && !wallpaper.equals(mFallbackWallpaper)) {
         detachWallpaperLocked(mLastWallpaper);
    }
    
    /* 4.將新的壁紙服務資訊進行保存*/
	wallpaper.wallpaperComponent = componentName;
    wallpaper.connection = newConn;
    ...

bindWallpaperComponentLocked函式在拉起壁紙服務的時候主要做了下面幾件事情:

  • 創建了WallpaperConnection物件,由于實作了ServiceConnection介面,所以WallpaperConnection可以用來監聽和壁紙服務的連接狀態,另外由于繼承了IWallpoaperConnection.Stub介面,所以WallpaperConnection具有了跨行程通信的能力,
  • 啟動壁紙服務:這里僅僅是拉起服務,和拉起普通服務的方式基本一致,拉起方式上則使用了bindServiceAsUser,查看官方注解,該介面增加了校驗該用戶是否能拉起該服務,其余的行為和bindService相同,
  • 保存當前WallpaperConnection實體,ConponentName,到WallpaperData中

bindWallpaperComponentLocked()函式將壁紙服務拉了起來,但是僅僅將壁紙服務拉起來是沒有辦法顯示影像的,因為啟動的服務并沒有視窗令牌,這樣就沒辦法添加視窗,剩下的這部分顯示的作業在WallpaperConnection的onServiceConnected()方法中進行,在該回呼中同樣也能拿到壁紙服務端服務端提供的Binder物件,
WallpaperService在被bind的時候回傳了一個IWallpaperServiceWrapper物件,從代碼中可以看到,該物件中保存了WallpaperService實體,看了代碼后再去理解這個物件的命名(包裝WallpaperService),果然名副其實,

  class IWallpaperServiceWrapper extends IWallpaperService.Stub {
        private final WallpaperService mTarget;
        private IWallpaperEngineWrapper mEngineWrapper;

        public IWallpaperServiceWrapper(WallpaperService context) {
            mTarget = context;
        }
        
        @Override
        public void attach(IWallpaperConnection conn, IBinder windowToken,
        int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,int displayId) {
		...
		}

        @Override
        public void detach() {
        ...
        }
    }

該介面中一共有兩個介面,attach和detach,attach介面在創建的時候可以將相關資訊傳遞到壁紙服務中,對應的,detach介面在服務銷毀的時候呼叫,

3.引擎的創建和初始化

引擎的創建準備作業開始于onServiceConnected()回呼處,該回呼會傳遞壁紙服務需要的視窗令牌和ServiceConnection物件等,
WallpaperManagerService.java

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            synchronized (mLock) {
                if (mWallpaper.connection == this) {
                    mService = IWallpaperService.Stub.asInterface(service);
                    attachServiceLocked(this, mWallpaper);
                    // XXX should probably do saveSettingsLocked() later
                    // when we have an engine, but I'm not sure about
                    // locking there and anyway we always need to be able to
                    // recover if there is something wrong.
                    if (!mWallpaper.equals(mFallbackWallpaper)) {
                    	// 保存當前的壁紙資訊到檔案系統中,這樣重啟的時候就可以加載之前用戶設定過的壁紙
                        saveSettingsLocked(mWallpaper.userId);
                    }
                    FgThread.getHandler().removeCallbacks(mResetRunnable);
                    mContext.getMainThreadHandler().removeCallbacks(mTryToRebindRunnable);
                }
            }
        }

onServiceConnected()函式中,首先將回傳的binder物件進行了保存,然后在attachServiceLocked()方法中會呼叫connectLocked()方法,connectLocked()介面中呼叫了attach方法傳遞了壁紙服務所需要的資訊,

            void connectLocked(WallpaperConnection connection, WallpaperData wallpaper) {
                ...
                mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId);
                final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);
                try {
                    connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
                            wpdData.mWidth, wpdData.mHeight,
                            wpdData.mPadding, mDisplayId);
                }
                ...
            }

attach介面回傳了許多資訊,其中

  • connection為WallpaperConnection的實體,WallpaperConnection之所以具有跨行程通信的能力是因為繼承了IWallpaperConnection.Stub類,該Stub物件中比較重要的一個介面就是attachEngine(),因為Engine實作才是動態壁紙的核心,WallpaperService會將創建好的Engine參考通過attachEngine()回傳給WallpaperManagerService進行管理,
  • mToken是向WMS注冊過的視窗令牌,只有擁有了這個令牌,WallpaperService才有權添加壁紙視窗,

傳遞了WallpaperService需要的資訊之后,WallPaperService開始進行引擎的創建,查看WallpaperService中attach()方法的實作,

    class IWallpaperServiceWrapper extends IWallpaperService.Stub {
        ...
        @Override
        public void attach(IWallpaperConnection conn, IBinder windowToken,
                int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
                int displayId) {
            mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken,
                    windowType, isPreview, reqWidth, reqHeight, padding, displayId);
        }
        ...
     }

attach方法創建了一個IWallpaperEngineWrapper,顧名思義,該物件有壁紙服務創建的引擎的參考,在創建IWallpaperEngineWrapper物件的時候,會發送DO_ATTACH訊息,該訊息用于壁紙服務引擎的創建,

IWallpaperEngineWrapper.java
        IWallpaperEngineWrapper(WallpaperService context,
                IWallpaperConnection conn, IBinder windowToken,
                int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
                int displayId) {
            mCaller = new HandlerCaller(context, context.getMainLooper(), this, true);
        	...
            Message msg = mCaller.obtainMessage(DO_ATTACH);
            mCaller.sendMessage(msg);
        }
...
		@Override
        public void executeMessage(Message message) {
            switch (message.what) {
                case DO_ATTACH: {
                    try {
                    // 將IWallpaperEngineWapper物件傳遞給WallpaperConnection進行保存,通過這個參考,WallpaperManagerService也可以通過它與engine進行通信
                        mConnection.attachEngine(this, mDisplayId);
                    } catch (RemoteException e) {
                        Log.w(TAG, "Wallpaper host disappeared", e);
                        return;
                    }
                    // 創建一個引擎,該方法為抽象方法,需要子類根據自身實作具體的引擎
                    Engine engine = onCreateEngine();
                    mEngine = engine;
                    mActiveEngines.add(engine);
					// 該方法中會完成視窗的創建,surface創建等作業,
                    engine.attach(this);
                    return;
                }

由于mConnection.attachEngine()方法將IWallpaperEngineWrapper傳遞給了WallpaperManagerService,因此WallpaperManagerService可以轉發相關的請求和設定到Engine物件中,實作WallpaperManagerService到壁紙的通信,
onCreateEngine方法執行后,引擎創建完成,之后通過engine.attach()方法進行引擎相關的初始化:

        void attach(IWallpaperEngineWrapper wrapper) {
            ...
            mIWallpaperEngine = wrapper;
            mCaller = wrapper.mCaller;
            mConnection = wrapper.mConnection;
            mWindowToken = wrapper.mWindowToken;
            mSurfaceHolder.setSizeFromLayout();
            mInitializing = true;

			// 這個session用于和WMS進行通信
            mSession = WindowManagerGlobal.getWindowSession();

			// mWindow是一個IWindow物件,用于接收從WMS發送過來的訊息
            mWindow.setSession(mSession);

            mLayout.packageName = getPackageName();
            mIWallpaperEngine.mDisplayManager.registerDisplayListener(mDisplayListener,
                    mCaller.getHandler());
            mDisplay = mIWallpaperEngine.mDisplay;
            mDisplayContext = createDisplayContext(mDisplay);
            mDisplayState = mDisplay.getState();

            if (DEBUG) Log.v(TAG, "onCreate(): " + this);
            // 子類可以重寫該介面,在該介面中可以修改mSurfaceHolder相關的屬性,這個時候
            // 視窗尚未創建,設定的相關屬性將在updateSurface中創建視窗時使用
            onCreate(mSurfaceHolder);

            mInitializing = false;

            mReportedVisible = false;
			
			// updateSurface會進行視窗以及Surface的創建,
            updateSurface(false, false, false);
        }

attach方法執行的完成,標志著壁紙啟動的完成,之后可以呼叫壁紙的surface顯示影像,

壁紙服務的啟動流程總結

壁紙服務的啟動相比于普通服務的啟動較為復雜,接下來用下面的示意圖對整體的流程進行梳理:
在這里插入圖片描述
壁紙服務在啟動的時候,大體可以分為兩個階段,首先就要是拉起對應的服務,拉起服務后然后將WindowToken等引數傳遞給引擎進行視窗的創建,surface的創建,在WallpaperManagerService和WallpaperService互動的程序中,主要有下面三個跨行程通信的Binder物件:

  • WallpaperConnection:實作在WallpaperManagerService中,并通過IWallpaperService.attach回呼傳遞給了IWallpaperEngineWrapper,通過WallpaperConnection.attachEngine()方法,WallpaperService將IWallpaperEngineWrapper回傳給了WallpaperManagerService,實作了雙向的通信,
  • IWallpaperService:實作在WallpaperService中,該物件提供了attach方法,用于從WallpaperManagerService獲取引擎創建時需要的WindowToken等資訊,
  • IWallpaperEngineWrapper:實作在壁紙服務行程中,同時參考交給了WallpaperManagerService,該物件封裝了Engine類,WallpaperManagerService對引擎相關的控制需要通過該物件提供的介面實作,

自己最近因為需要定位一個開機壁紙服務啟動慢的問題,所以熟悉了下壁紙服務的啟動程序,在此記錄啟動流程,定位該問題梳理從bind到onServiceConnected和引擎相關初始化,對比每一個階段的時間,最終確定問題的原因,
參考:《深入理解Android卷3》第八章:深入理解android壁紙服務

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

標籤:其他

上一篇:autojs腳本代碼大全(實戰演練1)

下一篇: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