快速實現android版抖音主介面的心得

DK_BurNIng發表於2018-10-06

如何快速確定競品某個介面的實現方式?

當你收到產品一個需求是模仿某個競品且時間很短沒有過多時間給你調研技術方案的時候,如何儘快確定這個功能的技術方案呢? 這裡我給出我自己的一個小竅門,可以避免走彎路,比如先確定的方案到最後發現各種各樣的原因方案不行,導致最後臨時變更 方案,需求延期釋出的悲劇。。。、

  • Android\sdk\tools 首先去這個目錄下 找到 monitor這個可執行檔案,然後開啟你想模仿的競品頁面,然後執行這個檔案

快速實現android版抖音主介面的心得

比方說抖音的主介面這種滑動整屏的東西,相信之前很多人都沒做過,很多人可能以為recyclerview可以拿來做這種場景, 實際上recyclerview確實可以做,但是抖音這裡能看出來卻是使用了viewpager來做。作為一個模仿者,為了少走彎路, 那麼顯然 我們也是優先要選擇viewpager的。

  • 其次用這個軟體,還可以看出來介面上的某個控制元件所屬的id。這個id很重要,後面我們反編譯的時候可以通過全域性 搜尋這個id 找到大概的原始碼位置,雖然都是混淆過的程式碼,但是大致還是能分析出一些技術細節的。 這裡注意一點:很多阿里系的產品用這個方法你分析介面的時候你會發現根本沒有id可以捕捉到,放心這並不是什麼 阿里的黑科技反 反編譯技術,而是阿里使用了諸如weex react native等類似的方案,當使用這種方案的時候, 我們是找不到id的噢,如果你大致瞭解weex react-native的實現方案的話你應該明白我在說些什麼

  • 反編譯就用jadx就可以,足夠使用。在用jadx反編譯之後搜尋你之前的id,就可以大概找到對應的程式碼位置,我就是通過 這個手段,確定了抖音使用的垂直的viewpager到底是出自於github上哪個開源控制元件。。。為了避免法律糾紛我這裡就 不明說是用的哪個開源的垂直滑動的viewpager,有興趣的同學可以自己玩一玩。

  • adb shell dumpsys activity | grep mFocusedActivity 這個命令是神器,可以把你當前正在顯示的activity的名字 打出來,方便你更加迅速的定位競品的程式碼。。

快速實現android版抖音主介面的心得

不要小看這條命令哦,很多功能當你不知道競品是彈了一個popwindow還是一個dialoagfragment還是啟動了一個透明的activity的時候,這個方法就很有用了,可以避免走很多彎路。
複製程式碼

總結一下模仿抖音主介面的時候遇到的一些問題和解決方案。

  • 抖音整體技術方案是 垂直的viewpager+下拉重新整理上拉載入控制元件+fragment的組合方案。不管是前者還是後者我們都有成熟的開源方案可以選擇,我們只是把他合併起來而已。這第一步其實並不難。

  • 儘量使用FragmentStatePagerAdapter。FragmentPagerAdapter不推薦使用,可能平時我們用FragmentPagerAdapter更多, 但是要知道FragmentPagerAdapter是不會釋放記憶體的,你不可見的fragment也是常駐記憶體的,像抖音這種幾乎無限滑動的 短視訊方案,使用FragmentPagerAdapter肯定是爆記憶體oom的。而用 FragmentStatePagerAdapter的話,對於不可見的 fragment,系統是自動釋放記憶體的,記憶體裡只會保留你能看到的fragment 和這個看到的fragment前後2個fragmnt。當然 這個值可以動態設定,但是至少都會保留3個fragment。

快速實現android版抖音主介面的心得

  • fragmentStatePagerAdapter.notifyDataSetChanged()失效? 有時候,notifyDataSetChanged方法呼叫了,介面卻沒有變化?實際上對於fragmentStatePagerAdapter來說,介面重新整理不重新整理 重繪不重繪 主要取決於getItemPosition方法的返回值,預設是返回 POSITION_UNCHANGED 也就是不重繪。只有 返回POSITION_NONE的時候才會重繪介面,重新繪製一遍fragemnt。 很多人不太理解什麼意思,我簡單描述一下場景:

    比如說,我們剛進抖音的主介面,假設返回了10條視訊資料,我們預設播第一條。展示的是第一個fragment,對吧。 然後這個時候我們下拉重新整理又來了10條資料,我們應該播放的是新來的這10條資料的第一條。於是你等待介面返回 以後把新來的10條資料插在了我們的資料來源這個陣列的 頭部。 然後呼叫了notifyDataSetChanged這個方法。 如果你沒有重寫getItemPosition的方法的話,這個方法預設返回POSITION_UNCHANGED,這是fragmentStatePagerAdapter就認為 我這個介面不需要重繪。所以你還是在不停的播放老的視訊。 有人可能會問,我們一直載入更多的話為什麼不會出現這種情況?

    比方說現在的載入更多其實都是預載入,比如我們一頁是返回10條資料,當我們滑到第5條資料的時候 我們可能就會自動請求 下一頁資料了。所以我們不停的滑動viewpager 往後面滑,因為position在不停的變化,所以是不斷的有新的fragment進來的。 所以不管getItemPosition 的值如何變化,針對此種情況我們都會重新整理介面的。

    但是對於下拉重新整理這種情況就不行,因為我們預設載入的是第一條資料,我們記憶體裡面已經有了這一條資料了,對於position 位置為0的fragment來說,他已經在記憶體裡了,等我們下拉重新整理來了新資料以後,雖然我們呼叫了notifyDataSetChanged方法, 但是我們發現這個位置為0的fragment 記憶體裡已經有了啊,getItemPosition又返回了POSITION_UNCHANGED,那我就不重繪了, 這就是一個容易出bug的地方。

  • 既然如此我們getItemPosition固定返回POSITION_NONE行不行? 答案是不行的,固定返回POSITION_NONE這個值,雖然可以解決下拉重新整理 介面不重新整理的問題,但是會引發新的問題。 主要有2: 第一,固定返回POSITION_NONE 意味著每次notifyDataSetChanged 被呼叫的時候,我們記憶體裡存在的三個fragment 都要重新繪製 這樣的成本太大,低端手機明顯會卡,android大部分視訊播放都是軟解碼方案,這樣的效能不行。

    第二:還是上面的預載入,比方說我們第一頁返回了10條資料,當我們滑到第五條資料的時候,我們預載入預先請求了第二頁的時候 然後第二頁的資料回來以後 我們呼叫了notifyDataSetChanged,注意這個時候 我們可能第五條資料對應的視訊我們還沒看完呢,比如這條短視訊我們才看到第六秒,結果整個介面突然重繪了,直接又重新 從第一秒開始播放。。這個體驗明顯不可接受。

    所以我們要做的就是 在需要的時候返回POSITION_NONE 不需要的時候返回 POSITION_UNCHANGED ,具體的邏輯可以根據你們自己的業務進行相應的調整。

    比方說我們可以判斷一下,如果源資料也就是mData裡面的id 和正在播放的fragmetn裡面的id 相等話,我們就判定不需要重新整理介面 否則不相等,就刷一下,剛好對應載入更多和下拉重新整理的2個場景。

  • 如何定位記憶體洩漏的問題? 對於播放器來說,初次接觸的團隊如果沒有經驗,即使有b站開源播放器的幫助也會發生記憶體洩漏的問題,比方說我們 繪製一個播放介面,總免不了要畫進度條,要展示倒數計時,textview預設的跑馬燈效果那麼差,說不定還要自己寫個自定義view 來完成跑馬燈的特效,這些都免不了使用執行緒,handler,timer等等容易發生記憶體洩漏的東西。所以當不停的滑動的時候, 如果被滑走的fragment沒有及時被釋放掉,那上線就肯定會發生oom的問題。對於android studio 3.0或者以上的版本來說:

快速實現android版抖音主介面的心得

快速實現android版抖音主介面的心得

快速實現android版抖音主介面的心得

嗯,既然mat都能讀了,剩下的就不囉嗦了,網上一搜一大堆。

  • 如何根據index 取對應的fragment? 這個也是比較小眾的一個知識點,對於fragmentStatePagerAdapter來說,我們知道除了當前在使用的fragment,我們 還有這個fragment前後2個fragment,對於滑動操作來說,我們至少要完成 滑動到下一個fragmetn 要停止前面一個fragment 的視訊播放和動畫播放等等。 所以根據index 來取fragment物件就變的十分重要。

快速實現android版抖音主介面的心得

這裡給出反射的實現:

 public static Fragment getIndexFragment(FragmentStatePagerAdapter fragmentStatePagerAdapter, int index) {
        try {
            Field privateArrayList = FragmentStatePagerAdapter.class.getDeclaredField("mFragments");
            privateArrayList.setAccessible(true);
            ArrayList<Fragment> mFragments = (ArrayList<Fragment>) privateArrayList.get(fragmentStatePagerAdapter);
            return mFragments.size() > 0 ? mFragments.get(index) : null;

        } catch (NoSuchFieldException e) {
        } catch (IllegalAccessException e) {
        }
        return null;
    }
複製程式碼

相關文章