关闭 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去叱诧风云了。

    上一篇返回首页 下一篇

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

    别人在看

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    Windows 10 终止支持后,企业为何要立即升级?

    Windows 10 将于 2025年10 月终止技术支持,建议迁移到 Windows 11

    Windows 12 发布推迟,微软正全力筹备Windows 11 25H2更新

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    IT头条

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    亚太地区的 AI 驱动型医疗保健:2025 年及以后的下一步是什么?

    16:30

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    技术热点

    SQL汉字转换为拼音的函数

    windows 7系统无法运行Photoshop CS3的解决方法

    巧用MySQL加密函数对Web网站敏感数据进行保护

    MySQL基础知识简介

    Windows7和WinXP下如何实现不输密码自动登录系统的设置方法介绍

    windows 7系统ip地址冲突怎么办?windows 7系统IP地址冲突问题的

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

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