主頁 > 移動端開發 > Android 深入了解 Window 、Activity、 View 三者關系

Android 深入了解 Window 、Activity、 View 三者關系

2021-09-09 12:53:25 移動端開發

Window、Activity、View都經常用到,但三者關系還是沒有系統的理清,今天咱們就開始整理整理這三者的關系:

Window:頂級視窗外觀和行為策略的抽象基類,唯一實作是 PhoneWindow類,

Activity:四大組件之一,它提供一個界面讓用戶點擊和各種滑動操作,

View:代表用戶界面組件的基本構建塊,UI 組件,

原始碼:Android SDK 30

Activity.setContentView

不知道從哪入手,咱們就從setContentView開始,比較剛創建的Android專案Activity和setContentView是必不可少的,記住看Activity的setContentView而不是AppCompatActivity的

來瞅瞅setContentView:

    public void setContentView(@LayoutRes int layoutResID) {
        //重點來了
        getWindow().setContentView(layoutResID);
        //創建并設定ActionBar,
        initWindowDecorActionBar();
    }

getWindow?這么快就遇到了,好簡單,咱繼續看,

Activity.getWindow

public Window getWindow() {
        return mWindow;
}

本來以為發現大魚了,顯然 Activity 幾乎什么都沒做,將操作直接交給了一個 Window 來處理,getWindow 回傳的是 Activity 中的全域變數 mWindow,它是 Window 視窗型別,那么它是什么時候賦值的呢?

Activity.attach()

找遍了整個Activity原始碼終于在 attach 方法找到了,咱們先看看代碼,

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
        //重點來了對PhoneWindow進行實體化
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        ...
        //呼叫setWindowManager
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        ...
    }

接下來呼叫 setWindowManager 方法,將系統 WindowManager 傳給 PhoneWindow,

Window:實際上整個 Android 系統中 Window 只有一個實作類,就是 PhoneWindow

PhoneWindow:Android-specific Window(特定視窗).

WindowManager:對Window進行管理,說到管理那就離不開對Window的添加、更新和洗掉的操作,在這里我們把它們統稱為Window的操作,對于Window的操作,最終都是交由WMS來進行處理,

Window.setWindowManager

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
            //通過Binder機制來獲取WMS
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        //注釋
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
    
    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }

注釋:這個方法很簡單就是就是又一次創建了 WindowManagerImpl 物件,這時 WindowManager 真正和Window關聯起來

PhoneWindow.setContentView

Activity 將 setContentView 的操作交給了 PhoneWindow,接下來看下其實作程序:

@Override
    public void setContentView(int layoutResID) {
        //注意:當主題屬性等結晶化時,可在安裝視窗裝飾的程序中設定功能內容轉換,
        //在這種情況發生之前,不要檢查功能,
        if (mContentParent == null) {
            //注釋1
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //注釋2
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

PhoneWindow.installDecor

  private void installDecor() {
        mForceDecorInstall = false;
        //初始化mDecor
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        //初始化mContentParent
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            ...
        }
    }

注釋1:呼叫 installDecor 初始化 DecorView 和 mContentParent,

注釋2mLayoutInflater.inflate(layoutResID, mContentParent); 從指定的xml資源展開新的視圖層次結構,呼叫 setContentView 傳入的布局添加到 mContentParent 中,

從中可以看出PhoneWindow 中默認有一個 DecorView(實際上是一個 FrameLayout),在 DecorView 中默認自帶一個 mContentParent(ViewGroup),我們自己實作的布局是被添加到 mContentParent 中的,因此經過 setContentView 之后,PhoneWindow 內部的 View 關系如下所示:

目前為止 PhoneWindow 中只是創建出了一個 DecorView,并在 DecorView 中填充了我們在 Activity 中傳入的 layoutId 布局,可是 DecorView 還沒有跟 Activity 建立任何聯系,也沒有被繪制到界面上顯示,那 DecorView 是何時被繪制到螢屏上的呢?

Activity 執行到 onCreate 時并不可見,只有執行完 onResume 之后 Activity 中的內容才是螢屏可見狀態,造成這種現象的原因就是,onCreate 階段只是初始化了 Activity 需要顯示的內容,而在 onResume 階段(當界面要與用戶進行互動時,會呼叫ActivityThread的handleResumeActivity方法)才會將 PhoneWindow 中的 DecorView 真正的繪制到螢屏上,

ActivityThread.handleResumeActivity

在 ActivityThread 的 handleResumeActivity 中,會呼叫 WindowManager 的 addView 方法將 DecorView 添加到 WMS(WindowManagerService) 上,如下所示:

    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
            ...
            ViewManager wm = a.getWindowManager();
            ...
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    //注釋
                    wm.addView(decor, l);
                } else {
                    a.onWindowAttributesChanged(l);
                }
            }
            ...
    }

注釋:WindowManger 的 addView 結果有兩個: DecorView 被渲染繪制到螢屏上顯示; DecorView 可以接收螢屏觸摸事件,

WindowManager.addView

ViewManager是一個介面,WindowManager是個介面同時又實作了ViewManager介面,而WindowManagerImpl又實作了WindowManager,這是只捋清楚這個方法的層級,清楚后放到一邊繼續查看WindowManagerImpl中的addview方法

WindowManagerImpl.addView

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }

WindowManagerImpl.addView也是一個空殼,它呼叫了 WindowManagerGlobaladdView 方法,

WindowManagerGlobal.addView

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        ...
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            ...
            root = new ViewRootImpl(view.getContext(), display);

            //注釋1
            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            //最后執行此操作,因為它會發出訊息開始執行操作
            try {
                //注釋2
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                ...
            }
        }
    }

注釋1:WindowMangerGlobal 是一個單例 在 addView 方法中,創建了一個最關鍵的 ViewRootImpl 物件,

注釋2:然通過 root.setView 方法將 view 添加到 WMS 中,

ViewRootImpl.setView

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ...
                int res; /* = WindowManagerImpl.ADD_OKAY; */
                //注釋1
                requestLayout();
                InputChannel inputChannel = null;
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    inputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    ...
                    //注釋2
                    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mDisplayCutout, inputChannel,
                            mTempInsets, mTempControls);
                    setFrame(mTmpFrame);
                } catch (RemoteException e) {
                ...
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
                
        }
    }

注釋1: requestLayout 是重繪布局的操作,呼叫此方法后 ViewRootImpl 所關聯的 View 也執行 measure -> layout -> draw 操作,確保在 View 被添加到 Window 上顯示到螢屏之前,已經完成測量和繪制操作,

注釋2:呼叫 mWindowSession 的 addToDisplay 方法將 View 添加到 WMS 中,

mWindowSession哪里來的?他是new RootViewlmpl物件是傳入進來的,呼叫WindowManagerGlobal.getWindowSession()生成的,

new ViewRootImpl(context,display);

    public ViewRootImpl(Context context, Display display) {
        this(context, display, WindowManagerGlobal.getWindowSession(),false);
    }

WindowManagerGlobal.getWindowSession()

WindowSession 是 WindowManagerGlobal 中的單例物件,初始化代碼如下:

    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    IWindowManager windowManager = getWindowManagerService();
                    //注釋
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }

注釋:sWindowSession 實際上是 IWindowSession 型別,是一個 Binder 型別,真正的實作類是 System 行程中的 Session,用 AIDL 獲取 System 行程中 Session 的物件,

Session.addToDisplay

@Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
            Rect outStableInsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
                outInsetsState, outActiveControls, UserHandle.getUserId(mUid));
    }

return mService.addWindow(...);

其中的 mService 就是 WMS,至此,Window 已經成功的被傳遞給了 WMS,剩下的作業就全部轉移到系統行程中的 WMS 來完成最終的添加操作,

又回到Activity

addView 成功有一個標志就是能夠接收觸屏事件,通過對 setContentView 流程的分析,可以看出添加 View 的操作實質上是 PhoneWindow 在全盤操作,背后負責人是 WMS,反之 Activity 自始至終沒什么參與感,但是我們也知道當觸屏事件發生之后,Touch 事件首先是被傳入到 Activity,然后才被下發到布局中的 ViewGroup 或者 View(Touch事件分發了解一下),那么 Touch 事件是如何傳遞到 Activity 上的呢?

ViewRootImpl 中的 setView 方法中,除了呼叫 IWindowSession 執行跨行程添加 View 之外,還有一項重要的操作就是設定輸入事件的處理:

ViewRootImpl.setView

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
          ..
          res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                      getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                      mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                      mAttachInfo.mDisplayCutout, inputChannel,
                      mTempInsets, mTempControls);
          // 注釋:設定輸入管道,
          CharSequence counterSuffix = attrs.getTitle();
          mSyntheticInputStage = new SyntheticInputStage();
          InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
          InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                  "aq:native-post-ime:" + counterSuffix);
          InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
          InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                  "aq:ime:" + counterSuffix);
          InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
          InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                  "aq:native-pre-ime:" + counterSuffix);
        ...
    }

注釋:設定了一系列的輸入通道,一個觸屏事件的發生是由螢屏發起,然后經過驅動層一系列的優化計算通過 Socket 跨行程通知 Android Framework 層(實際上就是 WMS),最終螢屏的觸摸事件會被發送到代碼中的輸入管道中,

這些輸入管道實際上是一個鏈表結構,當某一個螢屏觸摸事件到達其中的 ViewPostImeInputState 時,會經過 onProcess 來處理,如下所示:

ViewRootImpl.ViewPostImeInputStage

 final class ViewPostImeInputStage extends InputStage {
        public ViewPostImeInputStage(InputStage next) {
            super(next);
        }

        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    return processPointerEvent(q);
                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                    return processTrackballEvent(q);
                } else {
                    return processGenericMotionEvent(q);
                }
            }
        }
        private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;
            ...
            boolean handled = mView.dispatchPointerEvent(event);
            maybeUpdatePointerIcon(event);
            maybeUpdateTooltip(event);
            mAttachInfo.mHandlingPointerEvent = false;
            ...
            return handled ? FINISH_HANDLED : FORWARD;
        }
  }

processPointerEvent 在 ViewPostImeInputStage 中別找錯了,可以看到在 onProcess 中最終呼叫了一個 mView的dispatchPointerEvent 方法,mView 實際上就是 PhoneWindow 中的 DecorView,而 dispatchPointerEvent 是被 View.java 實作的,如下所示:

View.dispatchPointerEvent

public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

DecorView.dispatchTouchEvent

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

最好呼叫了 Window.Callback 中 cb.dispatchTouchEvent(ev) 方法,那這個 Callback 是不是 Activity 呢?

Activity.attach

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        ...
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        //this:Activity
        mWindow.setCallback(this);
        ...
    }

Activity 將自身傳遞給了 PhoneWindow,再接著看 Activity的dispatchTouchEvent,

Activity.dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            //該方法是用戶互動,每當向Activity分派按鍵、觸摸或軌跡球事件時呼叫,
            //在這里僅用于ACTION_DOWN的判斷
            onUserInteraction();
        }
        //回傳true
        if (getWindow().superDispatchTouchEvent(ev)) {
            //Activity.dispatchTouchEvent()就回傳true,則方法結束,
            //該點擊事件停止往下傳遞&事件傳遞程序結束
            return true;
        }
        return onTouchEvent(ev);
    }

PhoneWindow.superDispatchTouchEvent

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

DecorView.superDispatchTouchEvent

public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

Touch 事件在 Activity 中只是繞了一圈最后還是回到了 PhoneWindow 中的 DecorView 來處理, 剩下的就是從 DecorView 開始將事件層層傳遞給內部的子 View 中了

ViewGroup. dispatchTouchEvent

   @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    
    }

到這里就不做多的重復了,感興趣的可以看看事件分發機制,

小結

通過 setContentView 的流程,分析了 Activity、Window、View 之間的關系,整個程序 Activity 表面上參與度比較低,Activity持有Window的物件,View在Window上的增刪等操作又是通過WindowManager來管理的,而WindowManager又是通過Binder機制獲取到的WMS的映射,WMS把View真正顯示到螢屏上,

三者關系:

  1. 一個 Activity 中有一個 window,也就是 PhoneWindow 物件,在Activity中呼叫attach,創建了一個PhoneWindow,
  2. 在 PhoneWindow 中有一個 DecorView,Activity在 呼叫setContentView 中會將 layout 填充到此 DecorView 中,
  3. 一個應用行程中只有一個 WindowManagerGlobal 物件,因為在 ViewRootImpl 中它是 static 靜態型別,
  4. 每一個 PhoneWindow 對應一個 ViewRootImple 物件,
  5. WindowMangerGlobal 通過呼叫 ViewRootImpl 的 setView 方法,完成 window 的添加程序,
  6. 呼叫ViewGroup的removeAllView(),先將所有的view移除掉
  7. ViewRootImpl 的 setView 方法中主要完成兩件事情:View 渲染(requestLayout)以及接收觸屏事件,

往期回顧

OkHttp使用和原始碼詳解

RecyclerView 繪制流程及Recycler快取

Glide 快取機制及原始碼(二)

Glide 的簡單使用(一)

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

標籤:其他

上一篇:華為音頻編輯服務帶你一鍵伴奏分離!

下一篇:ACPI Specification 第三章 ACPI概念

標籤雲
其他(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