Android Fragmentary Knowledge Record 5

Keywords: Fragment Android xml

1. isAdded Method in Fragment

When set Retain Instance (true) is set in Fragment, the Fragment can be temporarily separated from Activity when the device rotates, etc.
If Fragment holds background threads at this point, or AsyncTask and others need to use Fragment's Context and so on, errors may occur.
To this end, the isAdded interface is defined in Fragment to determine whether Fragment has been bound to an Activity.

2. Basic usage of AsyncTask

AsyncTask is mainly used to perform time-consuming operations in the background and return the execution results to UI threads.

The definition of AsyncTask is as follows. It can be seen that AsyncTask is an abstract class.
Therefore, in practice, a subclass inheriting this class is usually defined.

public abstract class AsyncTask<Params, Progress, Result> {
    ..........
}

As you can see from the above code, AsyncTask defines three generic types: Params, Progress and Result. Among them:
Params represents input parameters when starting AsyncTask, such as the URL of an HTTP request.
Progress represents the percentage of background tasks performed, such as Integer.
Result represents the final result of a background task, such as Long.

An example of Android's use is as follows:

//The input parameter is URL, the progress update parameter is Integer, and the return result is Long.
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
    //Execute in the background thread, noting that the input parameter is URL and the output is long
    protected Long doInBackground(URL... urls) {
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++) {
            //Perform download operations
            totalSize += Downloader.downloadFile(urls[i]);

            //Notify the main thread of update progress
            publishProgress((int) ((i / (float) count) * 100));

            // Escape early if cancel() is called
            //If AsyncTask is detected to be cancelled, the task is stopped
            if (isCancelled()) break;
      }
      return totalSize;
    }

    //After calling publishProgress, call back the interface
    protected void onProgressUpdate(Integer... progress) {
        //Call methods in the main thread to update progress
        setProgressPercent(progress[0]);
    }

    //After the doInBackground is executed, this interface is called
    protected void onPostExecute(Long result) {
        showDialog("Downloaded " + result + " bytes");
    }
}

For the example above, using Download FilesTask is similar to:

.............
new DownloadFilesTask().execute(url1, url2, url3);
.............

The way to cancel Download FilesTask is similar to:

.............
//Download FilesTask saves the created AsyncTask
//When the parameter is false, the doInBackground detects isCancelled before stopping the task
//When the parameter is true, AsyncTask will stop immediately
downloadFilesTask.cancel(false);
.............

3. Basic usage of AsyncTaskLoader

When loading data with AsyncTask, if the device configuration changes or the user clicks the back button, etc.
The life cycle of AsyncTask needs to be managed, and download data needs to be saved.

At this point, consider using AsyncTaskLoader, which inherits from Loader (both native and support libraries are implemented).
Loader Manager helps us manage Loader and its loaded data properly when we encounter scenarios such as device rotation.
Furthermore, loader Manager is responsible for starting and stopping loaders and managing the lifecycle of loaders.

When the device configuration changes, if a loader that has loaded the data is initialized, it can submit the data immediately.
Instead of trying to retrieve data again.

The basic usage of AsyncTaskLoader is as follows:

public class PhotoGalleryFragment extends Fragment{
    .............
    //Specify the id corresponding to Loader
    public static final int mLoaderId = 0;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);

        //Define a callback interface, a callback can listen for multiple loader s
        LoaderManager.LoaderCallbacks<List<GalleryItem>> callback =
                new LoaderManager.LoaderCallbacks<List<GalleryItem>>() {

            //When Loader Manager creates Loader, it calls back the interface
            @Override
            public Loader<List<GalleryItem>> onCreateLoader(int id, Bundle args) {
                //Different Loader s can be created based on id
                //The loader required to be created cannot be a non-static internal class
                //The reason should be to avoid memory leaks
                if (id == mLoaderId) {
                    Log.d("ZJTest", "onCreateLoader");
                    return new FetchItemsLoader(getActivity());
                } else {
                    return null;
                }
            }

            //When FetchItemsLoader loads the data, it calls back the interface
            @Override
            public void onLoadFinished(Loader<List<GalleryItem>> loader, List<GalleryItem> data) {
                //You can also do different operations based on id
                if (loader.getId() == mLoaderId) {
                    Log.d("ZJTest", "onLoadFinished");

                    //You can update the interface and so on.
                    mGalleryItems = data;
                    setupAdapter();
                }
            }

            @Override
            public void onLoaderReset(Loader<List<GalleryItem>> loader) {
            }
        };

        //Start Loader with Loader Manager, specify the id, parameters and callback interface of the loader
        getLoaderManager().restartLoader(mLoaderId, null, callback);
    }
    ...................
    //AsyncTaskLoader is also an abstract class, specifying the return value of loadInBackground in the template parameter
    private static class FetchItemsLoader extends AsyncTaskLoader<List<GalleryItem>> {
        FetchItemsLoader(Context context) {
            super(context);
        }

        //This method must be implemented, forceLoad being the parent interface
        //After calling the interface, the loadInBackground operation is executed
        @Override
        protected void onStartLoading() {
            Log.d("ZJTest", "onStartLoading");
            forceLoad();
        }

        //The return value will be returned to the onLoadFinished interface of callback
        @Override
        public List<GalleryItem> loadInBackground() {
            Log.d("ZJTest", "loadInBackground");
            return new ImageFetcher().fetchItems();
        }
    }
 }

Let's run this code. log is similar to:

02-14 15:41:01.080 11297-11297/stark.a.is.zhang.photogallery D/ZJTest: onCreateLoader
02-14 15:41:01.089 11297-11297/stark.a.is.zhang.photogallery D/ZJTest: onStartLoading
02-14 15:41:01.095 11297-11313/stark.a.is.zhang.photogallery D/ZJTest: loadInBackground
02-14 15:41:01.562 11297-11297/stark.a.is.zhang.photogallery D/ZJTest: onLoadFinished

4. Examples of ViewTreeObserver usage

ViewTreeObserver can monitor the layout changes of View, and it has multiple monitoring interfaces.
Take OnGlobalLayoutListener as an example to briefly record how it is used:

//Let's take RecyclerView as an example to dynamically adjust the number of members per row of RecyclerView
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.fragment_photo_gallery, container, false);

    //Find Recycler View
    mPhotoRecyclerView = (RecyclerView) v.findViewById(R.id.fragment_photo_gallery_recycler_view);

    //Get ViewTreeObserver for RecyclerView
    ViewTreeObserver viewTreeObserver = mPhotoRecyclerView.getViewTreeObserver();

    //Add an OnGlobalLayoutListener for ViewTreeObserver
    viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        private int last = 0;

        //When the layout changes, that is, when drawing RecyclerView, the onGlobalLayout interface is called back.
        @Override
        public void onGlobalLayout() {
            if (getView() != null) {
                //In xml, we define the width of RecyclerView as match_parent
                int width = getView().getWidth();

                //Specify the width of each member to be 400 to get the number of members that can be placed in each column
                int count = width / 400;

                //When a change occurs, the Layout Manager of RecyclerView is reset
                if (last != count) {
                    //Every time you change Layout Manger, RecyclerView is redrawn
                    //After switching between the horizontal and vertical screens, the number of members can be placed dynamically in each row.
                    mPhotoRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), count));
                    last = count;
                }
            }
        }
    });

    setupAdapter();

    return v;
}

5. RecyclerView Detection Method for Sliding to the Top or Bottom

When using RecyclerView, it is often necessary to detect whether the user is sliding to the top or bottom.
At this point, a method similar to the following can be used:

............
    private void addOnScrollListener() {
        //Add OnScrollListener for RecyclerView to monitor sliding events
        mPhotoRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            private int lastVisibleItemPosition;
            private int firstVisibleItemPosition;

            //At the end of the slide, the onScrolled interface is called back
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy){
                super.onScrolled(recyclerView, dx, dy);

                RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();

                if (layoutManager instanceof GridLayoutManager) {
                    //Get the current interface, the last sub-view corresponding to the position
                    lastVisibleItemPosition = ((GridLayoutManager) layoutManager)
                            .findLastVisibleItemPosition();

                    //Get the current interface, the position of the first subview
                    firstVisibleItemPosition = ((GridLayoutManager) layoutManager)
                            .findFirstVisibleItemPosition();

                    ................
                }
            }

            //Callback the interface when the sliding state changes
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);

                RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();

                //Get the size of the visible data in the current interface
                int visibleItemCount = layoutManager.getChildCount();

                //Get the size of RecyclerView corresponding to all data
                int totalItemCount = layoutManager.getItemCount();

                //Judgment conditions can be adjusted according to actual needs
                if (newState == RecyclerView.SCROLL_STATE_IDLE && visibleItemCount > 0) {

                    //When the position corresponding to the last view equals - 1, it means that the bottom is touched when the last slide is over.
                    if (lastVisibleItemPosition == totalItemCount - 1){
                        //Business on demand
                        startURLLoader();

                    //The position of the first view is equal to 0, indicating that the last slide ended when the top was touched.
                    } else if (firstVisibleItemPosition == 0) {
                        Toast.makeText(getActivity(),
                                "Already to the top",
                                Toast.LENGTH_SHORT)
                                .show();
                    }
                }
            }
        });
    }
..............

The specific use of OnScrollListener can be adjusted as needed.
But the main idea is to judge whether to slide to the top or bottom according to the position of the current view.

6. RecyclerView slides to the specified interface

..............
//When this interface is called, the view corresponding to mLastPosition will become the first view that the interface displays after sliding.
mPhotoRecyclerView.scrollToPosition(mLastPosition);
...............

RecyclerView also has other interfaces for sliding operations.
But it essentially depends on the operation corresponding to LayoutManager.

For example, scrollToPosition's source code is as follows:

..............
    /**
     * Convenience method to scroll to a certain position.
     *
     * RecyclerView does not implement scrolling logic, rather forwards the call to
     * {@link android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)}
     * @param position Scroll to this adapter position
     * @see android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)
     */
    public void scrollToPosition(int position) {
        if (mLayoutFrozen) {
            return;
        }
        stopScroll();
        if (mLayout == null) {
            Log.e(TAG, "Cannot scroll to position a LayoutManager set. " +
                    "Call setLayoutManager with a non-null argument.");
            return;
        }
        //Depending on the implementation of the specific LayoutManager
        mLayout.scrollToPosition(position);
        awakenScrollBars();
    }
..............

7. Basic usage of LruCache

Android provides a basic caching strategy, namely LRU (least recently used).
Based on this strategy, when the storage space is exhausted, the cache clears the least recently used objects.

An example of using LruCache is as follows:

public class ImageCache {
    //Define LruCache, specify its key and the type of data it saves
    private LruCache<String, Bitmap> mImageCache;

    ImageCache() {
        //Gets the memory size available to the current process, converted to KB in units
        final int maxMemory = (int)(Runtime.getRuntime().maxMemory() / 1024);

        //Take 1/4 of the total memory as the cache
        final int cacheSize = maxMemory / 4;

        //Initialize LruCache
        mImageCache = new LruCache<String, Bitmap>(cacheSize) {
            //Define the size of each storage object
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };
    }

    //get data
    public Bitmap getBitmap(String url) {
        return mImageCache.get(url);
    }

    //Store data
    public void putBitmap(String url, Bitmap bitmap) {
        mImageCache.put(url, bitmap);
    }
}

8. Simple Use of Picasso Library

Picasso library is a high-performance third-party image download library. When using this library, it can simply complete the functions of image download, caching and so on.

Its usage is as follows: Take RecyclerView as an example:

//Adapter defining RecyclerView
private class PhotoAdapter extends RecyclerView.Adapter<PhotoHolder> {
    private List<GalleryItem> mGalleryItems;

    PhotoAdapter(List<GalleryItem> galleryItems) {
        mGalleryItems = galleryItems;
    }

    @Override
    public PhotoHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(getActivity());
        View view = inflater.inflate(R.layout.gallery_item, parent, false);
        return new PhotoHolder(view);
    }

    @Override
    public void onBindViewHolder(PhotoHolder holder, int position) {
        GalleryItem galleryItem = mGalleryItems.get(position);

        holder.bindGalleryItem(galleryItem, position);
    }

    @Override
    public int getItemCount() {
        return mGalleryItems.size();
    }
}

//ViewHolder Defining RecyclerView
private class PhotoHolder extends RecyclerView.ViewHolder {
    private ImageView mItemImageView;
    private TextView mTextView;

    PhotoHolder(View itemView) {
        super(itemView);
        mItemImageView = (ImageView) itemView.findViewById
                (R.id.fragment_photo_gallery_image_view);
        mTextView = (TextView) itemView.findViewById
                (R.id.fragment_photo_gallery_text_view);
    }

    void bindGalleryItem(GalleryItem galleryItem, int position) {
        mTextView.setText(getString(R.string.text_title, "" + position));

        //Picasso library, pass Context in with
        Picasso.with(getActivity())
                //Address of incoming downloaded pictures in load
                .load(galleryItem.getThumbURL())
                //Selective use to specify a placeholder when the image is not downloaded
                .placeholder(R.drawable.place_holder)
                //Specify the layout where the downloaded image should be placed
                .into(mItemImageView);
        }
}

With the above method, the Picasso library will automatically read from the cache or download the view when it loads.

Posted by djot on Fri, 29 Mar 2019 05:03:28 -0700