Androidは多様な設計の下で怠惰なロードメカニズムを実現する
15335 ワード
前言
この間、自分の練習項目で怠け者のロードメカニズムを使いたいと思って、多くの資料を見て、
怠惰ロードについて
1.怠惰ロードとは?
怠惰ロードは遅延ロードとも呼ばれ、アプリでは現在のページだけをロードすることを指し、アプリの性能を最適化する方法です.
2.なぜ怠惰なロードを使うのですか? APPの性能を最適化し、ユーザー体験を向上させる:ユーザーがあるページを開くと、他のページをプリロードする場合、データセットが小さいか、ネットワーク性能が優れている場合はまだ良いが、データセットが大きすぎるか、ネットワーク性能が悪い場合、ユーザーが待つ時間が長くなり、APPインタフェースに明らかなヒステリシスが発生し、ユーザーの体験に深刻な影響を及ぼす. 無効なリソースのロードを減らし、サーバの圧力を減らし、ユーザーの流量を節約する:ユーザーが特定のページを閲覧したいか、よく閲覧したい場合、プリロード方式を使用すると、リソースの浪費をもたらし、サーバの圧力を増加させるなど.
怠惰なロードを実現
1.ViewPager+Fragmentの場合
1.1問題
我々が普段開発している中で、
1.2解決の考え方 データ初期化は1回のみロードすべきであるため、2番目のフラグビット、 怠け者のロードデータを抽出する方法ですが、この方法はいつ呼び出されますか?まず、
1.3 BaseFragmentコード実装
2.Fragment+ViewPager+Fragmentの場合
2.1問題点
図2のように、このような
すなわち、これは、
2.2解決の考え方
ここで私の処理方法は、lazyInitData()に処理ロジックを追加し、以下のようにします.
ホームページの複数の
しかし、この場合、1つのAPPで1つ目、2つ目が併存している場合、このコードは1つ目の場合に適していません.1つ目の場合、
この場合、私の処理方法は、Fragmentごとにフラグ値を設定し、1つ目の場合はtrue、2つ目の場合はfalseを設定し、それぞれの判断ロジックを処理します.コードは次のとおりです.
このような処理を経て、第1のケースと第2のケース、または両者が併存している場合、1つのbaseを継承して怠け者のロードを実現することが保証される.
2.3 BaseFragmentTwo最終コード実装
その他の注意事項:
①
②demoでは
プロジェクトアドレス
プロジェクトアドレス
転載先:https://juejin.im/post/5cf9d5a66fb9a07eb15d473a
この間、自分の練習項目で怠け者のロードメカニズムを使いたいと思って、多くの資料を見て、
View Pager
+Fragment
の組み合わせで実現された怠け者のロードだけを紹介しましたが、今ではFragmentmanager
がホームページの複数のFragment
の表示と非表示を管理し、メインインタフェースのあるまたは複数のFragment
に複数のFragment
+ViewPager
がネストされています(詳細は下図参照).1つ目に当てはまる方法は2つ目の状況を直接解決することはできないので、この文章を書いて、踏んだいくつかの穴を記録して、同じ私のような初心者に一つの考え方を参考にしてほしい(間違いや不適切なところがあれば、各先輩がコメントエリアで指摘してくれたらありがたいです!).怠惰ロードについて
1.怠惰ロードとは?
怠惰ロードは遅延ロードとも呼ばれ、アプリでは現在のページだけをロードすることを指し、アプリの性能を最適化する方法です.
2.なぜ怠惰なロードを使うのですか?
怠惰なロードを実現
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)
コールバックメソッドがあり、ViewPager
がFragment
にネストされると機能し、ViewPager
を切り替えるとメソッドも呼び出され、パラメータisVisibleToUser
がtrue
であることは、現在のFragment
がユーザに表示されていることを意味し、そうでない場合は表示されません.だから最も簡単な考え方:Fragment
が見える時にデータをロードし、見えない時にデータをロードさせない.抽象BaseFragment
を作成し、カプセル化します.まず,isVisibleToUser
変数を導入し,現在のFragment
対のユーザの可視状態を保存する.また、いくつかの注意すべき点があります.setUserVisibleHint(boolean isVisibleToUser)
メソッドのコールバックタイミングは、Fragment
のライフサイクルと正確に関連していない.例えば、コールバックタイミングはonCreateView()
メソッドの後、onCreateView()
メソッドの前にある可能性がある.したがって、ビューの作成が完了したかどうかを判断するフラグビットisPrepareView
を導入する必要があります.そうしないと、空のポインタ異常が発生しやすくなります.この変数をfalse
に初期化し、onViewCreated()
、すなわちview作成が完了した後、true
に割り当てます.isInitData
を導入し、最初はfalse,
であり、データロードが完了した後、true
に割り当てられ、次回このページに戻るときに自動的にロードされない.これにより,我々の怠惰負荷法はすべての条件を考慮した.すなわち、isVisibleToUser
がtrue
isInitData
がfalse
isPrepareView
がtrue
である場合には、データのロードが行われ、ロード後に繰り返し呼び出しを防止するためにisInitData
がtrue
に割り当てられる.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
はこれを使用することができますが、ホームページのいくつかのFragment
はFragmentmanager
によって管理されているので、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つ目の場合、
isVisibleToUser
がfalse
であると判定された場合、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();
}
その他の注意事項:
①
viewpager
にadapter
を設置する場合、必ず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