Androidは多様な設計の下で怠惰なロードメカニズムを実現する

15335 ワード

前言
この間、自分の練習項目で怠け者のロードメカニズムを使いたいと思って、多くの資料を見て、View Pager+Fragmentの組み合わせで実現された怠け者のロードだけを紹介しましたが、今ではFragmentmanagerがホームページの複数のFragmentの表示と非表示を管理し、メインインタフェースのあるまたは複数のFragmentに複数のFragment+ViewPagerがネストされています(詳細は下図参照).1つ目に当てはまる方法は2つ目の状況を直接解決することはできないので、この文章を書いて、踏んだいくつかの穴を記録して、同じ私のような初心者に一つの考え方を参考にしてほしい(間違いや不適切なところがあれば、各先輩がコメントエリアで指摘してくれたらありがたいです!).
怠惰ロードについて
1.怠惰ロードとは?
怠惰ロードは遅延ロードとも呼ばれ、アプリでは現在のページだけをロードすることを指し、アプリの性能を最適化する方法です.
2.なぜ怠惰なロードを使うのですか?
  • APPの性能を最適化し、ユーザー体験を向上させる:ユーザーがあるページを開くと、他のページをプリロードする場合、データセットが小さいか、ネットワーク性能が優れている場合はまだ良いが、データセットが大きすぎるか、ネットワーク性能が悪い場合、ユーザーが待つ時間が長くなり、APPインタフェースに明らかなヒステリシスが発生し、ユーザーの体験に深刻な影響を及ぼす.
  • 無効なリソースのロードを減らし、サーバの圧力を減らし、ユーザーの流量を節約する:ユーザーが特定のページを閲覧したいか、よく閲覧したい場合、プリロード方式を使用すると、リソースの浪費をもたらし、サーバの圧力を増加させるなど.

  • 怠惰なロードを実現
    1.ViewPager+Fragmentの場合
    1.1問題
    我々が普段開発している中で、ViewPager+Fragmentの組み合わせを用いて左右にスライドするページ設計(上図)を実現することが多いが、ViewPgerにはプリロードメカニズムがあり、デフォルトではViewPagerの現在位置の左右隣接ページを初期化(通称プリロード)し、setOffscreenPageLimit(0)を設定しても効果がなく、プリロードする.ポイントインソースコードでは、setOffscreenPageLimit()メソッドをアクティブに設定しなければ、mOffscreenPageLimitのデフォルト値は1であり、0(1未満)の値が設定されていても、mOffscreenPageLimit=limit=1に従って処理されることが分かった.
    private int mOffscreenPageLimit = 1;//     ,     1
    
    public int getOffscreenPageLimit() {
            return this.mOffscreenPageLimit;
        }
        
    public void setOffscreenPageLimit(int limit) {
            if (limit < 1) {//   0,      1
                Log.w("ViewPager", "Requested offscreen page limit " + limit + " too small; defaulting to " + 1);
                limit = 1;
            }
            if (limit != this.mOffscreenPageLimit) {
                this.mOffscreenPageLimit = limit;
                this.populate();
            }
    

    1.2解決の考え方Fragmentには非ライフサイクルのsetUserVisibleHint(boolean isVisibleToUser)コールバックメソッドがあり、ViewPagerFragmentにネストされると機能し、ViewPagerを切り替えるとメソッドも呼び出され、パラメータisVisibleToUsertrueであることは、現在のFragmentがユーザに表示されていることを意味し、そうでない場合は表示されません.だから最も簡単な考え方:Fragmentが見える時にデータをロードし、見えない時にデータをロードさせない.抽象BaseFragmentを作成し、カプセル化します.まず,isVisibleToUser変数を導入し,現在のFragment対のユーザの可視状態を保存する.また、いくつかの注意すべき点があります.
  • setUserVisibleHint(boolean isVisibleToUser)メソッドのコールバックタイミングは、Fragmentのライフサイクルと正確に関連していない.例えば、コールバックタイミングはonCreateView()メソッドの後、onCreateView()メソッドの前にある可能性がある.したがって、ビューの作成が完了したかどうかを判断するフラグビットisPrepareViewを導入する必要があります.そうしないと、空のポインタ異常が発生しやすくなります.この変数をfalseに初期化し、onViewCreated()、すなわちview作成が完了した後、trueに割り当てます.
  • データ初期化は1回のみロードすべきであるため、2番目のフラグビット、isInitDataを導入し、最初はfalse,であり、データロードが完了した後、trueに割り当てられ、次回このページに戻るときに自動的にロードされない.これにより,我々の怠惰負荷法はすべての条件を考慮した.すなわち、isVisibleToUsertrueisInitDatafalseisPrepareViewtrueである場合には、データのロードが行われ、ロード後に繰り返し呼び出しを防止するためにisInitDatatrueに割り当てられる.
  • 怠け者のロードデータを抽出する方法ですが、この方法はいつ呼び出されますか?まず、setUserVisibleHint(boolean isVisibleToUser)メソッドで呼び出さなければならない.すなわち、Fragmentが可視から非可視に変化し、非可視に変化したときにコールバックする.次に、無視しやすい点です.最初のFragmentについて、setUserVisibleHint(boolean isVisibleToUser )メソッドがonCreateView()より前に呼び出されると、怠惰ロードメソッドがsetUserVisibleHint(boolean isVisibleToUser )でのみ呼び出されると、Fragmentは、アクティブに切り替えられた後にのみデータをロードすることができ、これは不可能であるに違いない.したがって、viewの作成が完了した後にも、1回の呼び出しを行う必要がある.考えてみればonActivityCreated()の方法の中で一番適当です.我々は継承時にonViewCreated()メソッドでいくつかの初期化を行えばよいので,衝突を起こさない.

  • 1.3 BaseFragmentコード実装
    public abstract class BaseFragment extends Fragment {
    
        private Boolean isInitData = false; //   ,         
        private Boolean isVisibleToUser = false; //   ,  fragment    
        private Boolean isPrepareView = false; //   ,  view              
    
        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            return inflater.inflate(getLayoutId(),container,false);
        }
    
        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            isPrepareView=true;//  view      ,    true
        }
        /**
         *      
         */
        public void lazyInitData(){
            if(!isInitData && isVisibleToUser && isPrepareView){//           ,  fragment    ,view      
                initData();//    
                isInitData=true;//               true
            }
        }
    
        @Override
        public void setUserVisibleHint(boolean isVisibleToUser) {
            super.setUserVisibleHint(isVisibleToUser);
            this.isVisibleToUser=isVisibleToUser;// fragment         isVisibleToUser
            lazyInitData();//   
        }
    
        /**
         * fragment     onViewCreated                             
         * @param savedInstanceState
         */
        @Override
        public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            lazyInitData();//   
        }
    
        /**
         *      
         * @return        id
          */
        abstract int getLayoutId();
    
        /**
         *        ,     
         */
        abstract void initData();
    }
    

    2.Fragment+ViewPager+Fragmentの場合
    2.1問題点
    図2のように、このようなFragmentmanagerによってホーム面が管理されている複数のFragmentの表示と非表示について、そのうちのいずれかのFragmentにまた複数のFragmentがネストされている場合(上記図のように)、上記の案では解決できないが、ホーム面のFragmentが直接上のBaseFragmentを継承すると、ホームのいくつかのFragmentがロードされない現象が現れるのはなぜなのか、道理でFragmentが見えるはずですが、データをロードする判断ロジックは大丈夫でしょう.そして、上のdemoも成功しました.最終的に、問題はsetUserVisibleHint()という方法で、そのソースコード発見注釈にこのような言葉があることに気づいた.
    This may be used by the system to prioritize operations such as fragment lifecycle updates or loader ordering behavior.
    

    すなわち、これは、Fragmentのライフサイクルの更新のような秩序化されたFragmentのセットに使用される可能性がある.この方法は1つのpagerで呼び出されることを望んでいるので、FragmentPagerAdapterはこれを使用することができますが、ホームページのいくつかのFragmentFragmentmanagerによって管理されているので、setUserVisibleHint()は呼び出されませんが、私たちが設定したisVisibleToUser=falseのデフォルト値はずっと変わらないので、lazyInitData()の方法はずっと実行されません.
     /**
         *      
         */
        public void lazyInitData(){
            if(!isInitData && isVisibleToUser && isPrepareView){//  isVisibleToUser    false,  iniData()       
                initData();//    
                isInitData=true;
            }
        }
        
        
    

    2.2解決の考え方
    ここで私の処理方法は、lazyInitData()に処理ロジックを追加し、以下のようにします.
    /**
         *      
         */
        public void lazyInitData(){
            if(!isInitData  && isVisibleToUser && isPrepareView){//           ,  fragment    ,view      
                initData();//    
                isInitData=true;//               true
            }else if (!isInitData && getParentFragment()==null && isPrepareView){
                initData();
                isInitData=true;
            }
        }
        
        /**
         * Fragment      
         * @param hidden
         */
        @Override
        public void onHiddenChanged(boolean hidden) {
            super.onHiddenChanged(hidden);
            if (!hidden) {
            lazyInitData(); 
            }
        }
    

    ホームページの複数のFragmentについては、2番目の判断論理処理(isVisibleToUserの値がfalseに等しいため)のみが行われ、ネストされたFragmentについては、最初の処理論理(getParentFragment()!=nullであるため)のみが通過し、onHiddenChanged()法によってlazyInitData()法がロードされ、これによって処理されるようになった.
    しかし、この場合、1つのAPPで1つ目、2つ目が併存している場合、このコードは1つ目の場合に適していません.1つ目の場合、isVisibleToUserfalseであると判定された場合、1つ目の処理ロジックは実行されませんが、getParentFragment()はずっとnullに等しいので、2つ目の判断ロジックを実行し、プリロードされます.
    この場合、私の処理方法は、Fragmentごとにフラグ値を設定し、1つ目の場合はtrue、2つ目の場合はfalseを設定し、それぞれの判断ロジックを処理します.コードは次のとおりです.
     /**
         *      
         */
        public void lazyInitData(){
            if(setFragmentTarget()){
                if(!isInitData && isVisibleToUser && isPrepareView){//           ,  fragment    ,view      
                    initData();//    
                    isInitData=true;//               true
                }
            }else {
                if(!isInitData && isVisibleToUser && isPrepareView){//           ,  fragment    ,view      
                    initData();//    
                    isInitData=true;//               true
                }else if (!isInitData && getParentFragment()==null && isPrepareView ){
                    initData();
                    isInitData=true;
                }
            }
        }
        
         /**
         *   Fragment target,     
         */
        abstract boolean setFragmentTarget();
    

    このような処理を経て、第1のケースと第2のケース、または両者が併存している場合、1つのbaseを継承して怠け者のロードを実現することが保証される.
    2.3 BaseFragmentTwo最終コード実装
    
    public abstract class BaseFragmentTwo extends Fragment {
        private Boolean isInitData = false; //   ,         
        private Boolean isVisibleToUser = false; //   ,  fragment    
        private Boolean isPrepareView = false; //   ,  view              
    
        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            return inflater.inflate(getLayoutId(),container,false);
        }
    
        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            isPrepareView=true;//  view      ,    true
        }
        /**
         *      
         */
        public void lazyInitData(){
            if(setFragmentTarget()){
                if(!isInitData && isVisibleToUser && isPrepareView){//           ,  fragment    ,view      
                    initData();//    
                    isInitData=true;//               true
                }
            }else {
                if(!isInitData && isVisibleToUser && isPrepareView){//           ,  fragment    ,view      
                    initData();//    
                    isInitData=true;//               true
                }else if (!isInitData && getParentFragment()==null && isPrepareView ){
                    initData();
                    isInitData=true;
                }
            }
        }
    
    
    
        @Override
        public void onHiddenChanged(boolean hidden) {
            super.onHiddenChanged(hidden);
            if (!hidden) { lazyInitData(); }
        }
    
        @Override
        public void setUserVisibleHint(boolean isVisibleToUser) {
            super.setUserVisibleHint(isVisibleToUser);
            this.isVisibleToUser=isVisibleToUser;// fragment         isVisibleToUser
            lazyInitData();//     
        }
    
        /**
         * fragment     onViewCreated                             
         * @param savedInstanceState
         */
        @Override
        public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            lazyInitData();
        }
    
        /**
         *      
         * @return        id
          */
        abstract int getLayoutId();
    
        /**
         *        ,     
         */
        abstract void initData();
    
        /**
         *   Fragment target,     
         */
        abstract boolean setFragmentTarget();
    
    }
    
    
    

    その他の注意事項:
    viewpageradapterを設置する場合、必ずgetChildFragmentManager()に伝えなければならない.そうしないと、getParentFragment()はずっとnullに等しくなり、lazyInitData()の判断に影響し、怠惰な負荷が混乱し、無効になる.
    ②demoではViewPager+Tablayoutの組み合わせを使用していますが、Tablayoutを使用する場合はstyles.xmlのトピックがTheme.AppCompat.Light.NoActionBarまたはTheme.AppCompat.LightなどのTheme.AppCompat.XXXのトピックを使用することを保証しなければなりません.
    プロジェクトアドレス
    プロジェクトアドレス
    転載先:https://juejin.im/post/5cf9d5a66fb9a07eb15d473a