主頁 > 移動端開發 > 「性能優化系列」APP記憶體優化理論與實踐

「性能優化系列」APP記憶體優化理論與實踐

2021-09-03 19:15:24 移動端開發

當一個應用同時運行越來越多的任務以及復雜的業務,Android系統的記憶體管理機制已經無法滿足記憶體的釋放與回收,為了應用的穩定性與性能,去控制記憶體的創建和回收就成為了一個重要的命題,

本篇文章主要涉及內容如下:

  • 物件的創建與回收;
  • 分配記憶體的方式,物件在JVM中的生命周期;
  • 判斷物件是否需要被回收,垃圾回收演算法;
  • 記憶體抖動、記憶體泄漏的監控;
  • Bitmap的大小、重復監控方案;
  • 設備分級方案,

一、物件的創建和回收

1.1、物件的創建

在java中物件的創建基本上就是一個new,但new的背后在記憶體中做了些什么?并且物件對記憶體有哪些影響?又是如何被回收的?…先了解這些基本知識點對后面記憶體性能優化有很大的幫助,

物件的創建基本上有以下幾點:

  1. 判斷物件對應的類是否加載、鏈接和初始化;
    例如我們利用new來創建一個User物件時,JVM虛擬機在收到new的指令時,會去方法區查詢該User類是否被參考,并檢查User類是否已經被加載,鏈接和初始化過,如果沒有,就需要先去執行類的加載程序,

  2. 為物件分配記憶體;
    在Java堆中劃分一塊記憶體分配給物件,分配記憶體會根據Java堆是否規整,分為兩種方式,指標碰撞和空閑串列

    • 指標碰撞

      使用指標碰撞的前提是Java堆的記憶體屬于規整模型,所謂指標碰撞,指的是利用一個指標將記憶體空間分為已被占用記憶體空閑記憶體,當為一個物件進行記憶體分配時,指標就向空閑記憶體一側移動,移動的距離與物件大小相等,如下圖所示:

    • 空閑串列

      空閑串列是在Java堆記憶體不完整的情況下使用的方式,已使用記憶體與空閑記憶體無規則,并且JVM另外維護了一張空閑記憶體的表,當有新物件需要分配記憶體時,就從空閑串列中查找一塊足夠該物件的記憶體,

  3. 處理并發安全問題;
    當物件創建很頻繁時,就需要去解決并發的問題,也就是執行緒安全,比如程式中多執行緒創建m和n兩個物件,給m物件分配記憶體的同時也會給n物件分配,如果這時候兩個物件分配的是同一塊記憶體,必然就出現了沖突, 為了解決這個并發的問題,JVM提供了兩種方式,

    • CAS演算法+失敗重試方式

      CAS是項樂觀鎖技術,當多個執行緒嘗試同時更新一個變數時,只有其中一個執行緒能夠更新變數的值,而其他的執行緒都是失敗的,但失敗的執行緒都不會被掛起,可以再次嘗試,直到成功為止,

    • 本地執行緒分配快取區-TLAB

      所謂本地執行緒分配快取區,就是當執行緒開啟時,就為每個執行緒在Eden區分配一塊記憶體,然后當執行緒內部創建物件時,就從自己的記憶體空間分配,若自己的記憶體不足或者用盡時,就開始從堆記憶體中分配,這個時候就是采用CAS的方式,

  4. 初始化記憶體空間;
    將分配到的記憶體,除物件頭以外都初始化為零值,這也是為什么物件的實體在Java代碼中不賦初始值就可以直接使用的原因,訪問的都是物件的零值,

  5. 設定物件的物件頭;
    將物件的所屬類,物件的HashCode以及物件的GC分代等資料存盤到物件的物件頭中,

  6. 執行init方法進行初始化
    執行init方法,初始化物件的成員變數,呼叫類的構造方法,到這里,一個物件就被創建了,

1.2、物件在JVM中的生命周期

  1. 創建階段,上面已經詳細給出物件的創建程序;
  2. 應用階段,當物件創建完成后,并分配給變數復制,狀態切換到應用階段;
  3. 不可見階段,在程式中找不到物件的任何強參考;
  4. 不可達階段,在程式中找不到物件的任何強參考,并且垃圾收集器發現物件不可達;
  5. 收集階段,垃圾收集器發現物件不可達,并且垃圾收集器已經準備好對該物件的記憶體空間進行重新進行分配;
  6. 終結階段,垃圾收集器回收該物件的空間,

1.3、物件的回收

1.3.1、判斷物件是否需要被回收

在對物件進行回收前,需要知道該物件是否是垃圾,而判斷一個物件是否需要被回收,有兩種方式,參考計數法和可達性分析演算法:

  1. 參考計數法
    所謂參考計數法,指的是物件會維護一個參考計數器,計算被參考的次數,如果被參考一次,計數器就+1,如果不在參考,則-1,知道計數器為0時,就說明該物件可以被回收了,
    如果堆中有兩個物件相互參考,那他們的計數器都為1,就不會被回收,會造成記憶體泄漏,這也是參考計數法的缺點,

  2. 可達性分析演算法
    該演算法基本思路就是GC Roots作為起點,從這個節點開始向下掃描,掃描到的物件即存活物件,未被掃描到的即需要被回收,
    GC Roots可以理解為由堆外指向堆內的參考, 一般而言,GC Roots包括(但不限于)以下幾種:

    1. Java 方法堆疊楨中的區域變數;
    2. 已加載類的靜態變數;
    3. JNI handles;
    4. 已啟動且未停止的 Java 執行緒,

了解了物件是否可回收,接下來就開始了解垃圾的回收演算法,

1.3.2、垃圾回收演算法

  1. 標記清除演算法
    對存活的物件進行標記,在最后掃描整個空間時,沒有被標記的物件就會被回收,整個程序如下圖所示:

    該演算法的缺點從上圖就可看到,清除垃圾物件后,會產生不連續的記憶體碎片,當后面需要分配較大的物件時,會因為無法找到足夠的連續記憶體空間,而觸發垃圾回收,如果記憶體還是不足,則會例外,
    標記壓縮演算法就解決了這個問題,

  2. 標記壓縮演算法
    對存活的物件進行標記,在最后掃描整個空間時,沒有被標記的物件就會被回收,并且進行記憶體碎片整理,整個程序如下圖所示:

    雖然標記壓縮演算法解決了標記清除演算法記憶體不規整的問題,但又存在新的問題,比如說,最后對記憶體空間的整理需要花費時間,且指標也需要不斷的重新移動,時間消耗會隨堆記憶體越來越大,

  3. 復制演算法
    復制演算法是為了解決對碎片的垃圾回收,該演算法一開始把堆一分為二,分為物件面和空閑面,程式在物件面為物件分配空間,當物件面滿了,就將每個存活物件復制到空閑面,這樣空閑面變成了物件面,物件面變成了空閑面,
    該演算法的執行效率比標記整理和標記清除的效率都高,但是每次只能利用50%的記憶體空間,

  4. 分代垃圾回收演算法
    分代垃圾回識訓制是根據不同物件的不同生命周期,采用不同的回收方法,提高回收效率,
    主要分為年輕代和老年代,
    年輕代: 用于存放新創建的物件,存活率較低的物件,經過多次GC后,該物件仍然存活,那么就會放入老年代,常用復制演算法
    老年代: 用于存放存活時間長的物件,常用標記清除或標記整理演算法

    演算法細節:

    • 物件新建,將存放在新生代的Eden區域,注意Suvivor區又分為兩塊區域,FromSuv和ToSuv;
    • 當年輕代Eden滿時,將會觸發Minor GC,如果物件仍然存活,物件將會被移動到Fromsuvivor空間,物件還是在新生代;
    • 再次發生minor GC,物件還存活,那么將會采用復制演算法,將物件移動到ToSuv區域,此時物件的年齡+1;
    • 再次發生minor GC,物件仍然存活,此時Survivor中跟物件Object同齡的物件還沒有達到Surivivor區的一半,所以還是會繼續采用復制演算法,將fromSuv和ToSuv的區域進行互換;
    • 當多次發生monorGC后,物件實體仍然存活,且此時,此時Survivor中跟物件Object同齡的物件達到Surivivor區的一半,那么物件實體將會移動到老年代區域,或者物件經過多次的回收,年齡達到了15歲,那么也會遷移到老年代,

磨刀不誤砍柴工,首先基本上了解了一些記憶體方面的知識點,那接下來就開始記憶體優化實踐,

二、記憶體優化實踐

記憶體優化主要分為幾個大方向,Bitmap優化記憶體泄漏記憶體抖動設備分級等,

2.1、記憶體抖動

記憶體抖動指的是記憶體頻繁分配和回收導致記憶體不穩定,頻繁GC,會導致卡頓,甚至會OOM,至于為什么會造成卡頓?是因為在GC時,會觸發STW(stop the world)機制,也就是在執行垃圾收集演算法時,應用程式的其他所有執行緒都被掛起(除了垃圾收集器之外),這個時候也就不會處理用戶的操作事件,從而出現卡頓,

下面將模擬一個記憶體抖動情況,并使用Memory profile進行記憶體抖動的分析, 這里自定義了一個小球加載中的影片效果:

在開始小球影片后,我們打開打開AS自帶的Memory,從下圖可以看到,記憶體的走勢是在上下起伏,并且記憶體也在不斷的增加,這就代表著記憶體在不斷的分配和回收,這就是記憶體抖動,

那記憶體抖動的具體問題出現在哪里?

為了分析記憶體抖動的問題所在,我們先選取一段記憶體抖動的地方,可以看到在我們選取的這段時間里,AS的profile 都顯示了每個物件的記憶體分配情況,如下所示,

從上面圖片中的紅色框里就可以了解到,App產生記憶體抖動的原因主要就是app heap的前幾項,而要匹配到專案中的代碼就需要一個一個查看占用記憶體多的模塊,

我們先選擇其中一個,在Allocation Call Stack模塊中清楚的看到具體所分配的堆疊,同時也找到了造成堆中實體物件多的源代碼,

頻繁創建物件實體的原代碼在com.fuusy.fuperformance.memory.view.WaveView的136行,

哦~~ ,原來是自定義View時,在onDraw中頻繁創建Paint所致,解決的辦法就是將paint作為全域變數,在外部創建,

這個案例只是記憶體抖動中一個小小的縮影,當專案越來越大時,排查的作業難度也隨之增加,這就要我們在平時開發時,就需要注意代碼細節問題,盡可能在coding的程序中就減少記憶體問題,

記憶體抖動的注意事項:

  1. 避免在回圈和頻繁呼叫的方法中創建物件;
  2. 使用物件池,如Handler、Glide中的物件池,

2.2、記憶體泄漏

記憶體泄漏指的是程式中已分配的記憶體由于某種原因未釋放或者無法釋放,造成系統記憶體的浪費,

造成記憶體泄漏的原因有很多,比如:

  • 長生命周期物件持有短生命周期物件的強參考,從而導致短生命周期物件無法被回收;
  • 異步執行緒持有短生命周期物件,如Handler、網路請求或者其他作業執行緒持有短生命周期物件;
  • 資源未及時關閉,如BroadcastReceiver、File、Cursor等;
  • 大量使用靜態變數;

當然,在實際專案中查找記憶體泄漏的原因的方式也有很多,比如主流工具LeakCanary、 MAT等,

2.3、Bitmap優化

Bitmap作為程式中記憶體占用的大戶,是必須優化的物件,之前寫過一篇關于Bitmap優化的文章「性能優化系列」不使用第三方庫,Bitmap的優化策略,可參考查看,

Bitmap除了基本優化外,其實還需要在coding的程序中,就將Bitmap記憶體問題扼殺在搖籃里,本篇文章就將從圖片大小監控,重復圖片監控兩個方向進行闡述,

2.3.1、Bitmap大小監控方案

Bitmap有一種從其尺寸上優化的手段,即當裝載圖片的容器例如ImageView只有100 * 100,而圖片的解析度為1800 * 800,這個時候將圖片直接放置在容器上,很容易OOM,同時也是對圖片和記憶體資源的一種浪費,當容器的寬高都很小于圖片的寬高,其實就需要對圖片進行尺寸上的壓縮.

而比較重要的點就是如何判斷Bitmap的尺寸符合圖片容器?

想到的第一種方法就是可以自定義一個ImageView,在View中去判斷圖片以及容器的大小,如果圖片太大,則進行尺寸上的壓碩訓優化,這種方式簡單實用,確實也解決了我們的問題,但在實際開發中,除了代碼侵入性強外,如果想要開發團隊中的每個人加載ImageView時都使用這個控制元件,也是一件很難展開的事情,

為了更加解耦性和減少代碼侵入性,這里介紹一種Bitmap大小的監控方案-ARTHook

ARTHook通俗來講就是統一添加代碼修改原有邏輯,基于ARTHook的框架有很多,這里介紹一個常用框架-Epic, Epic就是ART上的 Dexposed(支持 Android 5.0 ~ 11),它可以攔截本行程內部幾乎任意的 Java 方法呼叫,可用于實作 AOP 編程、運行時插樁、性能分析等,

下面就基于Epic框架進行Bitmap大小監控的撰寫,

添加依賴

dependencies {
     implementation 'me.weishu:epic:0.11.0'
}

新建一個Hook類用來撰寫圖片大小讀取,比較,繼承XC_MethodHook,并實作其afterHookedMethod,在其方法內實作需要監控的物件,具體監控的方法呼叫后將會呼叫該方法,

class BitmapARTHook : XC_MethodHook() {
    @Throws(Throwable::class)

    override fun afterHookedMethod(param: MethodHookParam) {
        super.afterHookedMethod(param)

        val imageView = param.thisObject as ImageView
        checkBitmap(imageView, (param.thisObject as ImageView).drawable)
    }
}

在afterHookedMethod方法里實作我們需要的邏輯,這里需要判斷圖片大小并給出警示,那就加上圖片大小的監測方法,

if (bitmap.width >= width shl 1 && bitmap.height >= height shl 1) {
    warn(
        bitmap.width,
        bitmap.height,
        width,
        height,
        RuntimeException("Bitmap size too large")
    )
}

詳細代碼請參考fuusy/FuPerformance

寫個測驗來看看最終的效果,在xml中新建一個ImageView,寬高都設定為100dp,在Activity中將一張解析度為1300* 500的圖片設定到ImageView中,

val imageView = findViewById<ImageView>(R.id.iv_bitmap)
BitmapFactory.decodeResource(resources, R.mipmap.bitmap1).apply {
    imageView.setImageBitmap(this)
}

運行后在終端可以看到圖片大小的提示資訊,

2.3.2、重復圖片監控

張邵文在高手課中提及重復圖片指的是 Bitmap 的像素資料完全一致,但是有多個不同的物件存在,給出的方案是使用HAHA 庫快速判斷記憶體中是否存在重復的圖片,并且將這些重復圖片的 PNG、堆疊等資訊輸出,

需要注意的是需要使用8.0以下的機器,因為8.0以后Bitmap中的buffer已經放到native記憶體中, 核心代碼與思路如下:

            //打開hprof檔案
            HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
            HprofParser parser = new HprofParser(buffer);
            //決議獲得快照
            com.squareup.haha.perflib.Snapshot snapshot = parser.parse();
            snapshot.computeDominators();

            //獲得Bitmap Class
            Collection<ClassObj> bitmapClasses = snapshot.findClasses("android.graphics.Bitmap");
            //獲取堆資料,這里包括專案app、系統、default heap的資訊,需要進行過濾
            Collection<Heap> heaps = snapshot.getHeaps();

            long startTime = System.currentTimeMillis();
            Tools.print("---------------------- 開始 ----------------------- ");
            for (Heap heap : heaps) {
                // 只需要分析app和default heap即可
                if (!heap.getName().equals("app") && !heap.getName().equals("default")) {
                    continue;
                }
                Tools.print("HeapName:" + heap.getName());

                Map<Integer, List<AnalyzerResult>> map = new HashMap<>();

                for (ClassObj clazz : bitmapClasses) {
                    //從heap中獲得所有的Bitmap實體
                    List<Instance> instances = clazz.getHeapInstances(heap.getId());

                    for (int i = 0; i < instances.size(); i++) {
                        //從GcRoot開始遍歷搜索,Integer.MAX_VALUE代表無法被搜索到,說明物件沒被參考可以被回收
                        if (instances.get(i).getDistanceToGcRoot() == Integer.MAX_VALUE) {
                            continue;
                        }
                        List<AnalyzerResult> analyzerResults;
                        int curHashCode = Tools.getHashCodeByInstance(instances.get(i));
                        AnalyzerResult result = Tools.getAnalyzerResult(instances.get(i));
                        result.setInstance(instances.get(i));
                        if (map.get(curHashCode) == null){
                            analyzerResults = new ArrayList<>();
                        }else {
                            analyzerResults = map.get(curHashCode);
                        }
                        analyzerResults.add(result);
                        map.put(curHashCode, analyzerResults);
                    }
                }

                if (map.isEmpty()){
                    Tools.print("當前head暫無bitmap物件");
                }

                for (Map.Entry<Integer, List<AnalyzerResult>> entry : map.entrySet()){
                    List<AnalyzerResult> analyzerResults = entry.getValue();
                    //去除size小于2的,剩余的為重復圖片,
                    if (analyzerResults.size() < 2){
                        continue;
                    }
                }

            }

2.4、設備分級

所謂設備分級,指的是根據不同設備環境來考慮不同的記憶體優化策略,目前市場上手機層出不窮,幾乎每一年都會對手機性能進行提升,但是對于性能較差的手機,app應用的運行狀況就會較差,

對于低端機用戶可以關閉復雜的影片,或者是某些功能;使用 565 格式的圖片,使用更小的快取記憶體等,在現實環境下,不是每個用戶的設備都跟我們的測驗機一樣高端,在開發程序我們要學會思考功能要不要對低端機開啟、在系統資源吃緊的時候能不能做降級,- 張邵文

那如何進行設備分級?

Facebook其實開發了一個 設備年份類別庫,它使用簡單的演算法將設備的 RAM、CPU 內核和時鐘速度與這些特性被認為是高端的年份相匹配,使得我們能夠根據手機的硬體功能撰寫不同的邏輯,

RAMconditionYear Class
768MB1 core2009
2+ cores2010
1GB<1.3GHz2011
1.3GHz+2012
1.5GB<1.8GHz2012
1.8GHz+2013
2GB2013
3GB2014
5GB2015
more2016

而針對設備性能,我們能做的優化就如上面張邵文所說,

  • 是否關閉影片;
  • 圖片質量分級;
  • 為低性能設備設計簡版應用,

設備分級實踐

添加設備年份庫的依賴

implementation 'com.facebook.device.yearclass:yearclass:2.1.0'

獲取設備年限以及進行設備分級,

val year = YearClass.get(applicationContext)
Log.d(TAG, "Year: $year")
when {
    year >= 2013 -> {
        // Do advanced animation
    }
    year > 2010 -> {
        // Do simple animation
    }
    else -> {
        // Phone too slow, don't do any animations
    }
}

三、總結

記憶體優化是一個很大的命題,從記憶體在虛擬機中的創建與回收,到LeakCanary、MAT分析工具的使用,再到記憶體泄漏、記憶體抖動以及Bitmap的監控和優化,再分細一點,線上線下的監控方案、優化手段以及如何一直保持記憶體的穩定,這些都是記憶體優化需要關注的問題,

感謝閱讀,這是一個性能優化系列,請持續關注,

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

標籤:其他

上一篇:iOS之深入決議UmbrellaFramework的封裝與應用

下一篇:Netty——實作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