关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »Android应用Loaders全面详解及源码浅析

    Android应用Loaders全面详解及源码浅析

    2016-01-21 00:00:00 出处:philo
    分享

    1 背景

    在Android中任何耗时的操作都不能放在UI主线程中,所以耗时的操作都需要使用异步实现。同样的,在ContentProvider中也可能存在耗时操作,这时也该使用异步操作,而3.0之后最推荐的异步操作就是Loader。它可以方便我们在Activity和Fragment中异步加载数据,而不是用线程或AsyncTask,他的优点如下:

    提供异步加载数据机制; 对数据源变化进行监听,实时更新数据; 在Activity配置发生变化(如横竖屏切换)时不用重复加载数据; 适用于任何Activity和Fragment;

    PS:由于在我们现在的多个项目中都大量的使用了Loader来处理数据加载(而且由于粗心跳过几个坑,譬如Loader ID重复导致数据逻辑异常、多线程中restartLoader导致Loader抛出异常(最后保证都在UI线程中执行即可)等),所以接下来我们进行下使用及源码浅析。

    PPPS:前方高能,文章巨长,请做好心理准备(您可以选择通过左上角目录点击索引到感兴趣的章节直接查看,或者,或者,或者直接高能往下看)。

    2 基础使用实例

    该基础实例讲解完全来自于官方文档,详细可以点击我查看英文原文。

    既然接下来准备要说说他的使用强大之处了,那不妨我们先来一张图直观的感性认识下不用Loader(左)与用Loader(右)对我们开发者及代码复杂度和框架的影响吧,如下:

    Android应用Loaders全面详解及源码浅析

    2-1 Loader API概述说明

    如下是我们开发中常用的一些Loader相关接口:

    Class/Interface Description
    LoaderManager 一个与Activity、Fragment关联的抽象类,用于管理一个或多个Loader实例。每个Activity或Fragment只能有一个LoaderManager,而一个LoaderManager可以有多个Loader。
    LoaderManager.LoaderCallbacks 用于和LoaderManager交互的回调接口。譬如,可以使用onCreateLoader()创建一个新的Loader。
    AsyncTaskLoader 抽象的Loader,提供一个AsyncTask继承实现。
    CursorLoader AsyncTaskLoader的子类,用于向ContentResover请求返回一个Cursor。该类以标准游标查询实现了Loader协议,使用后台线程进行查询,使用这个Loader是从ContentProvider加载异步数据最好的方式。

    2-2 在应用中使用Loader

    在我们开发的一个App里,使用Loader时常规的步骤包含如下一些操作需求:

    一个Activity或Fragment; 一个LoaderManager实例; 一个CursorLoader,从ContentProvider加载数据; 一个LoaderManager.LoaderCallbacks实现,创建新Loader及管理已存在Loader; 一个组织Loader数据的Adapter,如SimpleCursorAdapter;

    下面我们看下具体流程。

    2-2-1 启动一个Loader(initLoader)

    一个Activity或Fragment中LoaderManager管理一个或多个Loader实例,每个Activity或Fragment只有一个LoaderManager,我们可以在Activity的onCreate()或Fragment的onActivityCreated()里初始化一个Loader。例如:

    // Prepare the loader. Either re-connect with an existing one,
    // or start a new one.
    getLoaderManager().initLoader(0, null, this);

    可以看见上面的initLoader()方法有三个参数:

    第一个参数代表当前Loader的ID; 第二个参数代表提供给Loader构造函数的参数,可选; 第三个参数代表LoaderManager.LoaderCallbacks的回调实现;

    上面initLoader()方法的调用确保了一个Loader被初始化和激活的状态,该方法的调运有如下两种结果:

    假如代表该Loader的ID已经存在,则后面创建的Loader将直接复用已经存在的; 假如代表该Loader的ID不存在,initLoader()会触发LoaderManager.LoaderCallbacks回调的onCreateLoader()方法创建一个Loader;

    可以看见通过initLoader()方法可以将LoaderManager.LoaderCallbacks实例与Loader进行关联,且当Loader的状态变化时就被回调。所以说,假如调用者正处于其开始状态并且被请求的Loader已经存在,且已产生了数据,那么系统会立即调用onLoadFinished()(在initLoader()调用期间),所以你必须考虑到这种情况的发生。

    当然了,intiLoader()会返回一个创建的Loader,但是你不用获取它的引用,因为LoadeManager会自动管理该Loader的生命周期,你只用在它回调提供的生命周期方法中做自己数据逻辑的处理即可。

    2-2-2 重启一个Loader(restartLoader)

    通过上面initLoader()方法介绍我们可以知道initLoader调运后要么得到一个ID已存在的Loader,要么创建一个新的Loader;但是有时我们想丢弃旧数据然后重新开始创建一个新Loader,这可怎么办呢?别担心,要丢弃旧数据调用restartLoader()即可。例如,SearchView.OnQueryTextListener的实现重启了Loader,当用户查询发生变化时Loader需要重启,如下:

    public boolean onQueryTextChanged(String newText) {
        // Called when the action bar search text has changed. Update
        // the search filter, and restart the loader to do a new query
        // with this filter.
        mCurFilter = !TextUtils.isEmpty(newText)   newText : null;
        getLoaderManager().restartLoader(0, null, this);
        return true;
    }

    上面方法的参数啥的和再上面的init方法类似,就不再罗嗦了。

    2-2-3 使用LoaderManager Callbacks

    LoaderManager.LoaderCallbacks是LoaderManager的回调交互接口。LoaderManager.LoaderCallbacks包含如下三个方法:

    onCreateLoader()
    实例化并返回一个新创建给定ID的Loader对象; onLoadFinished()
    当创建好的Loader完成了数据的load之后回调此方法; onLoaderReset()
    当创建好的Loader被reset时调用此方法,这样保证它的数据无效;
    2-2-3-1 onCreateLoader说明

    当你尝试使用一个Loader(譬如通过initLoader()方法),它会检查给定Loader的ID是否存在,假如不存在就触发LoaderManager.LoaderCallbacks里的onCreateLoader()方法创建一个新Loader。创建新Loader实例典型的做法就是通过CursorLoader类创建,不过你也可以自定义一个继承自Loader的子类来实现自己的Loader。

    下面的例子中我们通过onCreateLoader()回调创建一个CursorLoader实例,使用CursorLoader的构造方法创建实例时需要一些参数去查询一个ContentProvider。具体参数如下:

    uri
    准备获取内容的URI。 projection
    要返回的列key list,null表示返回所有列,但是返回所有列很多时候会降低性能; selection
    要返回的行过滤,也就是SQL中的WHERE语句,null代表返回uri指定的所有行; selectionArgs
    用来替换上面selection中包含的“?”; sortOrder
    结果的行排序,也就是SQL中的ORDER BY,传递null则无序;
     // If non-null, this is the current filter the user has provided.
    String mCurFilter;
    ...
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // This is called when a new Loader needs to be created. This
        // sample only has one Loader, so we don't care about the ID.
        // First, pick the base URI to use depending on whether we are
        // currently filtering.
        Uri baseUri;
        if (mCurFilter != null) {
            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                      Uri.encode(mCurFilter));
        } else {
            baseUri = Contacts.CONTENT_URI;
        }
    
        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + Contacts.DISPLAY_NAME + " != '' ))";
        return new CursorLoader(getActivity(), baseUri,
                CONTACTS_SUMMARY_PROJECTION, select, null,
                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
    }
    2-2-3-2 onLoadFinished说明

    当创建好的Loader完成数据加载时回调此方法,大家要确保该方法在Loader释放现有维持的数据之前被调用。在这里我们应该移除所有对旧数据的使用(因为旧数据不久就会被释放),但是不用释放旧数据,因为Loader会帮我们完成旧数据的释放。

    Loader一旦知道App不再使用旧数据就会释放掉。例如,假如数据来自CursorLoader里的一个Cursor,我们不应该自己在代码中调用close()方法;假如一个Cursor正在被放置到一个CursorAdapter时我们应当使用swapCursor()进行新数据交换,这样正在被放置的旧的Cursor就不会被关掉,也就不会导致Adapter的加载异常。

    // This is the Adapter being used to display the list's data.
    SimpleCursorAdapter mAdapter;
    ...
    
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // Swap the new cursor in. (The framework will take care of closing the
        // old cursor once we return.)
        mAdapter.swapCursor(data);
    }
    2-2-3-3 onLoaderReset说明

    当实例化好的Loader被重启时该方法被回调,这里会让Loader的数据置于无效状态。这个回调方法其实就是为了告诉我们啥时候数据要被释放掉,所以我们应该在这个时候移除对它的引用。如下移除实例:

    // This is the Adapter being used to display the list's data.
    SimpleCursorAdapter mAdapter;
    ...
    
    public void onLoaderReset(Loader<Cursor> loader) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed. We need to make sure we are no
        // longer using it.
        mAdapter.swapCursor(null);
    }

    2-2-4 Loader使用实例实战

    下面这个实例是一个Fragment,模拟的是用ListView显示通讯录的实时匹配查询结果,使用CursorLoader管理通讯录Provider查询。如下源码,比较简单,注释也很丰富了,所以不过多解释:

    public static class CursorLoaderListFragment extends ListFragment implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
    
        // This is the Adapter being used to display the list's data.
        SimpleCursorAdapter mAdapter;
    
        // If non-null, this is the current filter the user has provided.
        String mCurFilter;
    
        @Override public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
    
            // Give some text to display if there is no data. In a real
            // application this would come from a resource.
            setEmptyText("No phone numbers");
    
            // We have a menu item to show in action bar.
            setHasOptionsMenu(true);
    
            // Create an empty adapter we will use to display the loaded data.
            mAdapter = new SimpleCursorAdapter(getActivity(),
                    android.R.layout.simple_list_item_2, null,
                    new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
                    new int[] { android.R.id.text1, android.R.id.text2 }, 0);
            setListAdapter(mAdapter);
    
            // Prepare the loader. Either re-connect with an existing one,
            // or start a new one.
            getLoaderManager().initLoader(0, null, this);
        }
    
        @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
            // Place an action bar item for searching.
            MenuItem item = menu.add("Search");
            item.setIcon(android.R.drawable.ic_menu_search);
            item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
            SearchView sv = new SearchView(getActivity());
            sv.setOnQueryTextListener(this);
            item.setActionView(sv);
        }
    
        public boolean onQueryTextChange(String newText) {
            // Called when the action bar search text has changed. Update
            // the search filter, and restart the loader to do a new query
            // with this filter.
            mCurFilter = !TextUtils.isEmpty(newText)   newText : null;
            getLoaderManager().restartLoader(0, null, this);
            return true;
        }
    
        @Override public boolean onQueryTextSubmit(String query) {
            // Don't care about this.
            return true;
        }
    
        @Override public void onListItemClick(ListView l, View v, int position, long id) {
            // Insert desired behavior here.
            Log.i("FragmentComplexList", "Item clicked: " + id);
        }
    
        // These are the Contacts rows that we will retrieve.
        static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
            Contacts._ID,
            Contacts.DISPLAY_NAME,
            Contacts.CONTACT_STATUS,
            Contacts.CONTACT_PRESENCE,
            Contacts.PHOTO_ID,
            Contacts.LOOKUP_KEY,
        };
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            // This is called when a new Loader needs to be created. This
            // sample only has one Loader, so we don't care about the ID.
            // First, pick the base URI to use depending on whether we are
            // currently filtering.
            Uri baseUri;
            if (mCurFilter != null) {
                baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                        Uri.encode(mCurFilter));
            } else {
                baseUri = Contacts.CONTENT_URI;
            }
    
            // Now create and return a CursorLoader that will take care of
            // creating a Cursor for the data being displayed.
            String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                    + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                    + Contacts.DISPLAY_NAME + " != '' ))";
            return new CursorLoader(getActivity(), baseUri,
                    CONTACTS_SUMMARY_PROJECTION, select, null,
                    Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
        }
    
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            // Swap the new cursor in. (The framework will take care of closing the
            // old cursor once we return.)
            mAdapter.swapCursor(data);
        }
    
        public void onLoaderReset(Loader<Cursor> loader) {
            // This is called when the last Cursor provided to onLoadFinished()
            // above is about to be closed. We need to make sure we are no
            // longer using it.
            mAdapter.swapCursor(null);
        }
    }

    到此整个Loader基础使用就介绍完了,关于Loader的高级功能,譬如自定义Loader等内容这里先不贴代码说明,因为在这里一下子说完都会觉得蒙圈,而且接受难度也比较大,所以我们在上面这些基础铺垫之后乘热先来源码浅析,有了源码浅析把持住全局结构后再去用Loader的高级用法就会觉得得心应手许多。

    3 源码浅析

    和上面的基本使用介绍一样,关于Loader的源码浅析过程会涉及到Activity、Fragment、LoaderManager、Loader、AsyncLoader、CursorLoader等类。所以我们分析的过程还是和以前一样,依据使用顺序进行分析。

    我们在分析之前先来看一个Loader框架概要图,如下:

    Android应用Loaders全面详解及源码浅析

    通过上面图和前面的基础实例你会发现Loader的框架和各个类的职责都很明确。Activity和Fragment管理LoaderManager,LoaderManager管理Loader,Loader得到数据后触发在LoaderManager中实现的Loader的callback接口,LoaderManager在接收到Loader的callback回传调运时触发我们Activity或Fragment中实现的LoaderManager回调callback接口,就这样就实现了Loader的所有功能,而我们平时写代码一般只用关心LoaderManager的callback实现即可;对于自定义Loader可能还需要关心AsyncTaskLoader子类的实现。

    3-1 Activity及Fragment中LoadManager的管理浅析

    首先我们都知道,在使用Loader的第一步就是在Activity或者Fragment中获取LoaderManager实例,所以我们先来看下Activity和Fragment是如何管理这些LoaderManager的。

    先来看看Fragment中的LoaderManager,如下:

    final class FragmentState implements Parcelable {
        ......
        LoaderManagerImpl mLoaderManager;
        boolean mLoadersStarted;
        boolean mCheckedForLoaderManager;
        ......
        //fragment中获取LoaderManager办法
        public LoaderManager getLoaderManager() {
            //可以看见,一个Fragment只有一个LoaderManager
            if (mLoaderManager != null) {
                return mLoaderManager;
            }
            if (mActivity == null) {
                throw new IllegalStateException("Fragment " + this + " not attached to Activity");
            }
            mCheckedForLoaderManager = true;
            //从Activity中获取LoaderManager,传入的mWho为当前Fragment的识别key,然后create传入true表示创建!!!!!!
            mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, true);
            return mLoaderManager;
        }
    
        public void onStart() {
            mCalled = true;
    
            if (!mLoadersStarted) {
                mLoadersStarted = true;
                if (!mCheckedForLoaderManager) {
                    mCheckedForLoaderManager = true;
                    //假如还没调运过getLoaderManager,那就尝试获取LoaderManager,传入的create为false!!!!!
                    mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false);
                }
                //生命周期依附上LoaderManager
                if (mLoaderManager != null) {
                    mLoaderManager.doStart();
                }
            }
        }
    
        public void onDestroy() {
            mCalled = true;
            if (!mCheckedForLoaderManager) {
                mCheckedForLoaderManager = true;
                //假如还没调运过getLoaderManager,那就尝试获取LoaderManager,传入的create为false!!!!!
                mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false);
            }
            //生命周期依附上LoaderManager
            if (mLoaderManager != null) {
                mLoaderManager.doDestroy();
            }
        }
    
        void performStart() {
            ......
            mCalled = false;
            onStart();
            ......
            //生命周期依附上LoaderManager
            if (mLoaderManager != null) {
                mLoaderManager.doReportStart();
            }
        }
    
        void performStop() {
            ......
            mCalled = false;
            onStop();
            ......
            if (mLoadersStarted) {
                mLoadersStarted = false;
                if (!mCheckedForLoaderManager) {
                    mCheckedForLoaderManager = true;
                    //假如还没调运过getLoaderManager,那就尝试获取LoaderManager,传入的create为false!!!!!
                    mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false);
                }
                if (mLoaderManager != null) {
                    //生命周期依附上LoaderManager
                    if (mActivity == null || !mActivity.mChangingConfigurations) {
                        mLoaderManager.doStop();
                    } else {
                        mLoaderManager.doRetain();
                    }
                }
            }
        }
    
        void performDestroyView() {
            ......
            mCalled = false;
            onDestroyView();
            ......
            //生命周期依附上LoaderManager
            if (mLoaderManager != null) {
                mLoaderManager.doReportNextStart();
            }
        }
    }

    从上面可以看出,Fragment在其生命周期内会控制LoaderManager(LoaderManager其实控制了Loader)的doStart、doDestroy等方法,也就是说我们在Fragment中只管通过getLoaderManager方法来获取LoaderManager实例,然后使用就行,别的Fragment都会帮我们处理OK的。

    接下来看看Activity中的LoaderManager,如下:

    public class Activity extends ContextThemeWrapper implements ... {
            //mAllLoaderManagers保存了Activity与Fragment的所有LoaderManager
        ArrayMap<String, LoaderManagerImpl> mAllLoaderManagers;
        LoaderManagerImpl mLoaderManager;
        ......
        //Activity中获取LoaderManager实例的方法
        public LoaderManager getLoaderManager() {
            //可以看见,一个Activity只有一个LoaderManager
            if (mLoaderManager != null) {
                return mLoaderManager;
            }
            mCheckedForLoaderManager = true;
            //咦?这不就是上面Fragment的getLoaderManager中调运的那个activity中的getLoaderManager吗,只是和这里的参数不一样而已
            mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true);
            return mLoaderManager;
        }
        //Activity与Fragment获取LoaderManager实例的真正方法!!
        LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
            //可见一个Activity维护一个mAllLoaderManagers的MAP
            if (mAllLoaderManagers == null) {
                mAllLoaderManagers = new ArrayMap<String, LoaderManagerImpl>();
            }
            //尝试从缓存mAllLoaderManagers的MAP中获取已经实例化的LoaderManager实例
            LoaderManagerImpl lm = mAllLoaderManagers.get(who);
            if (lm == null) {
                if (create) {
                    //假如没有找到并且需要实例化create(切记这个create参数是很重要的),就调运LoaderManagerImpl构造方法实例化一个LoaderManager对象,然后存入缓存mAllLoaderManagers的MAP中
                    lm = new LoaderManagerImpl(who, this, started);
                    mAllLoaderManagers.put(who, lm);
                }
            } else {
                lm.updateActivity(this);
            }
            return lm;
        }
    
        void invalidateFragment(String who) {
            if (mAllLoaderManagers != null) {
                LoaderManagerImpl lm = mAllLoaderManagers.get(who);
                if (lm != null && !lm.mRetaining) {
                    //生命周期依附上LoaderManager
                    lm.doDestroy();
                    mAllLoaderManagers.remove(who);
                }
            }
        }
    
        final void performStop() {
            if (mLoadersStarted) {
                mLoadersStarted = false;
                //生命周期依附上LoaderManager
                if (mLoaderManager != null) {
                    //mChangingConfigurations表示假如当前发生了配置变化则为true,否则为false!!!!!!!重点,Loader特性之一
                    if (!mChangingConfigurations) {
                        //当前Activity的stop不是由配置变化引起则直接调用LoaderManager的doStop()方法!!!!!!
                        mLoaderManager.doStop();
                    } else {
                        //当前Activity配置变化,所以需要保存当前的loaderManager,在Activity恢复时恢复这个LoaderManager!!!!!!
                        mLoaderManager.doRetain();
                    }
                }
            }
        ......
        }
    
        final void performDestroy() {
            ......
            onDestroy();
            //生命周期依附上LoaderManager
            if (mLoaderManager != null) {
                mLoaderManager.doDestroy();
            }
            ......
        }
    
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            if (mLastNonConfigurationInstances != null) {
                //从mLastNonConfigurationInstances中恢复mAllLoaderManagers(mLastNonConfigurationInstances是从onAttach中恢复的),Activity配置变化时会走这里!!!!
                mAllLoaderManagers = mLastNonConfigurationInstances.loaders;
            }
            ......
            mCalled = true;
        }
    
        final void performStart() {
        ......
            if (mAllLoaderManagers != null) {
                final int N = mAllLoaderManagers.size();
                LoaderManagerImpl loaders[] = new LoaderManagerImpl[N];
                for (int i=N-1; i>=0; i--) {
                    loaders[i] = mAllLoaderManagers.valueAt(i);
                }
                //生命周期依附上LoaderManager
                for (int i=0; i<N; i++) {
                    LoaderManagerImpl lm = loaders[i];
                    //调用LoaderManager.finishRetain()以及doReportStart()方法来恢复LoaderManager的状态!!!!!
                    lm.finishRetain();
                    lm.doReportStart();
                }
            }
            mActivityTransitionState.enterReady(this);
        }
        //该方法会被ActivityThread类调用,且调运时机早于performDestroy()方法!!!!!!
        NonConfigurationInstances retainNonConfigurationInstances() {
            ......
            NonConfigurationInstances nci = new NonConfigurationInstances();
            ......
            //配置变化时保存mAllLoaderManagers!!!!!!
            nci.loaders = mAllLoaderManagers;
            return nci;
        }
    }

    通过上面的分析可以发现,Activity其实真正的管理了Activity及Fragment的LoaderManager(Fragment也会管理一部分自己LoaderManager的周期),而LoaderManager又管理了Loader,可以发现他们各自的管理范围都是十分的清晰明了的。

    3-2 LoadManager及其实现类LoadManagerImpl的浅析

    上面分析Activity及Fragment中获取LoaderManager实例时已经知道,我们获取的LoaderManager实例其实就是LoaderManagerImpl对象,而LoaderManagerImpl又是LoaderManager类的子类,所以接下来我们来分析这两个父子类。

    先看下抽象父类LoaderManager,如下:

    public abstract class LoaderManager {
        //LoaderManager的回调接口定义
        public interface LoaderCallbacks<D> {
            public Loader<D> onCreateLoader(int id, Bundle args);
            public void onLoadFinished(Loader<D> loader, D data);
            public void onLoaderReset(Loader<D> loader);
        }
        //下面这些方法没必要再细说了,上面介绍过的
        public abstract <D> Loader<D> initLoader(int id, Bundle args,
                LoaderManager.LoaderCallbacks<D> callback);
    
        public abstract <D> Loader<D> restartLoader(int id, Bundle args,
                LoaderManager.LoaderCallbacks<D> callback);
        //会触发回调的onLoaderReset方法
        public abstract void destroyLoader(int id);
    
        public abstract <D> Loader<D> getLoader(int id);
    
        public abstract void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args);
    
        public static void enableDebugLogging(boolean enabled) {
            LoaderManagerImpl.DEBUG = enabled;
        }
    }

    可以看见LoaderManager抽象类只是定义了一些规范接口而已,那么接着我们看下抽象类LoaderManager的实现类LoaderManagerImpl,如下:

    class LoaderManagerImpl extends LoaderManager {
        static final String TAG = "LoaderManager";
        static boolean DEBUG = false;
    
        //保存当前存活的Loader
        final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>(0);
        //保存已经运行完的Loader
        final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>(0);
    
        final String mWho;
    
        Activity mActivity;
        boolean mStarted;
        boolean mRetaining;
        boolean mRetainingStarted;
        //是否正在创建Loader,多线程中同时调运创建会导致异常
        boolean mCreatingLoader;
        //Loader的封装类
        final class LoaderInfo implements Loader.OnLoadCompleteListener<Object>,
                Loader.OnLoadCanceledListener<Object> {
            final int mId;
            final Bundle mArgs;
            LoaderManager.LoaderCallbacks<Object> mCallbacks;
            Loader<Object> mLoader;
            boolean mHaveData;
            boolean mDeliveredData;
            Object mData;
            boolean mStarted;
            //mRetaining标记Activity配置变化时保持当前Loader,不用销毁;和上面分析Activity的LoaderManager的retainNonConfigurationInstances方法关联!!!!!!
            boolean mRetaining;
            boolean mRetainingStarted;
            boolean mReportNextStart;
            boolean mDestroyed;
            boolean mListenerRegistered;
    
            LoaderInfo mPendingLoader;
            //LoaderInfo构造方法
            public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) {
                mId = id;
                mArgs = args;
                mCallbacks = callbacks;
            }
            //启动一个Loader
            void start() {
                //配置改变恢复则不用启动,用原来的
                if (mRetaining && mRetainingStarted) {
                    mStarted = true;
                    return;
                }
                //假如已经启动,则不用再restart了
                if (mStarted) {
                    return;
                }
    
                mStarted = true;
    
                //假如当前封装中mLoader为空并且通过构造方法的mCallbacks不为空则回调onCreateLoader方法创建Loader
                if (mLoader == null && mCallbacks != null) {
                   mLoader = mCallbacks.onCreateLoader(mId, mArgs);
                }
                if (mLoader != null) {
                    if (mLoader.getClass().isMemberClass()
                            && !Modifier.isStatic(mLoader.getClass().getModifiers())) {
                            //假如当前创建的Loader对象是一个非静态内部类则抛异常!!!!!!
                        throw new IllegalArgumentException(
                                "Object returned from onCreateLoader must not be a non-static inner member class: "
                                + mLoader);
                    }
                    if (!mListenerRegistered) {
                        //注册Loader的监听方法
                        mLoader.registerListener(mId, this);
                        mLoader.registerOnLoadCanceledListener(this);
                        mListenerRegistered = true;
                    }
                    //调运Loader的startLoading方法
                    mLoader.startLoading();
                }
            }
            //Activity的配置改变时进行标志位的设置,以便可以保存,配合上面Activity的分析!!!!!!
            void retain() {
                mRetaining = true;
                ......
            }
            //Activity配置变化后重启后假如有数据则通知回调方法,配合上面Activity的分析!!!!!!
            void finishRetain() {
                ......
                if (mStarted && mHaveData && !mReportNextStart) {
                    callOnLoadFinished(mLoader, mData);
                }
            }
            //配合上面Activity的分析!!!!!!
            void reportStart() {
                ......
            }
            //停止Loader
            void stop() {
                mStarted = false;
                if (!mRetaining) {
                    //假如不是Activity配置变化,即不用保存则注销掉这些回调
                    if (mLoader != null && mListenerRegistered) {
                        ......
                    }
                }
            }
            //取消掉Loader
            void cancel() {
                ......
            }
            //销毁掉Loader
            void destroy() {
                ......
                if (mCallbacks != null && mLoader != null && mHaveData && needReset) {
                    ......
                    try {
                        //在destroy时假如有数据存在则调用callback的onLoaderReset方法!!!!!!
                        mCallbacks.onLoaderReset(mLoader);
                    } finally {
                        ......
                    }
                }
                ......
                if (mLoader != null) {
                    //注销监听方法
                    if (mListenerRegistered) {
                        ......
                    }
                    //close Cursor等重置操作
                    mLoader.reset();
                }
                if (mPendingLoader != null) {
                    mPendingLoader.destroy();
                }
            }
        //Loader被取消时回调该方法
            @Override
            public void onLoadCanceled(Loader<Object> loader) {
                ......
                LoaderInfo pending = mPendingLoader;
                //执行最新的Loader
                if (pending != null) {
                    mPendingLoader = null;
                    mLoaders.put(mId, null);
                    destroy();
                    installLoader(pending);
                }
            }
            //加载完成时回调
            @Override
            public void onLoadComplete(Loader<Object> loader, Object data) {
                ......
                //执行最新的Loader
                if (pending != null) {
                    mPendingLoader = null;
                    mLoaders.put(mId, null);
                    destroy();
                    installLoader(pending);
                    return;
                }
    
                if (mData != data || !mHaveData) {
                    mData = data;
                    mHaveData = true;
                    if (mStarted) {
                        callOnLoadFinished(loader, data);
                    }
                }
                ......
            }
            //调用onLoadFinished
            void callOnLoadFinished(Loader<Object> loader, Object data) {
                if (mCallbacks != null) {
                    ......
                    try {
                        //回调onLoadFinished方法
                        mCallbacks.onLoadFinished(loader, data);
                    } finally {
                        ......
                    }
                    mDeliveredData = true;
                }
            }
        }
    
        //!!!!!!真正LoaderManagerImpl的构造方法
        LoaderManagerImpl(String who, Activity activity, boolean started) {
            mWho = who;
            mActivity = activity;
            mStarted = started;
        }
    
        //更新当前Activity引用
        void updateActivity(Activity activity) {
            mActivity = activity;
        }
    
        //私有的创建Loader方法
        private LoaderInfo createLoader(int id, Bundle args,
                LoaderManager.LoaderCallbacks<Object> callback) {
            LoaderInfo info = new LoaderInfo(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
            //回调callback的onCreateLoader方法得到Loader对象
            Loader<Object> loader = callback.onCreateLoader(id, args);
            //把得到的Loader对象包装成LoaderInfo对象
            info.mLoader = (Loader<Object>)loader;
            return info;
        }
    
        //包装了创建Loader与install方法,并将mCreatingLoader标记置位
        private LoaderInfo createAndInstallLoader(int id, Bundle args,
                LoaderManager.LoaderCallbacks<Object> callback) {
            try {
                mCreatingLoader = true;
                //调运上面的私有创建方法创建LoaderInfo对象
                LoaderInfo info = createLoader(id, args, callback);
                //把创建的LoaderInfo对象传入installLoader方法
                installLoader(info);
                return info;
            } finally {
                mCreatingLoader = false;
            }
        }
    
        void installLoader(LoaderInfo info) {
            //将创建的LoaderInfo对象存入mLoaders的Map中
            mLoaders.put(info.mId, info);
            if (mStarted) {
                //假如Activity已经started,则启动LoaderInfo的start方法
                info.start();
            }
        }
    
        //public的方法,创建一个Loader,前面介绍过的
        @SuppressWarnings("unchecked")
        public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
            //假如多线程中正在有创建的则抛出异常(写代码要注意这种情况,尤其是跑Monkey容易抛出,解决办法就是保证都在统一线程中执行!!!!!!)
            if (mCreatingLoader) {
                throw new IllegalStateException("Called while creating a loader");
            }
            //从现有的Map中尝试获取指定ID的LoaderInfo对象
            LoaderInfo info = mLoaders.get(id);
            if (info == null) {
                //发现不存在就调运上面的createAndInstallLoader创建一个
                info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
            } else {
                //否则还用当前的Loader,只是重新赋值了callBack而已
                info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
            }
    
            if (info.mHaveData && mStarted) {
                //已经有数据,直接调运LoaderInfo的callOnLoadFinished
                info.callOnLoadFinished(info.mLoader, info.mData);
            }
            //返回Loader对象
            return (Loader<D>)info.mLoader;
        }
    
        //重新创造Loader,前面介绍过的
        @SuppressWarnings("unchecked")
        public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
            if (mCreatingLoader) {
                //假如多线程中正在有创建的则抛出异常(写代码要注意这种情况,尤其是跑Monkey容易抛出,解决办法就是保证都在统一线程中执行!!!!!!)
                throw new IllegalStateException("Called while creating a loader");
            }
    
            LoaderInfo info = mLoaders.get(id);
            if (info != null) {
                LoaderInfo inactive = mInactiveLoaders.get(id);
                if (inactive != null) {
                    if (info.mHaveData) {
                        //发现是已经运行完的Loader且已经存在的Loader有数据则destroy掉运行完的Loader
                        inactive.mDeliveredData = false;
                        inactive.destroy();
                        info.mLoader.abandon();
                        mInactiveLoaders.put(id, info);
                    } else {
                        if (!info.mStarted) {
                            //有相同id的Loader还没start则destory掉
                            mLoaders.put(id, null);
                            info.destroy();
                        } else {
                            //有一个相同id的Loader正在加载数据,但是还没加载完,调用它的cancel()方法通知取消加载
                            info.cancel();
                            if (info.mPendingLoader != null) {
                                info.mPendingLoader.destroy();
                                info.mPendingLoader = null;
                            }
                            //创建一个指定id的Loader同时赋给mPendingLoader,因为这个时候已经有一个Loader正在加载数据,而且我们已经调用了其cancel()方法来通知取消加载
                            info.mPendingLoader = createLoader(id, args, 
                                    (LoaderManager.LoaderCallbacks<Object>)callback);
                            //返回创建的Loader
                            return (Loader<D>)info.mPendingLoader.mLoader;
                        }
                    }
                } else {
                    //终止已存在的Loader
                    info.mLoader.abandon();
                    mInactiveLoaders.put(id, info);
                }
            }
            //重新创建Loader返回
            info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
            return (Loader<D>)info.mLoader;
        }
    
        //销毁指定id的Loader
        public void destroyLoader(int id) {
            if (mCreatingLoader) {
                throw new IllegalStateException("Called while creating a loader");
            }
            //不解释,单纯的destory
            int idx = mLoaders.indexOfKey(id);
            if (idx >= 0) {
                LoaderInfo info = mLoaders.valueAt(idx);
                mLoaders.removeAt(idx);
                info.destroy();
            }
            idx = mInactiveLoaders.indexOfKey(id);
            if (idx >= 0) {
                LoaderInfo info = mInactiveLoaders.valueAt(idx);
                mInactiveLoaders.removeAt(idx);
                info.destroy();
            }
            ......
        }
    
        //获取指定id的Loader对象
        @SuppressWarnings("unchecked")
        public <D> Loader<D> getLoader(int id) {
            if (mCreatingLoader) {
                throw new IllegalStateException("Called while creating a loader");
            }
            //优先获取LoaderInfo中的mPendingLoader
            LoaderInfo loaderInfo = mLoaders.get(id);
            if (loaderInfo != null) {
                if (loaderInfo.mPendingLoader != null) {
                    return (Loader<D>)loaderInfo.mPendingLoader.mLoader;
                }
                return (Loader<D>)loaderInfo.mLoader;
            }
            return null;
        }
        ......
    }

    我勒个去!好长,好累!通过上面粗略的分析你会发现和我们上面基础实例介绍LoaderManager的方法时描述的一样,每个方法都有自己的特点,发挥着各自的作用,LoaderManager的实质是将Loader对象转换为LoaderInfo来进行管理,也就是管理了所有的Loader对象。

    3-3 Loader及其实现类的浅析

    上面分析了Activity及Fragment管理了LoaderManager的相关方法,LoaderManager管理了Loader的相关方法,那么接下来我们就来看看这个被管理的终极目标Loader是咋回事,还有他的子类咋回事。

    先来看看我画的一张关系图,如下:

    Android应用Loaders全面详解及源码浅析

    我去,这图现在看可能有些吓人,我们还是先来慢慢分析一下再说吧。

    3-3-1 Loader基类源码浅析

    我们先来看看这个Loader基类吧,该类核心方法及内部类结构图如下:

    Android应用Loaders全面详解及源码浅析

    代码分析如下:

    public class Loader<D> {
        int mId;
        OnLoadCompleteListener<D> mListener;
        OnLoadCanceledListener<D> mOnLoadCanceledListener;
        Context mContext;
        boolean mStarted = false;
        boolean mAbandoned = false;
        boolean mReset = true;
        boolean mContentChanged = false;
        boolean mProcessingChange = false;
        //数据源变化监听器(观察者模式),实现了ContentObserver类
        public final class ForceLoadContentObserver extends ContentObserver {
            public ForceLoadContentObserver() {
                super(new Handler());
            }
    
            @Override
            public boolean deliverSelfNotifications() {
                return true;
            }
    
            @Override
            public void onChange(boolean selfChange) {
                //实质是调运Loader的forceLoad方法
                onContentChanged();
            }
        }
    
        //Loader加载完成接口,当加载完成时Loader通知loaderManager,loaderManager再回调我们initLoader方法的callback
        public interface OnLoadCompleteListener<D> {
            public void onLoadComplete(Loader<D> loader, D data);
        }
        //LoaderManager中监听cancel,同上类似
        public interface OnLoadCanceledListener<D> {
            public void onLoadCanceled(Loader<D> loader);
        }
    
        //构造方法
        public Loader(Context context) {
            //mContext持有Application的Context,防止泄露内存等
            mContext = context.getApplicationContext();
        }
    
        //加载完成时回调传递加载数据结果,实质是对OnLoadCompleteListener接口方法的封装
        public void deliverResult(D data) {
            if (mListener != null) {
                mListener.onLoadComplete(this, data);
            }
        }
        //类似同上,对OnLoadCanceledListener的方法的封装
        public void deliverCancellation() {
            if (mOnLoadCanceledListener != null) {
                mOnLoadCanceledListener.onLoadCanceled(this);
            }
        }
    
        public Context getContext() {
            return mContext;
        }
        public int getId() {
            return mId;
        }
        public void registerListener(int id, OnLoadCompleteListener<D> listener) {
            mListener = listener;
            mId = id;
        }
        public void unregisterListener(OnLoadCompleteListener<D> listener) {
            mListener = null;
        }
        public void registerOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
            mOnLoadCanceledListener = listener;
        }
        public void unregisterOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
            mOnLoadCanceledListener = null;
        }
        public boolean isStarted() {
            return mStarted;
        }
        public boolean isAbandoned() {
            return mAbandoned;
        }
        public boolean isReset() {
            return mReset;
        }
    
        //开始加载数据时LoaderManager会调用该方法
        public final void startLoading() {
            //设置标记
            mStarted = true;
            mReset = false;
            mAbandoned = false;
            onStartLoading();
        }
    
        //真正开始加载数据的地方******空方法,子类实现!!!!!!
        protected void onStartLoading() {
        }
    
        //取消Loader的方法
        public boolean cancelLoad() {
            return onCancelLoad();
        }
    
        //真正取消的地方******,子类实现!!!!!!return false表示取消失败(因为已完成或未开始)
        protected boolean onCancelLoad() {
            return false;
        }
    
        //强制重新Loader,放弃旧数据
        public void forceLoad() {
            onForceLoad();
        }
    
        //真正重新Loader的地方******空方法,子类实现!!!!!!
        protected void onForceLoad() {
        }
    
        //同上
        public void stopLoading() {
            mStarted = false;
            onStopLoading();
        }
        protected void onStopLoading() {
        }
    
        //同上
        public void abandon() {
            mAbandoned = true;
            onAbandon();
        }
        protected void onAbandon() {
        }
    
        //同上
        public void reset() {
            onReset();
            mReset = true;
            mStarted = false;
            mAbandoned = false;
            mContentChanged = false;
            mProcessingChange = false;
        }
        protected void onReset() {
        }
    
        //Loader数据变化的一些标记处理
        public boolean takeContentChanged() {
            boolean res = mContentChanged;
            mContentChanged = false;
            mProcessingChange |= res;
            return res;
        }
        public void commitContentChanged() {
            mProcessingChange = false;
        }
        public void rollbackContentChanged() {
            if (mProcessingChange) {
                mContentChanged = true;
            }
        }
    
        //上面ForceLoadContentObserver内部类的onChange方法调运
        public void onContentChanged() {
            if (mStarted) {
                forceLoad();
            } else {
                mContentChanged = true;
            }
        }
    
        //一些方便调试的方法
        public String dataToString(D data)
        public String toString()
        public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)
    }

    通过上面粗略的分析可以发现,Loader基类无非也就是一个方法接口的定义类,组织预留了一些方法供LoaderManager去调运处理,同时需要子类实现其提供的一些onXXX方法,以便LoaderManager调运Loader的方法时可以触发Loader子类的实现逻辑。

    3-3-2 AsyncTaskLoader抽象子类源码浅析

    上面既然说了Loader类的作用主要是规定接口,同时供LoaderManager管理,那LoaderManager管理的Loader自然需要做一些事情,也就是说我们需要继承Loader实现一些逻辑操作。然而好在系统API已经帮我们实现了一些简单的封装实现,我们这里就先来看下Loader的直接子类AsyncTaskLoader吧,先来看下该抽象子类的方法及内部类粗略图,如下:

    Android应用Loaders全面详解及源码浅析

    代码分析如下:

    public abstract class AsyncTaskLoader<D> extends Loader<D> {
        static final String TAG = "AsyncTaskLoader";
        static final boolean DEBUG = false;
        //LoadTask内部类是对AsyncTask的封装,实现了Runnable接口
        final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
            ......
            @Override
            protected D doInBackground(Void... params) {
                try {
                    //AsyncTask的子线程中执行AsyncTaskLoader的onLoadInBackground方法!!!!重点
                    D data = AsyncTaskLoader.this.onLoadInBackground();
                    //把执行结果数据D返回到UI线程
                    return data;
                } catch (OperationCanceledException ex) {
                    if (!isCancelled()) {
                        throw ex;
                    }
                    return null;
                }
            }
    
            /* Runs on the UI thread */
            @Override
            protected void onPostExecute(D data) {
                //AsyncTask子线程执行完毕后回调AsyncTaskLoader的dispatchOnLoadComplete方法
                AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
            }
    
            /* Runs on the UI thread */
            @Override
            protected void onCancelled(D data) {
                //取消AsyncTask时调运
                AsyncTaskLoader.this.dispatchOnCancelled(this, data);
            }
    
            //Runnable的实现方法
            @Override
            public void run() {
                waiting = false;
                AsyncTaskLoader.this.executePendingTask();
            }
        ......
        }
    
        private final Executor mExecutor;
    
        volatile LoadTask mTask;
        volatile LoadTask mCancellingTask;
    
        long mUpdateThrottle;
        long mLastLoadCompleteTime = -10000;
        Handler mHandler;
        //public构造方法
        public AsyncTaskLoader(Context context) {
            this(context, AsyncTask.THREAD_POOL_EXECUTOR);
        }
    
        /** {@hide} 无法被外部调运的构造方法 */
        public AsyncTaskLoader(Context context, Executor executor) {
            super(context);
            mExecutor = executor;
        }
    
        public void setUpdateThrottle(long delayMS) {
            mUpdateThrottle = delayMS;
            if (delayMS != 0) {
                mHandler = new Handler();
            }
        }
    
        @Override
        protected void onForceLoad() {
            super.onForceLoad();
            //取消当前的Loader
            cancelLoad();
            //新建task并执行
            mTask = new LoadTask();
            executePendingTask();
        }
    
        @Override
        protected boolean onCancelLoad() {
            ......
        }
    
        public void onCanceled(D data) {
        }
    
        //LoadTask的Runnable方法run中执行
        void executePendingTask() {
            if (mCancellingTask == null && mTask != null) {
                if (mTask.waiting) {
                    mTask.waiting = false;
                    mHandler.removeCallbacks(mTask);
                }
                if (mUpdateThrottle > 0) {
                    long now = SystemClock.uptimeMillis();
                    if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
                        // Not yet time to do another load.
                        mTask.waiting = true;
                        mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
                        return;
                    }
                }
                //真正的触发执行AsyncTask方法
                mTask.executeOnExecutor(mExecutor, (Void[]) null);
            }
        }
    
        void dispatchOnCancelled(LoadTask task, D data) {
            onCanceled(data);
            if (mCancellingTask == task) {
                rollbackContentChanged();
                mLastLoadCompleteTime = SystemClock.uptimeMillis();
                mCancellingTask = null;
                //触发Loader的接口方法onLoadCanceled,在LoaderManager中实现
                deliverCancellation();
                executePendingTask();
            }
        }
    
        void dispatchOnLoadComplete(LoadTask task, D data) {
            if (mTask != task) {
                dispatchOnCancelled(task, data);
            } else {
                if (isAbandoned()) {
                    // This cursor has been abandoned; just cancel the new data.
                    onCanceled(data);
                } else {
                    commitContentChanged();
                    mLastLoadCompleteTime = SystemClock.uptimeMillis();
                    mTask = null;
                    //触发Loader的接口方法onLoadComplete,在LoaderManager中实现
                    deliverResult(data);
                }
            }
        }
        //需要子类实现!!!!!在子线程中执行
        public abstract D loadInBackground();
    
        //LoadTask(AsyncTask的子线程中回调)中调运
        protected D onLoadInBackground() {
            return loadInBackground();
        }
    
        //LoadTask(AsyncTask的onCancelLoad中回调)调运
        public void cancelLoadInBackground() {
        }
    
        public boolean isLoadInBackgroundCanceled() {
            return mCancellingTask != null;
        }
        //锁标记处理
        public void waitForLoader() {
            LoadTask task = mTask;
            if (task != null) {
                task.waitForLoader();
            }
        }
    }

    可以看见上面继承Loader的AsyncTaskLoader其实质是提供了一个基于AsyncTask工作机制的Loader(子类LoadTask继承AsyncTask<Void, Void, D>,并且实现了Runable接口,功能十分强大。),但是不可直接用,因为其为abstract抽象类,所以我们需要继承实现它才可以使用,然而好在系统API已经帮我们提供了他现成的子类CursorLoader,但CursorLoader同时也限制了Loader的泛型数据为Cursor类型。当然了,我们假如想要Loader自己的类型数据那也很简单—继承实现AsyncTaskLoader即可,后面会给出例子的。

    3-3-3 CursorLoader子类源码浅析

    有了上面继承自Loader的抽象AsyncTaskLoader,接下来我们就来看看SDK为我们提供的抽象AsyncTaskLoader实现类CursorLoader,我们先来粗略看看该类的方法图,如下:

    Android应用Loaders全面详解及源码浅析

    具体代码分析如下:

    //继承自AsyncTaskLoader,数据类型为Cursor的Loader异步加载实现类
    public class CursorLoader extends AsyncTaskLoader<Cursor> {
        //Cursor的子类ForceLoadContentObserver
        final ForceLoadContentObserver mObserver;
    
        Uri mUri;
        String[] mProjection;
        String mSelection;
        String[] mSelectionArgs;
        String mSortOrder;
    
        Cursor mCursor;
        CancellationSignal mCancellationSignal;
    
        /* Runs on a worker thread 最核心的实现方法,在这里查询获取数据 */
        @Override
        public Cursor loadInBackground() {
            synchronized (this) {
                if (isLoadInBackgroundCanceled()) {
                    throw new OperationCanceledException();
                }
                mCancellationSignal = new CancellationSignal();
            }
            try {
                //不过多解释,耗时的查询操作
                Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
                        mSelectionArgs, mSortOrder, mCancellationSignal);
                if (cursor != null) {
                    try {
                        // Ensure the cursor window is filled.
                        cursor.getCount();
                        //给Cursor设置观察者;ContentProvider通知Cursor的观察者数据发生了改变,Cursor通知CursorLoader的观察者数据发生了改变,CursorLoader通过ContentProvider重新加载新的数据
                        cursor.registerContentObserver(mObserver);
                    } catch (RuntimeException ex) {
                        cursor.close();
                        throw ex;
                    }
                }
                return cursor;
            } finally {
                synchronized (this) {
                    mCancellationSignal = null;
                }
            }
        }
    
        @Override
        public void cancelLoadInBackground() {
            super.cancelLoadInBackground();
    
            synchronized (this) {
                if (mCancellationSignal != null) {
                    mCancellationSignal.cancel();
                }
            }
        }
    
        /* Runs on the UI thread */
        @Override
        public void deliverResult(Cursor cursor) {
            if (isReset()) {
                // An async query came in while the loader is stopped
                if (cursor != null) {
                    cursor.close();
                }
                return;
            }
            Cursor oldCursor = mCursor;
            mCursor = cursor;
    
            if (isStarted()) {
                super.deliverResult(cursor);
            }
    
            if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
                oldCursor.close();
            }
        }
    
        public CursorLoader(Context context) {
            super(context);
            mObserver = new ForceLoadContentObserver();
        }
    
        public CursorLoader(Context context, Uri uri, String[] projection, String selection,
                String[] selectionArgs, String sortOrder) {
            super(context);
            //新建一个当前类(Loader)的内部类对象,数据库变化时调运ForceLoadContentObserver的onChange方法,onChange调运Loader的onContentChanged方法,onContentChanged调运Loader的forceLoad方法
            mObserver = new ForceLoadContentObserver();
            mUri = uri;
            mProjection = projection;
            mSelection = selection;
            mSelectionArgs = selectionArgs;
            mSortOrder = sortOrder;
        }
    
        @Override
        protected void onStartLoading() {
            if (mCursor != null) {
                deliverResult(mCursor);
            }
            if (takeContentChanged() || mCursor == null) {
                forceLoad();
            }
        }
    
        @Override
        protected void onStopLoading() {
            // Attempt to cancel the current load task if possible.
            cancelLoad();
        }
    
        @Override
        public void onCanceled(Cursor cursor) {
            if (cursor != null && !cursor.isClosed()) {
                cursor.close();
            }
        }
    
        @Override
        protected void onReset() {
            super.onReset();
    
            // Ensure the loader is stopped
            onStopLoading();
    
            if (mCursor != null && !mCursor.isClosed()) {
                mCursor.close();
            }
            mCursor = null;
        }
    
        public Uri getUri() {
            return mUri;
        }
    
        public void setUri(Uri uri) {
            mUri = uri;
        }
    
        public String[] getProjection() {
            return mProjection;
        }
    
        public void setProjection(String[] projection) {
            mProjection = projection;
        }
    
        public String getSelection() {
            return mSelection;
        }
    
        public void setSelection(String selection) {
            mSelection = selection;
        }
    
        public String[] getSelectionArgs() {
            return mSelectionArgs;
        }
    
        public void setSelectionArgs(String[] selectionArgs) {
            mSelectionArgs = selectionArgs;
        }
    
        public String getSortOrder() {
            return mSortOrder;
        }
    
        public void setSortOrder(String sortOrder) {
            mSortOrder = sortOrder;
        }
    }

    可以发现,CursorLoader的封装大大简化了应用开发者代码的复杂度;它完全就是一个异步的数据库查询瑞士军刀,没有啥特别需要分析的地方,所以不再过多说明。

    3-4 Loaders相关源码浅析总结

    通过上面我们的源码分析和分析前那副图可以总结如下结论:

    一次完整的数据加载流程为Activity调用LoaderManager的doStart()方法,然后LoaderManager调用Loader的startLoading()方法,然后Loader调运AsyncTaskLoader的doingBackground()方法进行耗时数据加载,然后AsyncTaskLoader回调LoaderManager的complete数据加载完成方法,接着LoaderManager回调我们在Activity中实现的callback中的onLoadFinish()方法。 Acivity和Fragment的生命周期主动管理了LoaderManager,每个Activity用一个ArrayMap的mAllLoaderManager来保存当前Activity及其附属Frament的唯一LoaderManager;在Activity配置发生变化时,Activity在destory前会保存mAllLoaderManager,当Activity再重新创建时,会在Activity的onAttcach()、onCreate()、performStart()方法中恢复mAllLoaderManager。 LoaderManager给Activity提供了管理自己的一些方法;同时主动管理了对应的Loader,它把每一个Loader封装为LoadInfo对象,同时它负责主动调运管理Loader的startLoading()、stopLoading()、,forceLoad()等方法。 由于整个Activity和Fragment主动管理了Loader,所以关于Loader的释放(譬如CursorLoader的Cursor关闭等)不需要我们人为处理,Loader框架会帮我们很好的处理的;同时特别注意,对于CursorLoader,当我们数据源发生变化时Loader框架会通过ContentObserver调用onContentChanged的forceLoad方法重新请求数据进行回调刷新。

    好了,至此你会发现Loader真的很牛叉,No!应该是Google的工程师真的很牛叉,架构真的很赞,值得推荐。

    4 应用层开发之Loader进阶实战

    上面对于Loader的基础使用及源码框架都进行了简单分析,有了上面的铺垫我们再回过头来看看我们开发中的一些高级技巧,通过这些高级技巧不仅是对前面源码分析的实例验证,也是对自己知识的积累。

    4-1 ContentPorvider情况下的CurSorLoader自动刷新

    在我们使用CurSorLoader时大家都会考虑一种情况的处理—–当数据库发生变化时如何自动刷新当前UI。呵呵,我们先来说说这个原理,数据库在数据改变时通过ContentPorvider和ContentResolver发出通知,接着ContentProvider通知Cursor的观察者数据发生了变化,然后Cursor通知CursorLoader的观察者数据发生了变化,接着CursorLoader通过ContentProvider加载新数据,完事调用CursorAdapter的changeCursor()用新数据替换旧数据显示。

    这个过程具体的实现步骤如下:

    对获取的Cursor数据设置需要监听的URI(即,在ContentProvider的query()方法或者Loader的loadingBackground()方法中调用Cursor的setNotificationUri()方法); 在ContentProvider的insert()、update()、delete()等方法中调用ContentResolver的notifyChange()方法;

    通过上面两步我们就能享受CurSorLoader的自动数据刷新功能了;可以发现,所谓的CurSorLoader自动刷新无非就是观察者模式的框架而已,所以不再过多说明。

    特别注意:

    有些人觉得为了方便可能会将上面第一步对于Cursor设置监听直接写在了ContentProvider的query()方法中,如下:

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] 
    selectionArgs,String sortOrder) {
        SQLiteDatabase database = sqLiteOpenHelper.getReadableDatabase();
        Cursor cursor = database.query(EmailContent.CONTACT_TABLE, projection,
        selection,selectionArgs, null, null, sortOrder);
        //设置NotificationUri监听
        cursor.setNotificationUri(contentResolver, EmailContent.MESSAGE);
        return cursor;
    }

    这里要提醒的是,这种写法在某些场合下是不值得推荐的(譬如大规模上千次并发平凡的调运query操作场合),因为效率极低,他会频繁的通过Binder进行通信,导致system_server不停的调运GC操作,以至于会使系统卡顿。

    PS:因为我以前跳过一次这个坑,平时使用应用没啥问题,但是当进行压力测试时却发现LogCat一直在不停的打印GC,同时导致当前系统卡顿,杀掉应用后系统就不卡了,所以基本怀疑问题就出在了应用中,于是通过很多办法去查找(譬如dempsys content去查看个数),最终发现罪魁祸首是这个监听频繁调运导致的,随将其挪到loadingBackground中不再卡顿。

    4-2 不使用ContentPorvider且自定义Loader的情况下自动刷新

    我们目前的项目其实都使用了ContentPorvider实现,所以就是上面讲的那些情况。但是你一定会问,假如我们应用的数据不用于应用间共享,使用ContentProvider那得多麻烦啊?我先告诉你,是很麻烦,但是Android提供的CursorLoader的API必须使用ContentProvider才能实现数据加载和自动刷新。

    这时候你指定会说,那还说个屁!哎,别急,你看看下面这段代码是否会有所感触呢,如下:

    public NoProviderLoader extends AsyncTaskLoader {
        ......
        ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
        ......
        @Override
        public Cursor loadInBackground() {
            SQLiteDatabase database = sqLiteOpenHelper.getReadableDatabase();
            Cursor cursor = database.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
            if (cursor != null) {
                //最重要的两行代码!!!!!!
                cursor.registerContentObserver(mObserver);//给Cursor设置观察者
                cursor.setNotificationUri(getContext().getContentResolver(), otificationUri);//给Cursor设置要观察的URI
            }
    
            return cursor;
        }
        ......
    }

    咦?是不是上面代码很奇怪,异步操作的方法中没有使用ContentProvider,而是直接读取了数据库。握草!这不就是我们刚刚想要的需求么,它没有使用ContentProvider提供Cursor数据,同时实现了数据变化自动更新功能。

    简单解释下上面代码的原理吧,我们自定义的NoProviderLoader中定义的ForceLoadContentObserver是Loader的一个内部类,上面源码分析已经解释过了,当数据变化时会调运该类的onChange()方法,实质是调运了Loader的forceLoad()方法,所以能够自动刷新,不多解释了。

    4-3 Loader自定义之AsyncTaskLoader衍生

    可能看到这里你更加会举一反三的反驳一句了,上面搞了半天都是和数据库Cursor相关的东东,难道Loader就不能异步处理别的数据结构么?答案是能,因为你可能已经注意到了Loader和AsyncTaskLoader都是泛型类;既然这样,那我们找猫画虎一把呗,仿照CursorLoader自定义一个自己的异步加载试试,具体实现如下(哈哈,想了又想,这里还是直接给出官方的自定义AsyncTaskLoader好点,毕竟权威些,详细点我查看官方自定义实现Demo):

    官方对于查询已安装App列表的Loader实现,支持新App安装后自动刷新的功能,实现如下:

    /** * This class holds the per-item data in our Loader. */
    public static class AppEntry {
        public AppEntry(AppListLoader loader, ApplicationInfo info) {
            mLoader = loader;
            mInfo = info;
            mApkFile = new File(info.sourceDir);
        }
    
        public ApplicationInfo getApplicationInfo() {
            return mInfo;
        }
    
        public String getLabel() {
            return mLabel;
        }
    
        public Drawable getIcon() {
            if (mIcon == null) {
                if (mApkFile.exists()) {
                    mIcon = mInfo.loadIcon(mLoader.mPm);
                    return mIcon;
                } else {
                    mMounted = false;
                }
            } else if (!mMounted) {
                // If the app wasn't mounted but is now mounted, reload
                // its icon.
                if (mApkFile.exists()) {
                    mMounted = true;
                    mIcon = mInfo.loadIcon(mLoader.mPm);
                    return mIcon;
                }
            } else {
                return mIcon;
            }
    
            return mLoader.getContext().getResources().getDrawable(
                    android.R.drawable.sym_def_app_icon);
        }
    
        @Override public String toString() {
            return mLabel;
        }
    
        void loadLabel(Context context) {
            if (mLabel == null || !mMounted) {
                if (!mApkFile.exists()) {
                    mMounted = false;
                    mLabel = mInfo.packageName;
                } else {
                    mMounted = true;
                    CharSequence label = mInfo.loadLabel(context.getPackageManager());
                    mLabel = label != null   label.toString() : mInfo.packageName;
                }
            }
        }
    
        private final AppListLoader mLoader;
        private final ApplicationInfo mInfo;
        private final File mApkFile;
        private String mLabel;
        private Drawable mIcon;
        private boolean mMounted;
    }
    
    /** * Perform alphabetical comparison of application entry objects. */
    public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {
        private final Collator sCollator = Collator.getInstance();
        @Override
        public int compare(AppEntry object1, AppEntry object2) {
            return sCollator.compare(object1.getLabel(), object2.getLabel());
        }
    };
    
    /** * Helper for determining if the configuration has changed in an interesting * way so we need to rebuild the app list. */
    public static class InterestingConfigChanges {
        final Configuration mLastConfiguration = new Configuration();
        int mLastDensity;
    
        boolean applyNewConfig(Resources res) {
            int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
            boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
            if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE
                    |ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) {
                mLastDensity = res.getDisplayMetrics().densityDpi;
                return true;
            }
            return false;
        }
    }
    
    /** * Helper class to look for interesting changes to the installed apps * so that the loader can be updated. */
    public static class PackageIntentReceiver extends BroadcastReceiver {
        final AppListLoader mLoader;
    
        public PackageIntentReceiver(AppListLoader loader) {
            mLoader = loader;
            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
            filter.addDataScheme("package");
            mLoader.getContext().registerReceiver(this, filter);
            // Register for events related to sdcard installation.
            IntentFilter sdFilter = new IntentFilter();
            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
            mLoader.getContext().registerReceiver(this, sdFilter);
        }
    
        @Override public void onReceive(Context context, Intent intent) {
            // Tell the loader about the change.
            mLoader.onContentChanged();
        }
    }
    
    /** * A custom Loader that loads all of the installed applications. */
    public static class AppListLoader extends AsyncTaskLoader<List<AppEntry>> {
        final InterestingConfigChanges mLastConfig = new InterestingConfigChanges();
        final PackageManager mPm;
    
        List<AppEntry> mApps;
        PackageIntentReceiver mPackageObserver;
    
        public AppListLoader(Context context) {
            super(context);
    
            // Retrieve the package manager for later use; note we don't
            // use 'context' directly but instead the save global application
            // context returned by getContext().
            mPm = getContext().getPackageManager();
        }
    
        /** * This is where the bulk of our work is done. This function is * called in a background thread and should generate a new set of * data to be published by the loader. */
        @Override public List<AppEntry> loadInBackground() {
            // Retrieve all known applications.
            List<ApplicationInfo> apps = mPm.getInstalledApplications(
                    PackageManager.GET_UNINSTALLED_PACKAGES |
                    PackageManager.GET_DISABLED_COMPONENTS);
            if (apps == null) {
                apps = new ArrayList<ApplicationInfo>();
            }
    
            final Context context = getContext();
    
            // Create corresponding array of entries and load their labels.
            List<AppEntry> entries = new ArrayList<AppEntry>(apps.size());
            for (int i=0; i<apps.size(); i++) {
                AppEntry entry = new AppEntry(this, apps.get(i));
                entry.loadLabel(context);
                entries.add(entry);
            }
    
            // Sort the list.
            Collections.sort(entries, ALPHA_COMPARATOR);
    
            // Done!
            return entries;
        }
    
        /** * Called when there is new data to deliver to the client. The * super class will take care of delivering it; the implementation * here just adds a little more logic. */
        @Override public void deliverResult(List<AppEntry> apps) {
            if (isReset()) {
                // An async query came in while the loader is stopped. We
                // don't need the result.
                if (apps != null) {
                    onReleaseResources(apps);
                }
            }
            List<AppEntry> oldApps = mApps;
            mApps = apps;
    
            if (isStarted()) {
                // If the Loader is currently started, we can immediately
                // deliver its results.
                super.deliverResult(apps);
            }
    
            // At this point we can release the resources associated with
            // 'oldApps' if needed; now that the new result is delivered we
            // know that it is no longer in use.
            if (oldApps != null) {
                onReleaseResources(oldApps);
            }
        }
    
        /** * Handles a request to start the Loader. */
        @Override protected void onStartLoading() {
            if (mApps != null) {
                // If we currently have a result available, deliver it
                // immediately.
                deliverResult(mApps);
            }
    
            // Start watching for changes in the app data.
            if (mPackageObserver == null) {
                mPackageObserver = new PackageIntentReceiver(this);
            }
    
            // Has something interesting in the configuration changed since we
            // last built the app list 
            boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());
    
            if (takeContentChanged() || mApps == null || configChange) {
                // If the data has changed since the last time it was loaded
                // or is not currently available, start a load.
                forceLoad();
            }
        }
    
        /** * Handles a request to stop the Loader. */
        @Override protected void onStopLoading() {
            // Attempt to cancel the current load task if possible.
            cancelLoad();
        }
    
        /** * Handles a request to cancel a load. */
        @Override public void onCanceled(List<AppEntry> apps) {
            super.onCanceled(apps);
    
            // At this point we can release the resources associated with 'apps'
            // if needed.
            onReleaseResources(apps);
        }
    
        /** * Handles a request to completely reset the Loader. */
        @Override protected void onReset() {
            super.onReset();
    
            // Ensure the loader is stopped
            onStopLoading();
    
            // At this point we can release the resources associated with 'apps'
            // if needed.
            if (mApps != null) {
                onReleaseResources(mApps);
                mApps = null;
            }
    
            // Stop monitoring for changes.
            if (mPackageObserver != null) {
                getContext().unregisterReceiver(mPackageObserver);
                mPackageObserver = null;
            }
        }
    
        /** * Helper function to take care of releasing resources associated * with an actively loaded data set. */
        protected void onReleaseResources(List<AppEntry> apps) {
            // For a simple List<> there is nothing to do. For something
            // like a Cursor, we would close it here.
        }
    }

    不用多说,上面Loader为Google出品,强大的不得了,我们完全可以仿写这个例子实现自己的请求。

    如下为官方对该自定义Loader调运的Demo代码:

    public static class AppListAdapter extends ArrayAdapter<AppEntry> {
        private final LayoutInflater mInflater;
    
        public AppListAdapter(Context context) {
            super(context, android.R.layout.simple_list_item_2);
            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }
    
        public void setData(List<AppEntry> data) {
            clear();
            if (data != null) {
                addAll(data);
            }
        }
    
        /** * Populate new items in the list. */
        @Override public View getView(int position, View convertView, ViewGroup parent) {
            View view;
    
            if (convertView == null) {
                view = mInflater.inflate(R.layout.list_item_icon_text, parent, false);
            } else {
                view = convertView;
            }
    
            AppEntry item = getItem(position);
            ((ImageView)view.findViewById(R.id.icon)).setImageDrawable(item.getIcon());
            ((TextView)view.findViewById(R.id.text)).setText(item.getLabel());
    
            return view;
        }
    }
    
    public static class AppListFragment extends ListFragment implements OnQueryTextListener, OnCloseListener, LoaderManager.LoaderCallbacks<List<AppEntry>> {
    
        // This is the Adapter being used to display the list's data.
        AppListAdapter mAdapter;
    
        // The SearchView for doing filtering.
        SearchView mSearchView;
    
        // If non-null, this is the current filter the user has provided.
        String mCurFilter;
    
        @Override public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
    
            // Give some text to display if there is no data. In a real
            // application this would come from a resource.
            setEmptyText("No applications");
    
            // We have a menu item to show in action bar.
            setHasOptionsMenu(true);
    
            // Create an empty adapter we will use to display the loaded data.
            mAdapter = new AppListAdapter(getActivity());
            setListAdapter(mAdapter);
    
            // Start out with a progress indicator.
            setListShown(false);
    
            // Prepare the loader. Either re-connect with an existing one,
            // or start a new one.
            getLoaderManager().initLoader(0, null, this);
        }
    
        public static class MySearchView extends SearchView {
            public MySearchView(Context context) {
                super(context);
            }
    
            // The normal SearchView doesn't clear its search text when
            // collapsed, so we will do this for it.
            @Override
            public void onActionViewCollapsed() {
                setQuery("", false);
                super.onActionViewCollapsed();
            }
        }
    
        @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
            // Place an action bar item for searching.
            MenuItem item = menu.add("Search");
            item.setIcon(android.R.drawable.ic_menu_search);
            item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM
                    | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
            mSearchView = new MySearchView(getActivity());
            mSearchView.setOnQueryTextListener(this);
            mSearchView.setOnCloseListener(this);
            mSearchView.setIconifiedByDefault(true);
            item.setActionView(mSearchView);
        }
    
        @Override public boolean onQueryTextChange(String newText) {
            // Called when the action bar search text has changed. Since this
            // is a simple array adapter, we can just have it do the filtering.
            mCurFilter = !TextUtils.isEmpty(newText)   newText : null;
            mAdapter.getFilter().filter(mCurFilter);
            return true;
        }
    
        @Override public boolean onQueryTextSubmit(String query) {
            // Don't care about this.
            return true;
        }
    
        @Override
        public boolean onClose() {
            if (!TextUtils.isEmpty(mSearchView.getQuery())) {
                mSearchView.setQuery(null, true);
            }
            return true;
        }
    
        @Override public void onListItemClick(ListView l, View v, int position, long id) {
            // Insert desired behavior here.
            Log.i("LoaderCustom", "Item clicked: " + id);
        }
    
        @Override public Loader<List<AppEntry>> onCreateLoader(int id, Bundle args) {
            // This is called when a new Loader needs to be created. This
            // sample only has one Loader with no arguments, so it is simple.
            return new AppListLoader(getActivity());
        }
    
        @Override public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> data) {
            // Set the new data in the adapter.
            mAdapter.setData(data);
    
            // The list should now be shown.
            if (isResumed()) {
                setListShown(true);
            } else {
                setListShownNoAnimation(true);
            }
        }
    
        @Override public void onLoaderReset(Loader<List<AppEntry>> loader) {
            // Clear the data in the adapter.
            mAdapter.setData(null);
        }
    }

    强大的一逼!这下满技能,不解释,自己看。

    4-4 进阶总结

    通过前面基础实例、源码分析、进阶演示你会发现Loader的真的非常好用,非常牛逼,牛逼的我不想再解释啥了,自己体会吧。

    PS:之前看见微博上有人讨论AsyncTaskLoader与AsyncTask的区别,这下彻底明朗了,看完源码我们再回过头来总结性的说说他们二者区别,如下:

    class 优势 劣势
    AsyncTaskLoader 会自动刷新数据变化;会自动处理Activiy配置变化造成的影响;适合处理纯数据加载; 不能实时通知UI刷新;不能在onLoadFinished时主动切换生命周期(譬如replace Fragment);
    AsyncTask 可以与UI实时交互及replace操作; 不会自动处理Activiy配置变化造成的影响;

    好了,该撕逼的也撕了,该装逼的也装了,该分析的也分析了,该学习的也学到了,接下来就是看自己如何带着Loader去叱诧风云了。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    抖音安全与信任开放日:揭秘推荐算法,告别单一标签依赖

    ultraedit编辑器打开文件时,总是提示是否转换为DOS格式,如何关闭?

    Cornell大神Kleinberg的经典教材《算法设计》是最好入门的算法教材

    从 Microsoft 下载中心安装 Windows 7 SP1 和 Windows Server 2008 R2 SP1 之前要执行的步骤

    Llama 2基于UCloud UK8S的创新应用

    火山引擎DataTester:如何使用A/B测试优化全域营销效果

    腾讯云、移动云继阿里云降价后宣布大幅度降价

    字节跳动数据平台论文被ICDE2023国际顶会收录,将通过火山引擎开放相关成果

    这个话题被围观超10000次,火山引擎VeDI如此解答

    误删库怎么办?火山引擎DataLeap“3招”守护数据安全

    IT头条

    平替CUDA!摩尔线程发布MUSA 4性能分析工具

    00:43

    三起案件揭开侵犯个人信息犯罪的黑灰产业链

    13:59

    百度三年开放2.1万实习岗,全力培育AI领域未来领袖

    00:36

    工信部:一季度,电信业务总量同比增长7.7%,业务收入累计完成4469亿元

    23:42

    Gartner:2024年全球半导体营收6559亿美元,AI助力英伟达首登榜首

    18:04

    技术热点

    iOS 8 中如何集成 Touch ID 功能

    windows7系统中鼠标滑轮键(中键)的快捷应用

    MySQL数据库的23个特别注意的安全事项

    Kruskal 最小生成树算法

    Ubuntu 14.10上安装新的字体图文教程

    Ubuntu14更新后无法进入系统卡在光标界面解怎么办?

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。