主頁 > 移動端開發 > setContentView原始碼分析

setContentView原始碼分析

2021-01-29 14:34:43 移動端開發

1 從我們自己新建的一個Activity的setContentView 就開始

public class ActivityTest extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

    }

}
                ↓
                ↓ setContentView(R.layout.activity_test);
                ↓
public class AppCompatActivity extends ... {

     public void setContentView(@LayoutRes int layoutResID) {
        this.getDelegate().setContentView(layoutResID);
     }


                ↓
                ↓ getDelegate().setContentView(layoutResID);先找getDelegate()
                ↓ getDelegate()也在AppCompatActivity 中
                ↓
 @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
}
                ↓
                ↓  getDelegate() = AppCompatDelegate.create(this, this);
                ↓
public abstract class AppCompatDelegate {
  public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
    }   
}

所以最后呼叫的是

2 AppCompatDelegateImpl 中的 setContentView 方法:

class AppCompatDelegateImpl extends ...{

    public void setContentView(int resId) {
        this.ensureSubDecor();
        ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
        contentParent.removeAllViews();
        LayoutInflater.from(this.mContext).inflate(resId, contentParent);
        this.mOriginalWindowCallback.onContentChanged();
    }

}

接著去看 ensureSubDecor 方法:

2.1 ensureSubDecor

private void ensureSubDecor() {
        if (!this.mSubDecorInstalled) {
            this.mSubDecor = this.createSubDecor();//核心代碼

           //省略其它代碼,,,
        }

    }

2.2 createSubDecor

 private ViewGroup createSubDecor() {
        ?這里是獲取Activity的theme,并根據主題風格為subDecor確定加載哪個布局 
        TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
        if (!a.hasValue(styleable.AppCompatTheme_windowActionBar)) {
            a.recycle();
            throw new IllegalStateException("You need to use a Theme.AppCompat theme (or descendant) with this activity.");
        } else {
             

            this.mIsFloating = a.getBoolean(styleable.AppCompatTheme_android_windowIsFloating, false);
            a.recycle();
            this.mWindow.getDecorView(); ---> ???step1 創建DecirView
            LayoutInflater inflater = LayoutInflater.from(this.mContext);
            ViewGroup subDecor = null; ???Step2 開始
            if (!this.mWindowNoTitle) {
                if (this.mIsFloating) {
                    subDecor = (ViewGroup)inflater.inflate(layout.abc_dialog_title_material, (ViewGroup)null);
                    
                } else if (this.mHasActionBar) {
                    
                    subDecor = (ViewGroup)LayoutInflater.from((Context)themedContext).inflate(layout.abc_screen_toolbar, (ViewGroup)null); 
                    
                }
            } else {
                if (this.mOverlayActionMode) {
                    subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple_overlay_action_mode, (ViewGroup)null);
                } else {
                    subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple, (ViewGroup)null);
                }

                //其它代碼省略...
                
                
            }
            ? 通過上面subDecor = inflate(布局檔案)可以看出,subDecor 的布局檔案是下面四種布局檔案之一:
                  1  abc_dialog_title_material
                  2  abc_screen_toolbar
                  3  abc_screen_simple_overlay_action_mode
                  4  abc_screen_simple
            if (subDecor == null) {
                ? 如果subDecor為空就拋出例外,這個例外看起來是不是很熟悉 
                throw new IllegalArgumentException("AppCompat does not support the current theme features: { windowActionBar: " + this.mHasActionBar + ", windowActionBarOverlay: " + this.mOverlayActionBar + ", android:windowIsFloating: " + this.mIsFloating + ", windowActionModeOverlay: " + this.mOverlayActionMode + ", windowNoTitle: " + this.mWindowNoTitle + " }");
            } else {
                if (this.mDecorContentParent == null) {
                    this.mTitleView = (TextView)subDecor.findViewById(id.title);
                }

                ViewUtils.makeOptionalFitsSystemWindows(subDecor);
                 //? 上面說了 subDecor 是四個布局檔案中的一個創建,
                 // ?每個布局檔案都有一action_bar_activity_content   
                ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);
                ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(16908290);
                if (windowContentView != null) {
                    while(windowContentView.getChildCount() > 0) {
                        View child = windowContentView.getChildAt(0);
                        windowContentView.removeViewAt(0);
                        contentView.addView(child);
                    }

                    windowContentView.setId(-1);
                    contentView.setId(16908290);--->???Step2 結束 contentView的id被設定成16908290
                    if (windowContentView instanceof FrameLayout) {
                        ((FrameLayout)windowContentView).setForeground((Drawable)null);
                    }
                }

                this.mWindow.setContentView(subDecor);---> ???Step3 把subDecor加載到DecorView布局檔案中的容器中
                contentView.setAttachListener(new OnAttachListener() {
                    public void onAttachedFromWindow() {
                    }

                    public void onDetachedFromWindow() {
                        AppCompatDelegateImpl.this.dismissPopups();
                    }
                });
                return subDecor; ? 把subDecor回傳賦值給mSubDecor
            }
        }
    }

總結一下createSubDecor方法,一共做了三件事:

1 this.mWindow.getDecorView(); 創建Decorview,并為它加載一個布局檔案,找到這個布局檔案中 R.id.content 的容器,賦值給 mContentParent,

這樣我們就準備好了一個DecorView和其布局中id為R.id.content 的容器,

2 給ViewGroup subDecor根據主題、style選擇合適布局檔案并加載到subDecor中:

ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);

ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(R.id.content);? 這里就是上一步里面那個布局檔案的R.id.content 容器

windowContentView.setId(View.NO_ID); ? 把windowContentView的id設定為View.NO_ID 即 -1

contentView.setId(android.R.id.content); ? 把contentView 的id設定為R.id.content

這樣我們準備好了subDecor和其布局中 id為action_bar_activity_content的容器,并把這個容器的id改成 R.id.content

3 this.mWindow.setContentView(subDecor); 將第2步的subDecor添加到 第1步準備好的DecorView的容器mContentParent中,

下面分析這三步驟

Step1 this.mWindow.getDecorView(); this.mWindow 這個是啥呢?看下面

 AppCompatDelegateImpl(Context context, Window window, AppCompatCallback callback) {
        ......

       mWindow 的初始化是在AppCompatDelegateImpl建構式里
                ↓
       this.mWindow = window;

        .....
       
    }
        想要知道mWindow是啥就要找到AppCompatDelegateImpl(context,window,callback)這個
        建構式初始化的時候傳入的window是啥
        還記得最開始我們從setContentView點進來的代碼么
        
                ↓
                ↓  
                ↓
*********************************************************************** 
public class ActivityTest extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

    }

}
                ↓
                ↓ setContentView(R.layout.activity_test);
                ↓
public class AppCompatActivity extends ... {

     public void setContentView(@LayoutRes int layoutResID) {
        this.getDelegate().setContentView(layoutResID);
     }


                ↓
                ↓ getDelegate().setContentView(layoutResID);先找getDelegate()
                ↓ getDelegate()也在AppCompatActivity 中
                ↓
 @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
}
                ↓
                ↓  getDelegate() = AppCompatDelegate.create(this, this);
                ↓
public abstract class AppCompatDelegate {
  public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        在這里初始化的,activity就是AppCompatActivity ,window就是activity.getWindow()
        return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
    }   
}
***************************************************************************************

window就是AppCompatActivity.getWindow(),但是AppCompatActivity中沒有getWindow()方法,
getWindow()是在其父類Activity中實作
public class Activity extends ... ... {

       private Window mWindow;
        
      final void attach(Context context, ......) {
        attachBaseContext(context);

           

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        
        ......
       }

      public @Nullable Window getWindow() {
            return mWindow;
       }
}



最后找到window就是PhoneWindow物件,

那就到里PhoneWindow面看一下 getDecorView()是如何實作的

PhoneWindow.java: 
@Override
    public final View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();  --->  ?呼叫installDecor()
        }
        return mDecor;
    }

 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1); ---> ? generateDecor就是創建DecorView物件
           
            ....省略其它代碼....
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor); ---> ? 給DecorView物件加載布局檔案
                                                              并準備好R.id.content容器               
            ....省略其它代碼.....
            }
}

先來看 mDecor = generateDecor(-1); generateDecor方法很簡單就是創建了一個DecorView物件并回傳賦值給 mDecor

 protected DecorView generateDecor(int featureId) {
      
        ...其它代碼省略...        

        return new DecorView(context, featureId, this, getAttributes());
    }

接著看 mContentParent = generateLayout(mDecor);

1:mContentParent是一個ViewGroup;

2 :generateLayout(mDecor);將上面剛剛創建的DecorView作為引數傳了進去

protected ViewGroup generateLayout(DecorView decor) {
      ? 獲取windowStyle,給DecorView加載哪種布局就是根據這個來判斷的!
      TypedArray a = getWindowStyle();        

        ...其它代碼省略...

        // Inflate the window decor.

        ? 這里精簡保留的,layoutResource = R.layout.布局檔案 
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
              
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            
            layoutResource = R.layout.screen_progress;
           
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
           
            if (mIsFloating) {
               
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
           
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
             
            if (mIsFloating) {
              
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
            
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
             
            layoutResource = R.layout.screen_simple;
            
        }

        mDecor.startChanging();

       ? layoutResource就是上面根據WindowStyle決定的布局檔案之中的一個,
            然后加載到mDecor即DecorView中
                    ↓
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
       
       ? 而上面布局檔案中它們都有一個id是content的
       ? 而且這個findViewById是在PhoneWindow直接使用?它是Activity?文章最后分析
                      ↓
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        
        return contentParent; ← ?回傳DecorView的布局檔案中id為content的控制元件
    }

到這里 step1 this.mWindow.getDecorView() 就完成了,PhoneWindow新建了一個DecorView并為其加載好布局檔案,并將布局檔案中R.id.content容器準備好,

Step2 具體分析已在上面代碼中注釋

step3 this.mWindow.setContentView(subDecor)

接著我們就來看看3 this.mWindow.setContentView(subDecor);同樣是this.mWindow我們就到PhoneWindow中找setContentView方法

@Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
           ?還記得 step1 的時候準備好的mContentParent,現在就是把subDecor加載到其中
            ↓  
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

mContentParent.addView(view, params);

|--- mContentParent 就是上面DecorView中加載布局檔案的 content 控制元件

|--- view 就是我們傳遞進來的subDecor

到這里Step3 就結束了,

再來看最開始的setContentView

 public void setContentView(int resId) {
        this.ensureSubDecor(); ? 我們上面這么久的呼叫就是走完這一行代碼,想不到吧,

        ?找到id為R.id.content的控制元件,即subDecor布局中的控制元件
        ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(R.id.content);

        contentParent.removeAllViews();

        ?把我們的布局檔案加載到R.id.content的控制元件中即subDecor布局中
        LayoutInflater.from(this.mContext).inflate(resId, contentParent);

        this.mOriginalWindowCallback.onContentChanged();
    }

我們自己的布局檔案被加載到了subDecor布局中id為 action_bar_activity_content的 ContentFrameLayout中,

而subDecor被加載到了DecorView布局中id為content的FrameLayout中;

DecorView是由PhoneWindow 創建

PhoneWindow 在Activity attach方法中初始化,

這樣整個加載程序就串起來了,

是時候放上這樣一張圖了

DecorView加載的xml檔案為下面中的一個:

screen_swipe_dismiss

screen_title_icons

screen_progress

screen_custom_title

screen_action_bar

screen_title

screen_simple_overlay_action_mode

screen_simple

而他們都有一個id為content的FrameLayout控制元件用來加載subDcor.

subDcor加載的xml檔案為下面中的一個:

abc_dialog_title_material
abc_screen_toolbar
abc_screen_simple_overlay_action_mode
abc_screen_simple

而他們都有一個id為action_bar_activity_content 的 ContentFrameLayout 控制元件用來加載我們自定義的布局檔案,就是我們創建Activity時候創建的xml檔案,

上面還有一個問題PhoneWindow為啥可以直接findViewById? 點進去就會發現是在其父類Window中實作的

public abstract class Window {
     @Nullable
    public <T extends View> T findViewById(@IdRes int id) {
        return getDecorView().findViewById(id);
    }

    public abstract View getDecorView();

}

getDecorView的具體實作又回到了PhoneWindow 中;

    @Override
    public final View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }

這個方法看起來是不是很熟悉,就是我們初始化DecorView物件的方法,回傳的就是我們初始化并加載了布局的DecorView.

所以PhoneWindow進行findViewById其實就是對其持有的DecorView進行操作,

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

標籤:其他

上一篇:C/C++編程學習 - 第11周 ⑤ 發工資咯:)

下一篇:Android 11適配指南之系統相機拍照、打開相冊

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