android之LoaderManager原理分析

一.    LoaderManager的使用

LoaderManager用于异步加载数据,当然,它异步的实现依赖于AsyncTask的原理。如果不深究它的实现原理,这个LoaderManager的使用还是很简单的。我们只需要实现LoaderCallbacks类,然后调用getLoaderManager().initLoader()就可以了,执行加载数据的语句写在LoaderCallbacks类的onCreateLoader()方法中,如果加载完毕,我们可以在onLoadFinished()方法中获取相关的数据结果。具体代码如下:

public class MainActivity extends Activity {

	private String TAG = "LoaderManager";
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		getLoaderManager().initLoader(0, null, mCallback);
	}
	private final LoaderManager.LoaderCallbacks<Cursor> mCallback = new LoaderCallbacks<Cursor>() {

		@Override
		public Loader<Cursor> onCreateLoader(int id, Bundle args) {
			// TODO Auto-generated method stub
			android.util.Log.v(TAG, "LoaderCallbacks-------onCreateLoader");
			return new CursorLoader(getApplicationContext(), Telephony.MmsSms.CONTENT_CONVERSATIONS_URI, null, null, null, null);
			//return null;
		}

		@Override
		public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
			// TODO Auto-generated method stub
			android.util.Log.v(TAG, "LoaderCallbacks-------onLoadFinished----count:"+data.getCount());
            while(data.moveToNext()){
            	android.util.Log.v(TAG,":"+data.getString(data.getColumnIndex("address")));
            	android.util.Log.v(TAG,":"+data.getString(data.getColumnIndex("body")));
            }
		}

		@Override
		public void onLoaderReset(Loader<Cursor> loader) {
			// TODO Auto-generated method stub
			android.util.Log.v(TAG, "LoaderCallbacks-------onCreateLoader");

		}
	};
}

以上代码的功能是从Mms数据库中读取所有短信信息并打印。

二.    LoaderManager源代码分析

1.LoaderManager

那么为什么要实现LoaderCallbacks类呢(回调机制),我们等会再说。先来看看怎么获取LoaderManage对象----getLoaderManager():我们可以看到,Activity.java和Fragment.java中都有这个方法,说明我们在创建每一个Activity和Fragment的时候,都会生成一个相对应的LoaderManager对象。当然,在显式的调用getLoaderManager()的时候,如果先前没有创建过LoaderManager对象,它会直接new
LoaderManagerImpl()并返回这个对象。如果先前已经创建过LoaderManager对象,那么就会直接使用以前的LoaderManager对象。

public LoaderManager getLoaderManager() {
        if (mLoaderManager != null) {
            return mLoaderManager;
        }
        mCheckedForLoaderManager = true;
        mLoaderManager = getLoaderManager(null, mLoadersStarted, true);
        return mLoaderManager;
    }

    LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
        if (mAllLoaderManagers == null) {
            mAllLoaderManagers = new HashMap<String, LoaderManagerImpl>();
        }
        LoaderManagerImpl lm = mAllLoaderManagers.get(who);
        if (lm == null) {
            if (create) {
                lm = new LoaderManagerImpl(who, this, started);
                mAllLoaderManagers.put(who, lm);
            }
        } else {
            lm.updateActivity(this);
        }
        return lm;
}

其实在Activity.java和Fragment.java的onStart()方法中,也有初始化这个LoaderManager对象,只是它并不会创建新对象,只会使用旧的对象进行赋值,如下:

protected void onStart() {
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
        mCalled = true;

        if (!mLoadersStarted) {
            mLoadersStarted = true;
            if (mLoaderManager != null) {
                mLoaderManager.doStart();
            } else if (!mCheckedForLoaderManager) {
                mLoaderManager = getLoaderManager(null, mLoadersStarted, false);
            }
            mCheckedForLoaderManager = true;
        }

        getApplication().dispatchActivityStarted(this);
    }

LoaderManager对象得到后,接下来会调用initLoader()方法。接下来的东西就跟LoaderManager.java有关了。先来了解一下LoaderManager.java这个类都有哪些方法和内部类。

abstract class LoaderManager:

一个接口:interface LoaderCallbacks<D>

方法:initLoader(int id, Bundle args,LoaderManager.LoaderCallbacks<D> callback)

restartLoader(int id, Bundle args,LoaderManager.LoaderCallbacks<D> callback)

destroyLoader(int id);

内部类:class LoaderManagerImpl extends LoaderManager {…}

内部类的内部类:final class LoaderInfo implements Loader.OnLoadCompleteListener<Object>,

Loader.OnLoadCanceledListener<Object> {

由于LoaderManager是一个抽象类,所以它的子类LoaderManagerImpl必须要实现它的几个抽象方法。而这几个抽象方法使用了LoaderCallbacks类型的变量作为形参,所以,我们要自己实现LoaderCallbacks这个接口。那么,initLoader和restartLoader这两个方法是干嘛用的呢?要了解这个,我们必须先得了解Loader.java这个类。

2. Loader

public class Loader<D>

内部类:public final class ForceLoadContentObserver extends ContentObserver

内部接口:public interface OnLoadCompleteListener<D>

public interface OnLoadCanceledListener<D>

方法:deliverResult(D data)

registerListener(int id, OnLoadCompleteListener<D> listener)

startLoading()

Loader的继承关系如下:

Loader

AsyncTaskLoader

CursorLoader,ContactLoader……..

先来看看Loader的两个内部接口,这两个内部接口是用来干嘛的呢? 要想知道它的作用,那么先得找到是谁实现了它,又是谁调用了它。我们看到在deliverResult()方法中,接口的方法onLoadComplete被调用了。

public void deliverResult(D data) {
        if (mListener != null) {
            mListener.onLoadComplete(this, data);
        }
}

而这个接口的实现对象mListener是被registerListener()传进来的。

public void registerListener(int id, OnLoadCompleteListener<D> listener) {
        if (mListener != null) {
            throw new IllegalStateException("There is already a listener registered");
        }
        mListener = listener;
        mId = id;
    }

再来看看实现它的地方,是在LoaderManager的内部实现类LoaderManagerImpl中,如下:

final class LoaderInfo implements Loader.OnLoadCompleteListener<Object>,Loader.OnLoadCanceledListener<Object>

这里也使用了回调机制,如果有谁调用了deliverResult()方法,就通过回调机制通知LoaderInfo的onLoadComplete()方法,通过字眼可知,到了deliverResult被调用的时候,需要加载的数据也就得到了,当然得通知LoaderInfo方法进行数据处理,再由LoaderManager统一部署,最后也是通过回调机制回到LoaderCallbacks的实现方法onLoadFinished()中。那么Loader的作用也就出来了:

1. 检测数据源,如果有变化则通知(ForceLoadContentObserver)。

2.定义Loader的状态,根据状态的不同,执行不同的操作,状态有:mStarted,mAbandoned,mReset

3.回调机制:OnLoadCompleteListener,OnLoadCanceledListener

3. AsyncTaskLoader

关于异步的过程并不是在Loader实现,而是在它的子类AsyncTaskLoader中实现。来看看AsyncTaskLoader中的方法及内部类:

volatile LoadTask mTask;
final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable

onForceLoad():重载

在上一节有关于AsyncTask的介绍中,我们知道使用AsyncTask进行异步时,只需要创建一个AsyncTask的对象,并调用execute()方法就可以了。所以可以看看都有哪个方法执行了异步的开始方法:

@Override
    protected void onForceLoad() {
        super.onForceLoad();
        cancelLoad();
        mTask = new LoadTask();
        if (DEBUG) Slog.v(TAG, "Preparing load: mTask=" + mTask);
        executePendingTask();
}
    void dispatchOnCancelled(LoadTask task, D data) {
        onCanceled(data);
        if (mCancellingTask == task) {
            if (DEBUG) Slog.v(TAG, "Cancelled task is now canceled!");
            mLastLoadCompleteTime = SystemClock.uptimeMillis();
            mCancellingTask = null;
            if (DEBUG) Slog.v(TAG, "Delivering cancellation");
            deliverCancellation();
            executePendingTask();
        }
    }

dispatchOnCancelled()方法不是很明确它到底要干什么,我的理解是:大概在加载数据时(LoadTask),时间比较长,等结果出来时,新的Loader任务来了(new LoadTask()),所以放弃掉旧数据,重新开始加载。有待再研究。

毫无疑问,onForceLoad()方法创建了AsyncTask对象,并开启了异步线程的任务。任务开始后,后台的执行方法是doInBackground():详细的看上一节AsyncTask介绍。

protected D doInBackground(Void... params) {
            if (DEBUG) Slog.v(TAG, this + " >>> doInBackground");
            try {
                D data = AsyncTaskLoader.this.onLoadInBackground();
                if (DEBUG) Slog.v(TAG, this + "  <<< doInBackground");
                return data;
            } catch (OperationCanceledException ex) {
                if (!isCancelled()) {
                    throw ex;
                }
                if (DEBUG) Slog.v(TAG, this + "  <<< doInBackground (was canceled)");
                return null;
            }
        }
    protected D onLoadInBackground() {
        return loadInBackground();
}
public abstract D loadInBackground();

很明显,最后执行的将是AsyncTaskLoader子类中的实现方法loadInBackground(),我们拿一个它的子类CursorLoader来看看,执行了什么语句:

public Cursor loadInBackground() {
        Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
                mSelectionArgs, mSortOrder);
        if (cursor != null) {
            // Ensure the cursor window is filled
            cursor.getCount();
            cursor.registerContentObserver(mObserver);
        }
        return cursor;
}

查询数据库数据并且把结果cursor返回到AsyncTaskLoader中。好了,这样异步加载的线程就结束了,得到的结果将在onPostExecute()中进行下一步的处理:

protected void onPostExecute(D data) {
            if (DEBUG) Slog.v(TAG, this + " onPostExecute");
            try {
                AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
            } finally {
                mDone.countDown();
            }
        }

    void dispatchOnLoadComplete(LoadTask task, D data) {
        if (mTask != task) {
            if (DEBUG) Slog.v(TAG, "Load complete of old task, trying to cancel");
            dispatchOnCancelled(task, data);
        } else {
            if (isAbandoned()) {
                // This cursor has been abandoned; just cancel the new data.
                onCanceled(data);
            } else {
                mLastLoadCompleteTime = SystemClock.uptimeMillis();
                mTask = null;
                if (DEBUG) Slog.v(TAG, "Delivering result");
                deliverResult(data);
            }
        }
}

是不是感觉这个deliverResult(data)很熟悉,没错,我们在Loader中见过它的身影,但是这里执行的却并不是Loader中的deliverResult(data),而是CursorLoader中的deliverResult(data),看看它又干了什么:

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();
        }
    }

这个isStarted()是Loader中的方法,判断当前Loader的状态是mStarted,则调用Loader中的deliverResult()方法,执行它后,会回调LoaderManager中的onLoadComplete(),然后再回调一次,这个数据就回到LoaderCallbacks的实现方法onLoadFinished()中,这样我们就可以随心所欲的操作最后的数据结果了。如果当前Loader状态是mAbandoned,mReset,那么说明当前的Loader已经不需要加载数据了,放弃数据并释放资源。

那么,这里还有一个问题。到底谁调用onForceLoad()方法开启异步任务呢?

Loader.java
    public void forceLoad() {
        onForceLoad();
}

在Loader的子类CursorLoader中,有调用这个forceLoad()方法:

@Override
    protected void onStartLoading() {
        if (mCursor != null) {
            deliverResult(mCursor);
        }
        if (takeContentChanged() || mCursor == null) {
            forceLoad();
        }
}

谁调用CursorLoader中的onStartLoading()?

Loader.java
    public final void startLoading() {
        mStarted = true;
        mReset = false;
        mAbandoned = false;
        onStartLoading();
}

最后发现其实是在LoaderManager中的start()方法调用了startLoading(),由此,数据加载开始了,LoaderManager在其中起了一个Loader大管家的作用,它决定了什么时候启动、停止、保持、重启、关闭它的Loaders。

刚开始时,我们是从LoaderManager的initLoader()方法中开始入手研究的,其中涉及到了Loader,AsyncTaskLoader ,CursorLoader以及它们相关的内部类和接口,它们主要解决了异步的问题,并提供了单个数据加载的模板。统筹调度与管理主要还是在LoaderManager中。

开始研究LoaderManager之前,我们先记住单个数据的加载工作是在Loader的startLoading()中开始的。LoaderInfo实现了Loader内部的两个接口,数据加载结束后,Loader会回调LoaderInfo中的方法。了解了这两点,我们可以着手看LoaderManager了。

LoaderInfo的几个变量:

LoaderManager.LoaderCallbacks<Object> mCallbacks;
        Loader<Object> mLoader;
        public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) {
            mId = id;
            mArgs = args;
            mCallbacks = callbacks;
        }

如上,LoaderInfo用mLoader来保存当前Loader,我们可以认为LoaderInfo是对Loader的进一步封装,除此之外,它的实例化还需要一个LoaderCallbacks对象,从我们的例子来看,这个LoaderCallbacks对象其实就是new CursorLoader(),为什么是这个呢,后面会讲到。

LoaderManager的几个变量:

<span style="font-size:14px;">final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>();
final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>();
</span>

mLoaders是一个数据集合,它保存了所有的LoaderInfo对象(封装了Loader),也就是保存了所有的Loader对象。

了解了这几个变量,那来看看initLoader()方法吧:

public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
        if (mCreatingLoader) {
            throw new IllegalStateException("Called while creating a loader");
        }

        LoaderInfo info = mLoaders.get(id);

        if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);

        if (info == null) {
            // Loader doesn't already exist; create.
            info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
            if (DEBUG) Log.v(TAG, "  Created new loader " + info);
        } else {
            if (DEBUG) Log.v(TAG, "  Re-using existing loader " + info);
            info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
        }

        if (info.mHaveData && mStarted) {
            // If the loader has already generated its data, report it now.
            info.callOnLoadFinished(info.mLoader, info.mData);
        }

        return (Loader<D>)info.mLoader;
    }

试图从集合mLoaders中加载已经存在的LoaderInfo对象,如果有,则直接返回LoaderInfo中保存的Loader对象。如果没有,则新创建一个LoaderInfo用来封装Loader,并把此LoaderInfo对象保存在集合中。

private LoaderInfo createAndInstallLoader(int id, Bundle args,
            LoaderManager.LoaderCallbacks<Object> callback) {
        try {
            mCreatingLoader = true;
            LoaderInfo info = createLoader(id, args, callback);
            installLoader(info);
            return info;
        } finally {
            mCreatingLoader = false;
        }
}

创建新的LoaderInfo对象,看看这个callback.onCreateLoader(id, args),callback就是传进来的LoaderCallbacks对象,所以这个onCreateLoader()调用的是我们自己实现的LoaderCallbacks里面的方法,所以说这个Loader对象现在是new CursorLoader()。

private LoaderInfo createLoader(int id, Bundle args,
            LoaderManager.LoaderCallbacks<Object> callback) {
        LoaderInfo info = new LoaderInfo(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
        Loader<Object> loader = callback.onCreateLoader(id, args);
        info.mLoader = (Loader<Object>)loader;
        return info;
}

把新建的LoaderInfo对象加入到集合中,如果下次需要加载同样的数据的话,就从集合中查找并直接返回LoaderInfo中携带的数据,避免了重复查询。

void installLoader(LoaderInfo info) {
        mLoaders.put(info.mId, info);
        if (mStarted) {
            // The activity will start all existing loaders in it's onStart(),
            // so only start them here if we're past that point of the activitiy's
            // life cycle
            info.start();
        }
}

在上面的方法中,每一个新创建的LoaderInfo都会执行info.start(),那这个方法是启动什么的呢:

void start() {
            if (mRetaining && mRetainingStarted) {
                // Our owner is started, but we were being retained from a
                // previous instance in the started state...  so there is really
                // nothing to do here, since the loaders are still started.
                mStarted = true;
                return;
            }

            if (mStarted) {
                // If loader already started, don't restart.
                return;
            }

            mStarted = true;

            if (DEBUG) Log.v(TAG, "  Starting: " + this);
            if (mLoader == null && mCallbacks != null) {
               mLoader = mCallbacks.onCreateLoader(mId, mArgs);
            }
            if (mLoader != null) {
                if (mLoader.getClass().isMemberClass()
                        && !Modifier.isStatic(mLoader.getClass().getModifiers())) {
                    throw new IllegalArgumentException(
                            "Object returned from onCreateLoader must not be a non-static inner member class: "
                            + mLoader);
                }
                if (!mListenerRegistered) {
                    mLoader.registerListener(mId, this);
                    mLoader.registerOnLoadCanceledListener(this);
                    mListenerRegistered = true;
                }
                mLoader.startLoading();
            }
        }

mLoader.registerListener(mId, this);注册监听,其实就是完成了回调机制的布置。

mLoader.startLoading(),单个数据的加载工作开始。是不是有点熟悉,首尾相连了有没有。

好了,至此,LoaderManager的大致工作流程,我们已经跟踪一遍。现在我们简单再总结一下。

从我们自己实现的LoaderManager.LoaderCallbacks开始,它有两个方法:

onCreateLoader():生成Loader的子类对象,返回给LoaderManager进行集中管理。

onLoadFinished():LoaderManager决定Loader对象的启动、停止、保持、重启、关闭,如果得到数据,这个方法就会被回调,我们只需要在这里操作得到的数据就ok了。

补充:

LoaderManager
    void doStart() {
        if (DEBUG) Log.v(TAG, "Starting in " + this);
        if (mStarted) {
            RuntimeException e = new RuntimeException("here");
            e.fillInStackTrace();
            Log.w(TAG, "Called doStart when already started: " + this, e);
            return;
        }

        mStarted = true;

        // Call out to sub classes so they can start their loaders
        // Let the existing loaders know that we want to be notified when a load is complete
        for (int i = mLoaders.size()-1; i >= 0; i--) {
            mLoaders.valueAt(i).start();
        }
}

Activity
    protected void onStart() {
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
        mCalled = true;

        if (!mLoadersStarted) {
            mLoadersStarted = true;
            if (mLoaderManager != null) {
                mLoaderManager.doStart();
            } else if (!mCheckedForLoaderManager) {
                mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false);
            }
            mCheckedForLoaderManager = true;
        }

        getApplication().dispatchActivityStarted(this);
    }

每次Activity的生命周期到onStart时,都会重新加载LoaderInfo集合中的所有Loader数据,见mLoaderManager.doStart();

时间: 2024-08-25 16:18:50

android之LoaderManager原理分析的相关文章

Android加壳原理分析

0x00 阅读本文前,建议读者首先阅读Android加壳原理,参考文章Android中的Apk的加固(加壳)原理解析和实现.如果没有看过这篇文章,本文理解起来比较困难. 0x01 下面我们来分析脱壳代码为什么要这样写,核心脱壳代码在ProxyApplication类里面,首先执行成员方法attachBaseContext,然后执行成员方法onCreate. 那么attachBaseContext是什么时候被执行的呢,为什么先于onCreate执行呢?那就需要看Android的源码了,我们选用的是

android之AsyncTask原理分析

通过名字就可以知道,AsyncTask主要用于处理android中的异步任务.但是通过源码,我们可以看到它的实现其实还是依赖于Handler的异步消息处理机制.现在我们先来学习它的使用方式,然后再研究源码. 一.AsyncTask的基本用法: AsyncTask是一个抽象类,在之类继承它时,必须指定三个泛型参数,这三个参数的用途如下: 1. 在执行AsyncTask时需要传入的参数,可用于在后台任务中使用. 2. 后台任何执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位.

Android Retrofit实现原理分析

retrofit有几个关键的地方. 1.用户自定义的接口和接口方法.(由动态代理创建对象.) 2.converter转换器.(把response转换为一个具体的对象) 3.注解的使用. 让我们跟随Api来看吧. RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint(API_URL).build(); build()其内部实现是这样的: [代码]java代码: ? 1 2 3 4 5 6 7 8 public RestAda

Android 中LayoutInflater原理分析

概述 在Android开发中LayoutInflater的应用非常普遍,可以将res/layout/下的xml布局文件,实例化为一个View或者ViewGroup的控件.与findViewById的作用类似,但是findViewById在xml布局文件中查找具体的控件,两者并不完全相同. 应用场景: 1.在一个没有载入或者想要动态载入的界面中,需要使用layoutInflater.inflate()来载入布局文件: 2.对于一个已经载入的界面,就可以使用findViewById方法来获得其中的界

android脱壳之DexExtractor原理分析[zhuan]

http://www.cnblogs.com/jiaoxiake/p/6818786.html内容如下 导语: 上一篇我们分析android脱壳使用对dvmDexFileOpenPartial下断点的原理,使用这种方法脱壳的有2个缺点: 1.  需要动态调试 2.  对抗反调试方案 为了提高工作效率, 我们不希望把宝贵的时间浪费去和加固的安全工程师去做对抗.作为一个高效率的逆向分析师, 笔者是忍不了的,所以我今天给大家带来一种的新的脱壳方法——DexExtractor脱壳法. 资源地址: Dex

android脱壳之DexExtractor原理分析

导语: 上一篇我们分析android脱壳使用对dvmDexFileOpenPartial下断点的原理,使用这种方法脱壳的有2个缺点: 1.  需要动态调试 2.  对抗反调试方案 为了提高工作效率, 我们不希望把宝贵的时间浪费去和加固的安全工程师去做对抗.作为一个高效率的逆向分析师, 笔者是忍不了的,所以我今天给大家带来一种的新的脱壳方法--DexExtractor脱壳法. 资源地址: DexExtractor源码:https://github.com/bunnyblue/DexExtracto

Android视图SurfaceView的实现原理分析

附:Android控件TextView的实现原理分析 来源:http://blog.csdn.net/luoshengyang/article/details/8661317 在Android系统中,有一种特殊的视图,称为SurfaceView,它拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面.由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独立的线程中进行绘制.又由于不会占用主线程资源,SurfaceView一方面可以实现复杂而高效的UI,另一方面又不会导致用户输

Android 4.4 KitKat NotificationManagerService使用详解与原理分析(一)__使用详解

概况 Android在4.3的版本中(即API 18)加入了NotificationListenerService,根据SDK的描述(AndroidDeveloper)可以知道,当系统收到新的通知或者通知被删除时,会触发NotificationListenerService的回调方法.同时在Android 4.4 中新增了Notification.extras 字段,也就是说可以使用NotificationListenerService获取系统通知具体信息,这在以前是需要用反射来实现的. 转载请

Android 4.4 KitKat NotificationManagerService使用详解与原理分析(二)__原理分析

前置文章: <Android 4.4 KitKat NotificationManagerService使用详解与原理分析(一)__使用详解> 转载请务必注明出处:http://blog.csdn.net/yihongyuelan 概况 在上一篇文章<Android 4.4 KitKat NotificationManagerService使用详解与原理分析(一)__使用详解>中详细介绍了NotificationListenerService的使用方法,以及在使用过程中遇到的问题和