四大组件相关知识点回顾

        最近看了一遍关于Activity的知识点总结的文章,发现在平时开发的过程中,用的最多的Activity居然还有很多细小的知识点需要重复回忆,所以就顺势总结了一下四大组件相关的知识点,里面的一些知识点可能在平时的开发过程中不常接触,但是通过这些知识点的学习和回顾,可以让你更加全面和深入的了解系统提供的每个组件的原因和意义,也希望每个同学在看完这篇文章后,可以在日常的开发过程中,遇到问题,可以从源码设计的本质出发,做到举一反三,从本质分析问题,解决问题,下面就列出相关的知识点。


相关知识点汇总:

一:Activity知识点回顾

二:Service知识点回顾

三:BroadcaseReceiver知识点回顾 

四:ContentProvider知识点回顾 

五:Context知识点回顾 

六:扩展阅读


一:Activity知识点回顾

问题一:Dialog 弹出时对Activity生命周期的影响会怎样?

1、如果是单纯是创建的 dialog ,Activity 并不会执行生命周期的方法

2、但是如果是跳转到一个不是全屏的 Activity 的话, 当然就是按照正常的生命周期来执行了

3、onPause() ( 不会执行原 Activity 的 onStop() , 否则上个页面就不显示了 )


问题二:横竖屏切换时的生命周期影响?

1、不设置 Activity 的 android:configChanges 时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次

2、设置 Activity 的 android:configChanges="orientation" 时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次

3、设置 Activity 的 android:configChanges="orientation|keyboardHidden" 时,切屏不会重新调用各个生命周期,只会执行 onConfigurationChanged 方法

注意:还有一点,非常重要,一个 Android 的变更细节!当 API >12 时,需要加入 screenSize 属性,否则屏幕切换时即使你设置了 orientation 系统也会重建 Activity !


问题三:不同场景下 Activity 生命周期的变化过程?

1、锁屏时会执行 onPause() 和 onStop() , 而开屏时则应该执行 onStart() onResume()

2、Activity 退居后台: 当前 Activity 转到新的 Activity 界面或按 Home 键回到主屏: onPause() ---> onStop() ,进入停滞状态。

3、Activity 返回前台: onRestart() ---> onStart() ---> onResume() ,再次回到运行状态。

4、Activity 退居后台: 且系统内存不足, 系统会杀死这个后台状态的 Activity ,若再次回到这个

5、Activity ,则会走 onCreate() --> onStart() ---> onResume()


问题四:如何退出已调用多个 Activity 的 Application?

1、发送特定广播:

在需要结束应用时, 发送一个特定的广播,每个 Activity 收到广播后,关闭 即可。

给某个 activity 注册接受接受广播的意图 registerReceiver(receiver, filter)

如果过接受到的是 关闭 activity 的广播 activity finish()掉。

2、递归退出

就调用 finish() 方法 把当前的 Activity 退出,在打开新的 Activity 时使用 startActivityForResult , 然后自己加标志, 在 onActivityResult 中处理, 递归关闭。

3、通过flag实现

也可以通过 intent的flag来实现intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)激活一个新的 activity。

此时如果该任务栈中已经有该 Activity , 那么系统会把这个Activity上面的所有Activity干掉。

其实相当于给 Activity 配置的启动模式为 singleTask。

4、记录打开的 Activity

每打开一个 Activity , 就记录下来。

在需要退出时 , 关闭每一个Activity。


问题五:修改 Activity进入和退出动画的实现?

1、可以通过两种方式 ,一是通过定义Activity的主题 ,二是通过覆写Activity的 overridePendingTransition方法。

2、通过设置主题样式在 styles.xml 中编辑代码 , 添加 themes.xml 文件:在AndroidManifest.xml中给指定的 Activity指定theme。

3、覆写overridePendingTransition方法:overridePendingTransition(R.anim.fade, R.anim.hold);


问题六:如何处理异常退出?

1、Activity 异常退出的时候 --> onPause() --> onSaveInstanceState() --> onStop() --> onDestory()

2、需要注意的是 onSaveInstanceState() 方法与 onPause 并没有严格的先后关系,有可能在 onPause 之前,也有可能在其后面调用,但会在 onStop() 方法之前调用

3、异常退出后又重新启动该 Activity --> onCreate() --> onStart() --> onRestoreInstanceState() --> onResume()


问题七:什么是onNewIntent,什么时候触发执行?

1、如果 IntentActivity 处于任务栈的顶端,也就是说之前打开过的 Activity ,现在处于 onPause 、 onStop 状态的话,其他应用再发送 Intent 的话

2、执行顺序为:onNewIntent,onRestart,onStart,onResume。


问题八:Activity的Flags有哪些,有什么作用?

1、FLAG_ACTIVITY_NEW_TASK

作用是为 Activity 指定 “SingleTask” 启动模式。跟在 AndroidMainfest.xml 指定效果同样。

2、FLAG_ACTIVITY_SINGLE_TOP

作用是为 Activity 指定 “SingleTop” 启动模式,跟在 AndroidMainfest.xml 指定效果同样。

3、FLAG_ACTIVITY_CLEAN_TOP

具有此标记位的 Activity ,启动时会将与该 Activity 在同一任务栈的其他 Activity 出栈。

一般与 SingleTask 启动模式一起出现。

它会完成 SingleTask 的作用。

但事实上 SingleTask 启动模式默认具有此标记位的作用。

4、FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

4.1、具有此标记位的 Activity 不会出如今历史 Activity 的列表中

4.2、使用场景:当某些情况下我们不希望用户通过历史列表回到 Activity 时,此标记位便体现了它的效果。

4.3、它等同于在 xml 中指定 Activity 的属性.


问题九:Activity间通过Intent传递数据大小限制是多大?

1、Intent 在传递数据时是有大小限制的,这里官方并未详细说明,不过通过实验的方法可以测出数据应该被限制在 1MB 之内( 1024KB )

2、我们采用传递 Bitmap 的方法,发现当图片大小超过 1024(准确地说是 1020 左右)的时候,程序就会出现闪退、停止运行等异常(不同的手机反应不同)

3、因此可以判断 Intent 的传输容量在 1MB 之内。


问题十:内存不足时系统会杀掉后台的Activity,若需要进行一些临时状态的保存,在哪个方法进行?

1、Activity 的 onSaveInstanceState() 和 onRestoreInstanceState() 并不是生命周期方法,它们不同于 onCreate() 、onPause() 等生命周期方法,它们并不一定会被触发。

2、onSaveInstanceState() 方法,当应用遇到意外情况(如:内存不足、用户直接按 Home 键)由系统销毁一个 Activity ,onSaveInstanceState() 会被调用。

3、但是当用户主动去销毁一个 Activity 时,例如在应用中按返回键,onSaveInstanceState() 就不会被调用。

4、除非该 activity 不是被用户主动销毁的,通常 onSaveInstanceState() 只适合用于保存一些临时性的状态,而 onPause() 适合用于数据的持久化保存。


问题十一:onSaveInstanceState() 被执行的场景有哪些?

1、系统不知道你按下 HOME 后要运行多少其他的程序,自然也不知道 activity A 是否会被销毁

2、因此系统都会调用 onSaveInstanceState() ,让用户有机会保存某些非永久性的数据。

以下几种情况的分析都遵循该原则:

2.1、当用户按下 HOME 键时

2.2、长按 HOME 键,选择运行其他的程序时

2.3、锁屏时

2.4、从activity A 中启动一个新的 activity 时

2.5、屏幕方向切换时


问题十二:scheme跳转协议简介?

1、定义

1.1、服务器可以定制化跳转 app 页面。

1.2、app 可以通过 Scheme 跳转到另一个 app 页面。

1.3、可以通过 h5 页面跳转 app 原生页面。

2、协议格式

Uri.parse("ph://test:8080/goods?goodsId==8897&name=fuck")

1、qh 代表 Scheme 协议名称

2、test 代表 Scheme 作用的地址域

3、8080 代表该路径的端口号

4、/goods 代表的是指定页面(路径)

5、goodsId 和 name 代表传递的两个参数

3、Scheme使用

4、获取Scheme跳转的参数


调用方式:

1、原生调用

Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse("ph://test:8080/goods?goodsId==8897&name=fuck"));

startActivity(intent);

2、html调用

<a href="ph://test:8080/goods?goodsId==8897&name=fuck">打开商品详情</a>

3、判断某个Scheme是否有效

Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse("ph://test:8080/goods?goodsId==8897&name=fuck"));

List<ResolveInfo> activites = getPackageManager().queryIntentActivities(intent,0);

boolean isValid = !activities.isEmpty();

if(isVallid){

  startActivity(intent);

}


问题十三:ActivityLifecycleCallbacks的使用场景有哪些?

   ActivityLifecycleCallbacks 是用来监听所有 Activity 的生命周期回调。接口定义如下:

public interface ActivityLifecycleCallbacks {

    void onActivityCreated(Activity activity, Bundle savedInstanceState);

    void onActivityStarted(Activity activity);

    void onActivityResumed(Activity activity);

    void onActivityPaused(Activity activity);

    void onActivityStopped(Activity activity);

    void onActivitySaveInstanceState(Activity activity, Bundle outState);

    void onActivityDestroyed(Activity activity);

}

应用场景:

1、应用新开进程加重启处理(低内存回收、修改权限)

2、管理 Activity 页面栈

3、获取当前 Activity 页面

4、判断应用前后台

5、保存恢复状态值 savedInstanceState

6、页面分析统计埋点

7、对BaseActivity的封装部分可以在这里实现


问题十四:Activity的管理机制如何?

1、AMS提供了一个ArrayList mHistory来管理所有的activity,activity在AMS中的形式是ActivityRecord,task在AMS中的形式为TaskRecord,进程在AMS中的管理形式为ProcessRecord。

如下图所示:


从图中我们可以看出如下几点规则:

1、所有的ActivityRecord会被存储在mHistory管理;

2、每个ActivityRecord会对应到一个TaskRecord,并且有着相同TaskRecord的ActivityRecord在mHistory中会处在连续的位置;

3、同一个TaskRecord的Activity可能分别处于不同的进程中,每个Activity所处的进程跟task没有关系;

      Activity启动时ActivityManagerService会为其生成对应的ActivityRecord记录,并将其加入到回退栈(back stack)中,另外也会将ActivityRecord记录加入到某个Task中。请记住,ActivityRecord,backstack,Task都是ActivityManagerService的对象,由ActivityManagerService进程负责维护,而不是由应用进程维护。

      在回退栈里属于同一个task的ActivityRecord会放在一起,也会形成栈的结构,也就是说后启动的Activity对应的ActivityRecord会放在task的栈顶。

什么是ActivityRecord:

1、Activityrecord源码中的注解介绍:An Entry in the History Stack,Representing An Activity。

2、翻译:历史栈中的一个条目,代表一个Activity。

3、一个Activityrecord对应一个Activity,保存了一个Activity的所有信息,但是一个Activity可能会有多个Activityrecord,因为Activity可以被多次启动,这个主要取决于其启动模式。

什么是TaskRecord:

1、TaskRecord内部维护一个ArrayList<Activityrecord>用来保存Activityrecord。

2、一个Taskrecord由一个或者多个Activityrecord组成,这就是我们常说的任务栈,具有后进先出的特点。

什么是ActivityManagerService:

1、Ams是系统的引导服务,应用进程的启动、切换和调度、四大组件的启动和管理都需要Ams的支持。

2、ActivityManager中的方法会通过ActivityManagerService的GetDefault方法来得到ActivityManagerProxy,通过

Amp就可以和Amn进行通信,而Amn是一个抽象类,他会将功能由它的子类Ams来处理,因此,Amp就是Amn的代理类。


问题十五:什么是 Activity

1、四大组件之一,通常一个用户交互界面对应一个 activity 。

2、activity 是 Context 的子类,同时实现了 window.callback 和 keyevent.callback ,可以处理与窗体用户交互的事件。

3、开发中常用的有 FragmentActivity 、ListActivity 、TabActivity( Android 4.0 被 Fragment 取代)


问题十六:android的任务栈Task

1、一个 Task 包含的就是 activity 集合,android 系统可以通过任务栈有序的管理 activity

2、一个app当中可能不止一个任务栈,在某些情况下,一个 activity 也可以独享一个任务栈( singleInstance 模式启动的 activity )


问题十七:Activity的onStop和onDestroy()回调延时执行的原因

    Activity的onDestory是系统回调方法,调用时机本来就是不确定的,并不是是用户看见界面消失了,onDestroy()就是执行了。


问题十八:activity的加载过程是怎么样的

第一步:Activity中最终到startActivityForResult()(mMainthread.getApplicationThread()传入了一个Applicationthread检查APT)

->Instrumentation#execStartActivity()和checkStartctivityResult()(这是在启动了Activity之后判断Activty是否启动成功,例如没有在AM中注册那么就会报错)

->ActivityManagerNative.getDefault().startActivity()(类似AIDL,实现了IAM,实际是由远端的AMS实现startActivity())

->ActivityStackSupervisor#startActivityMayWait()

->ActivityStack#resumeTopActivityInnerLocked

->ActivityStackSupervisor#realStartActivityLocked(在这里调用APT的scheduleLaunchActivity,也是AID,不过是在远端调起了本进程Application线程)

->ApplicationThread#scheduleLaunchActivity()(这本进程的一个线程,用于作为Service端来接受AMSclient端的调起)

->ActivityThread#handleLaunchActivity()(接收内类H的消息,ApplicationThread线程发送LAUNCH_ACTIVTY消息给H)->最终在ActivityThread#performLaunchActivity()中现Activity的启动完成了以下几件事。

第二步:从传入的ActivityClientRecord中获取待启动的Activty的组件信息。

第三步:创建类加载器,使用Instrumentation#newActivity(加载Activity对象)。

第四步:调用LoadedApk.makeApplication方法尝试创建Appliction,由于单例所以不会重复创建。

第五步:创建Context的实现类ContextImpl对象,并通过Activity#attach()完成数据初始化和Context建立联系,因为Activity是Context的桥接类,最后就是创建和关联window,让Window接收的事件传给Activity,在Window的创建过程中会调用ViewRootImpl的performTraversals()初始化View。

第六步:Instrumentation#callActivityOnCreate()->Activity#performCreate()->Activity#onCreate().onCreate()会通过Activity#setContentView()调用PhoneWindow的setContentView()更新界面。  


问题十九:activity的startActivity和context的startActivity区别

1、从Activity中启动新的Activity时可以直接mContext.startActivity(intent)就好

2、如果从其他Context中启动Activity则必须给intent设置Flag:

    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ;

    mContext.startActivity(intent);


问题二十:Android 屏幕旋转处理 AsyncTask 和 ProgressDialog的方案

1、如果是少量数据,可以通过onSaveInstanceState()和onRestoreInstanceState()进行保存与恢复。

Android会在销毁你的Activity之前调用onSaveInstanceState()方法,于是,你可以在此方法中存储关于应用状态的数据。然后你可以在onCreate()或onRestoreInstanceState()方法中恢复。

2、如果是大量数据,使用Fragment保持需要恢复的对象。

3、自已处理配置变化。


问题二十一:Android下拉通知栏会影响Activity的生命周期方法吗

不会的


问题二十二:android:windowSoftInputMode的作用?

1、activity主窗口与软键盘的交互模式, 自从API level 3 被引入活动的主窗口如何与包含屏幕上的软键盘窗口交互。

2、软键盘的状态——是否它是隐藏或显示——当活动(Activity)成为用户关注的焦点。

3、活动的主窗口调整——是否减少活动主窗口大小以便腾出空间放软键盘或是否当活动窗口的部分被软键盘覆盖时它的内容的当前焦点是可见的。


问题二十三:系统的启动过程

1、Android 系统启动后已经启动了 Zygote,ServiceManager,SystemServer 等系统进程;ServiceManager 进程中完成了 Binder 初始化;SystemServer 进程中 ActivityManagerService,WindowManagerService,PackageManagerService 等系统服务在 ServiceManager 中已经注册;最后启动了 Launcher 桌面应用。

2、其实 Launcher 本身就是一个应用程序,运行在自己的进程中,我们看到的桌面就是 Launcher 中的一个 Activity。

3、应用安装的时候,通过 PackageManagerService 解析 apk 的 AndroidManifest.xml 文件,提取出这个 apk 的信息写入到 packages.xml 文件中,这些信息包括:权限、应用包名、icon、apk 的安装位置、版本、userID 等等。packages.xml 文件位于系统目录下/data/system/packages.xml。

4、同时桌面 Launcher 会为安装过的应用生成不同的应用入口,对应桌面上的应用图标。


问题二十四:应用的启动过程

1、在 system_server 进程中的服务端 ActivityManagerService 收到 START_ACTIVITY_TRANSACTION 命令后进行处理,调用 startActivity() 方法。

2、从 Launcher 点击图标,如果应用没有启动过,则会 fork 一个新进程。创建新进程的时候,ActivityManagerService 会保存一个 ProcessRecord 信息,Activity 应用程序中的AndroidManifest.xml 配置文件中,我们没有指定 Application 标签的 process 属性,系统就会默认使用 package 的名称。每一个应用程序都有自己的 uid,因此,这里 uid + process 的组合就可以为每一个应用程序创建一个 ProcessRecord。每次在新建新进程前的时候会先判断这个 ProcessRecord 是否已存在,如果已经存在就不会新建进程了,这就属于应用内打开 Activity 的过程了。

3、进程创建成功切换至 App 进程,进入 app 进程后将 ActivityThread 类加载到新进程,并调用 ActivityThread.main() 方法。

4、此时只创建了应用程序的 ActivityThread 和 ApplicationThread,和开启了 Handler 消息循环机制,其他的都还未创建, ActivityThread.attach(false) 又会最终到 ActivityMangerService 的 attachApplication,这个工程其实是将本地的 ApplicationThread 传递到 ActivityMangerService。然后 ActivityMangerService 就可以通过 ApplicationThread 的代理 ApplicationThreadProxy 来调用应用程序 ApplicationThread.bindApplication,通知应用程序的 ApplicationThread 已和 ActivityMangerService 绑定,可以不借助其他进程帮助直接通信了。此时 Launcher 的任务也算是完成了。

5、在 system_server 进程中的服务端 ActivityManagerService 收到 ATTACH_APPLICATION_TRANSACTION 命令后进行处理,调用 attachApplication()。

5.1、Activity: 检查最顶层可见的 Activity 是否等待在该进程中运行,调用ActivityStackSupervisor.attachApplicationLocked()。

5.2、Service:寻找所有需要在该进程中运行的服务,调用 ActiveServices.attachApplicationLocked()。

5.3、Broadcast:检查是否在这个进程中有下一个广播接收者,调用 sendPendingBroadcastsLocked()。

备注:此处讨论 Activity 的启动过程,只讨论 ActivityStackSupervisor.attachApplicationLocked() 方法。


问题二十五:Activity的启动过程

疑问一:ActivityThread 是什么,它是一个线程吗,如何被启动的?

     它不是一个线程,它是运行在 App 进程中的主线程中的一个方法中。当 App 进程创建时会执行 ActivityThread.main(),ActivityThread.main() 首先会创建 Looper 执行 Looper.prepareMainLooper();然后创建 ActivityThread 并调用 ActivityThread.attach() 方法告诉 ActivityManagerService 我们创建了一个应用并将 ApplicationThread 传给 ActivityManagerService;最后调用 Looper.loop()。


疑问二:ActivityClientRecord 与 ActivityRecord 是什么?

     记录 Activity 相关信息,比如:Window,configuration,ActivityInfo 等。

ActivityClientRecord 是客户端的,ActivityRecord 是 ActivityManagerService 服务端的。


疑问三:Context 是什么,ContextImpl,ContextWapper 是什么?

     Context 定义了 App 进程的相关环境,Context 是一个接口,ContextImpl 是子类,ContextWapper 是具体实现。

     应用资源是在 Application 初始化的时候,也就是创建Application,ContextImpl的时候,ContextImpl就包含这个路径,主要就是对ResourcesManager 这个单例的引用。

    可以看出每次创建 Application 和 Acitvity 以及 Service 时就会有一个 ContextImpl 实例,ContentProvider 和BroadcastReceiver 的 Context 是其他地方传入的。

     所以 Context 数量 = Application 数量 + Activity 数量 + Service 数量,单进程情况下 Application 数量就是 1。


疑问四:Instrumentation 是什么?

     管理着组件Application,Activity,Service等的创建,生命周期调用。


疑问五:Application 是什么,什么时候创建的,每个应用程序有几个 Application?

       Application 是在 ActivityThread.handleBindApplication() 中创建的,一个进程只会创建一个 Application,但是一个应用如果有多个进程就会创建多个 Application 对象。


疑问六:点击 Launcher 启动 Activity 和应用内部启动 Activity 的区别?

       点击 Launcher 时会创建一个新进程来开启 Activity,而应用内打开 Activity,如果 Activity 不指定新进程,将在原来进程打开,是否开启新进程是在 ActivityManagerService 进行控制的,上面分析得到,每次开启新进程时会保存进程信息,默认为 应用包名 + 应用UID,打开 Activity 时会检查请求方的信息来判断是否需要新开进程。Launcher 打开 Activity 默认 ACTIVITY_NEW_TASK,新开一个 Activity 栈来保存 Activity 的信息。


疑问七:Activity 启动过程,onCreate(),onResume() 回调时机及具体作用?

       Activity.onCreate() 完成了 App 进程,Application,Activity 的创建,调用 setContentView() 给 Activity 设置了 layout 布局。

Activity.onResume() 完成了 Activity 中 Window 与 WindowManager 的关联,并对所有子 View 进行渲染并显示。


二:Service知识点回顾

问题一:什么是 Service

1、Service (服务) 是一个一种可以在后台执行长时间运行操作而没有用户界面的应用组件。

2、服务可由其他应用组件启动(如 Activity ),服务一旦被启动将在后台一直运行,即使启动服务的组件( Activity )已销毁也不受影响。

3、此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 ( IPC )。

4、Service 通常总是称之为 “后台服务”

4.1、其中“后台”一词是相对于前台而言的,具体是指:其本身的运行并不依赖于用户可视的 UI 界面

4.2、因此,从实际业务需求上来理解,Service 的适用场景应该具备以下条件:

4.3、并不依赖于用户可视的 UI 界面(当然,这一条其实也不是绝对的,如前台 Service 就是与 Notification 界面结合使用的)

4.4、具有较长时间的运行特性。

4.5、注意: 是运行在主线程当中的。

5、服务进程

5.1、服务进程是通过 startService() 方法启动的进程,但不属于前台进程和可见进程。例如,在后台播放音乐或者在后台下载就是服务进程。

5.2、系统保持它们运行,除非没有足够内存来保证所有的前台进程和可视进程。


问题二:Service的生命周期

二、bindService()

1、创建 BindService 服务端,继承 Service 并在类中,创建一个实现 IBinder 接口的实例对象,并提供公共方法给客户端( Activity )调用。

2、从 onBinder() 回调方法返回该 Binder 实例。

3、在客户端( Activity )中, 从 onServiceConnection() 回调方法参数中接收 Binder ,通过 Binder 对象即可访问 Service 内部的数据。

4、在 manifests 中注册 BindService , 在客户端中调用 bindService() 方法开启绑定 Service , 调用 unbindService() 方法注销解绑 Service 。

5、该启动方式依赖于客户端生命周期,当客户端 Activity 销毁时, 没有调用 unbindService() 方法 , Service 也会停止销毁。

三:需要注意的是如果这几个方法交织在一起的话,会出现什么情况呢?

1、一个原则是 Service 的 onCreate 的方法只会被调用一次,就是你无论多少次的 startService 又 bindService ,Service 只被创建一次。

2、如果先是 bind 了,那么 start 的时候就直接运行 Service 的 onStartCommand 方法,如果先是 start ,那么 bind 的时候就直接运行 onBind 方法。

3、如果 service 运行期间调用了 bindService ,这时候再调用 stopService 的话,service 是不会调用 onDestroy 方法的,service 就 stop 不掉了,只能调用 UnbindService , service 就会被销毁

4、如果一个 service 通过 startService 被 start 之后,多次调用 startService 的话,service 会多次调

用 onStartCommand 方法。多次调用 stopService 的话,service 只会调用一次 onDestroyed 方法。

5、如果一个 service 通过 bindService 被 start 之后,多次调用 bindService 的话,service 只会调用一次 onBind 方法。多次调用 unbindService 的话会抛出异常。


问题三:Service与Thread的区别

定义上的区别:

1、thread 是程序执行的最小单元,他是分配 cpu 的基本单位安卓系统中,我们常说的主线程,UI 线程,也是线程的一种。当然,线程里面还可以执行一些耗时的异步操作。

2、而 service 大家记住,它是安卓中的一种特殊机制,service 是运行在主线程当中的,所以说它不能做耗时操作,它是由系统进程托管,其实 service 也是一种轻量级的 IPC 通信,因为 activity 可以和 service 绑定,可以和 service 进行数据通信。

3、而且有一种情况,activity 和 service 是处于不同的进程当中,所以说它们之间的数据通信,要通过 IPC 进程间通信的机制来进行操作。

在实际开发的过程当中的区别:

1、在安卓系统当中,线程一般指的是工作线程,就是后台线程,做一些耗时操作的线程,而主线程是一种特殊的线程,它只是负责处理一些 UI 线程的绘制,UI 线程里面绝对不能做耗时操作,这里是最基本最重要的一点。(这是 Thread 在实际开发过程当中的应用)

2、而 service 是安卓当中,四大组件之一,一般情况下也是运行在主线程当中,因此 service 也是不可以做耗时操作的,否则系统会报 ANR 异常( ANR 全称:Application Not Responding ),就是程序无法做出响应。

3、如果一定要在 service 里面进行耗时操作,一定要记得开启单独的线程去做。

应用场景上的区别:

1、当你需要执行耗时的网络,或者这种文件数据的查询,以及其它阻塞 UI 线程的时候,都应该使用工作线程,也就是开启一个子线程的方式。

2、这样才能保证 UI 线程不被占用,而影响用户体验。

3、而 service 来说,我们经常需要长时间在后台运行,而且不需要进行交互的情况下才会使用到服务,比如说,我们在后台播放音乐,开启天气预报的统计,还有一些数据的统计等等。


问题四:为什么要用Service而不是Thread

1、Thread 的运行是独立于 Activity 的,也就是当一个 Activity 被 finish 之后,如果没有主动停止 Thread 或者 Thread 中的 run 没有执行完毕时那么这个线程会一直执行下去。

2、因此这里会出现一个问题:当 Activity 被 finish 之后,你不再持有该Thread的引用。

3、另一方面,你没有办法在不同的 Activity 中对同一 Thread 进行控制。


问题五:Service 里面是否能执行耗时的操作

1、service 里面不能执行耗时的操作(网络请求,拷贝数据库,大文件 )

2、Service不是独立的进程,也不是独立的线程,它是依赖于应用程序的主线程的,也就是说,在更多时候不建议在 Service 中编写耗时的逻辑和操作(比如:网络请求,拷贝数据库,大文件),否则会引起 ANR 。

3、如果想在服务中执行耗时的任务。有以下解决方案:

3.1、在service 中开启一个子线程new Thread(){}.start();

3.2、可以使用 IntentService 异步管理服务。


问题六:Service是否在main thread中执行

1、默认情况, 如果没有显示的指 service 所运行的进程, Service 和 activity 是运 行在当前 app 所在进程的 main thread ( UI 主线程)里面。

2、Service 和 Activity 在同一个线程,对于同一 app 来说默认情况下是在同一个线程中的 main Thread ( UI Thread )。

3、特殊情况 ,可以在清单文件配置 service 执行所在的进程 ,让 service 在另 外的进程中执行 Service 不死之身。


问题七:Service的使用

1、在 onStartCommand 方法中设置为前台进程:

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

  Notification notification = new Notification(R.mipmap.ic_launcher, "服务正在运行",System.currentTimeMillis());

   Intent notificationIntent = new Intent(this, MainActivity.class);

    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,notificationIntent,0);

    RemoteViews remoteView = new RemoteViews(this.getPackageName(),R.layout.notification);

    remoteView.setImageViewResource(R.id.image, R.mipmap.ic_launcher);

    remoteView.setTextViewText(R.id.text , "Hello,this message is in a custom expanded view");

    notification.contentView = remoteView;

    notification.contentIntent = pendingIntent;

    startForeground(1, notification);

    return Service.START_STICKY;

}

2、用AlarmManager.setRepeating(…)方法循环发送闹钟广播, 接收的时候调用service的onStartCommand方法

Intent intent = new Intent(MainActivity.this,MyAlarmReciver.class);

PendingIntent sender = PendingIntent.getBroadcast( MainActivity.this, 0, intent, 0);

// We want the alarm to go off 10 seconds from now.

Calendar calendar = Calendar.getInstance();

calendar.setTimeInMillis(System.currentTimeMillis());

calendar.add(Calendar.SECOND, 1);

AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);

//重复闹钟

/**

*  @param type

* @param triggerAtMillis t 闹钟的第一次执行时间,以毫秒为单位

* go off, using the appropriate clock (depending on the alarm type).

* @param intervalMillis 表示两次闹钟执行的间隔时间,也是以毫秒为单位

* of the alarm.

* @param operation 绑定了闹钟的执行动作,比如发送一个广播、给出提示等等

*/

am.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), 2 * 1000, sender);


问题八:什么是 IntentService

1、IntentService 是 Service 的子类,比普通的 Service 增加了额外的功能。

2、我们常用的 Service 存在两个问题:

2.1、Service 不会专门启动一条单独的进程,Service 与它所在应用位于同一个进程中

2.2、Service 也不是专门一条新线程,因此不应该在 Service 中直接处理耗时的任务


问题九:IntentService 的特征

1、会创建独立的 worker 线程来处理所有的 Intent 请求

2、会创建独立的 worker 线程来处理 onHandleIntent() 方法实现的代码,无需处理多线程问题

3、所有请求处理完成后,IntentService 会自动停止,无需调用 stopSelf() 方法停止 Service

4、为 Service 的 onBind() 提供默认实现,返回 null

5、为 Service 的 onStartCommand 提供默认实现,将请求 Intent 添加到队列中


问题十:Service和IntentService区别

Service 是用于后台服务的:

1、当应用程序被挂到后台的时候,为了保证应用某些组件仍然可以工作而引入了 Service 这个概念

2、那么这里面要强调的是:Service 不是独立的进程,也不是独立的线程,它是依赖于应用程序的主线程的,也就是说,在更多时候不建议在 Service 中编写耗时的逻辑和操作,否则会引起 ANR 。

也就是,service 里面不可以进行耗时的操作。虽然在后台服务。但是也是在主线程里面。

当我们编写的耗时逻辑,不得不被 service 来管理的时候,就需要引入 IntentService 。

1、IntentService 是继承 Service 的,那么它包含了 Service 的全部特性,当然也包含 service 的生命周期。

2、那么与 service 不同的是,IntentService 在执行 onCreate 操作的时候,内部开了一个线程,去你执行你的耗时操作。

3、IntentService 是一个通过 Context.startService(Intent) 启动可以处理异步请求的 Service。

3.1、使用时你只需要继承 IntentService 和重写其中的 onHandleIntent(Intent) 方法接收一个 Intent 对象 , 在适当的时候会停止自己 ( 一般在工作完成的时候 ) 。

3.2、所有的请求的处理都在一个工作线程中完成 , 它们会交替执行 ( 但不会阻塞主线程的执行 ) ,一次只能执行一个请求。

4、是一个基于消息的服务

4.1、每次启动该服务并不是马上处理你的工作,而是首先会创建对应的 Looper ,Handler 并且在 MessageQueue 中添加的附带客户 Intent 的 Message 对象。

4.2、当 Looper 发现有 Message 的时候接着得到 Intent 对象通过在 onHandleIntent((Intent)msg.obj) 中调用你的处理程序,处理完后即会停止自己的服务。

4.3、意思是 Intent 的生命周期跟你的处理的任务是一致的,所以这个类用下载任务中非常好,下载任务结束后服务自身就会结束退出。

5、IntentService 的特征

5.1、会创建独立的 worker 线程来处理所有的 Intent 请求;

5.2、会创建独立的 worker 线程来处理 onHandleIntent() 方法实现的代码,无需处理多线程问题;

5.3、所有请求处理完成后,IntentService会自动停止,无需调用 stopSelf() 方法停止 Service ;


问题十一:Service与 Activity的相关

1、Activity怎么和Service 绑定,怎么在Activity中启动对应的Service

1.1、Activity 通过 bindService(Intent service, ServiceConnection conn, int flags) 跟 Service 进行绑定,当绑定成功的时候 Service 会将代理对象通过回调的形式传给 conn ,这样我们就拿到了 Service 提供的服务代理对象。

1.2、在 Activity 中可以通过 startService 和 bindService 方法启动 Service。一般情况下如果想获取 Service 的服务对象那么肯定需要通过 bindService() 方法,比如音乐播放器,第三方支付等。

1.3、如果仅仅只是为了开启一个后台任务那么可以使用 startService()方法。


2、说说Activity、Intent 、Service是什么关系

2.1、他们都是 Android 开发中使用频率最高的类。其中 Activity 和 Service 都属于 Android 的四大组件。他俩都是 Context 类的子类 ContextWrapper 的子类,因此他俩可以算是兄弟关系吧。

2.2、不过他们各有各自的本领,Activity 负责用户界面的显示和交互,Service 负责后台任务的处理。

2.3、Activity 和 Service 之间可以通过 Intent 传递数据,因此可以把 Intent 看作是通信使者。


3、Activity与Service交互方式

3.1、广播交互

1、Server 端将目前的下载进度,通过广播的方式发送出来,Client 端注册此广播的监听器,当获取到该广播后,将广播中当前的下载进度解析出来并更新到界面上。

2、定义自己的广播,这样在不同的 Activity 、Service 以及应用程序之间,就可以通过广播来实现交互。


3.2、共享文件交互

1、我们使用 SharedPreferences 来实现共享,当然也可以使用其它 IO 方法实现,通过这种方式实现交互时需要注意,对于文件的读写的时候,同一时间只能一方读一方写,不能两方同时写。

2、Server 端将当前下载进度写入共享文件中,Client 端通过读取共享文件中的下载进度,并更新到主界面上。


3.3、Messenger交互( 信使交互)

1、Messenger 翻译过来指的是信使,它引用了一个 Handler 对象,别人能够向它发送消息 ( 使用 mMessenger.send ( Message msg ) 方法)。

2、该类允许跨进程间基于 Message 通信,在服务端使用 Handler 创建一个 Messenger ,客户端只要获得这个服务端的 Messenger 对象就可以与服务端通信了。

3、在 Server 端与 Client 端之间通过一个 Messenger 对象来传递消息,该对象类似于信息中转站,所有信息通过该对象携带。


3.4、自定义接口交互

1、其实就是我们自己通过接口的实现来达到 Activity 与 Service 交互的目的,我们通过在 Activity 和 Service 之间架设一座桥樑,从而达到数据交互的目的,而这种实现方式和 AIDL 非常类似

2、自定义一个接口,该接口中有一个获取当前下载进度的空方法。Server 端用一个类继承自 Binder 并实现该接口,覆写了其中获取当前下载进度的方法。Client 端通过 ServiceConnection 获取到该类的对象,从而能够使用该获取当前下载进度的方法,最终实现实时交互。


3.5、AIDL交互

1、远程服务一般通过 AIDL 来实现,可以进行进程间通信,这种服务也就是远程服务。

2、AIDL 属于 Android 的 IPC 机制,常用于跨进程通信,主要实现原理基于底层 Binder 机制。

备注:Service与Activity的交互方式详解:https://blog.csdn.net/cjj2100/article/details/39180061


问题十二:Service为什么被设计出来

1、根据 Service 的定义,我们可以知道需要长期在后台进行的工作我们需要将其放在 Service 中去做。

2、得再通熟易懂一点,就是不能放在 Activity 中来执行的工作就必须得放到 Service 中去做。

3、如:音乐播放、下载、上传大文件、定时关闭应用等功能。这些功能如果放到 Activity 中做的话,那么 Activity 退出被销毁了的话,那这些功能也就停止了,这显然是不符合我们的设计要求的,所以要将他们放在 Service 中去执行。


问题十三:onStartCommand() 返回值 int 值的区别

1、START_STICKY :

如果 service 进程被 kill 掉,保留 service 的状态为开始状态,但不保留递送的 intent 对象。

随后系统会尝试重新创建 service, 由于服务状态为开始状态,所以创建服务后一定会调用 onStartCommand ( Intent, int, int ) 方法。

如果在此期间没有任何启动命令被传递到 service , 那么参数 Intent 将为 null 。

2、START_NOT_STICKY :

“非粘性的”。

使用这个返回值时 , 如果在执行完 onStartCommand 后 , 服务被异常 kill 掉 ,系统不会自动重启该服务。

3、START_REDELIVER_INTENT:

重传 Intent 。

使用这个返回值时,如果在执行完 onStartCommand 后,服务被异常 kill 掉

系统会自动重启该服务 , 并将 Intent 的值传入。

4、START_STICKY_COMPATIBILITY:

START_STICKY 的兼容版本 , 但不保证服务被 kill 后一定能重启。


问题十四:Service 的 onRebind ( Intent ) 方法在什么情况下会执行

1、如果在 onUnbind() 方法返回 true 的情况下会执行 , 否则不执行。


问题十五:如何提高 service 的优先级

1、在 AndroidManifest.xml 文件中对于 intent-filter 可以通过 android:priority = “1000” 这个属性设置最高优先级,1000 是最高值,如果数字越小则优先级越低,同时实用于广播。

2、在 onStartCommand 里面调用 startForeground() 方法把 Service 提升为前台进程级别,然后再 onDestroy 里面要记得调用 stopForeground () 方法。

3、onStartCommand 方法,手动返回 START_STICKY 。

4、广播

4.1、在 onDestroy 方法里发广播重启 service 。

service + broadcast 方式,就是当 service 走 ondestory 的时候,发送一个自定义的广播

4.2、当收到广播的时候,重新启动 service 。( 第三方应用或是在 setting 里-应用强制停止时,APP 进程就直接被干掉了,onDestroy 方法都进不来,所以无法保证会执行 )

5、监听系统广播判断 Service 状态。

5.1、通过系统的一些广播

5.2、比如:手机重启、界面唤醒、应用状态改变等等监听并捕获到,然后判断我们的 Service 是否还存活。

6、Application 加上 Persistent 属性。


问题十六:AccessibilityService类的作用

描述:辅助功能(AccessibilityService)是一个Android系统提供的一种服务,继承自Service类。AccessibilityService运行在后台,能够监听系统发出的一些事件(AccessibilityEvent),这些事件主要是UI界面一系列的状态变化,比如按钮点击、输入框内容变化、焦点变化等等,查找当前窗口的元素并能够模拟点击等事件。

使用:这个系统功能主要为一些残障人士用户设计,他们由于各种原因比如视力、年龄、身体等因素导致使用Android设备困难。但是很多android开发者用这个功能来做一些不正常的操作。


问题十七:怎么在Service中创建Dialog对话框

1、在我们取得Dialog对象后,需给它设置类型,即:

dialog.getWindow().setType(WindowManager.LayoutPaams.TYPE_SYSTEM_ALERT)

2、在Manifest中加上权限:

   <uses-permissionandroid:name="android.permission.SYSTEM_ALERT_WINOW" />


问题十八:直接在Activity中创建一个thread跟在service中创建一个thread之间的区别

描述:Service是Android的四大组件之一,被用来执行长时间的后台任务,同样,线程也可以实现在后台执行任务。

需要了解Service的几个特点。

1、默认情况下,Service其实是运行在主线程中的,如果需要执行复杂耗时的操作,必须在Service中再创建一个Thread来执行任务。

2、Service的优先级高于后台挂起的Activity,当然,也高于Activity所创建的Thread,因此,系统可能在内存不足的时候优先杀死后台的Activity或者Thread,而不会轻易杀死Service组件,即使被迫杀死Service,也会在资源可用时重启被杀死的Service

3、Service和Thread根本就不是一个级别的东西,Service是系统的四大组件之一,Thread只是一个用来执行后台任务的工具类,它可以在Activity中被创建,也可以在Service中被创建。因此,我们其实不应该讨论该使用Service还是Thread,而是应该讨论在什么地方创建Thread。

    典型的应用中,它可以在以下三个位置被创建,不同的位置,其生命周期不一样,所以,我们应该根据该Thread的目标生命周期来决定是在Service中创建Thread还是在Activity中创建它。

1、在Activity中被创建

    这种情况下,一般在onCreate时创建,在onDestroy()中销毁,否则,Activity销毁后,Thread是会依然在后台运行着。

    这种情况下,Thread的生命周期即为整个Activity的生命周期。所以,在Activity中创建的Thread只适合完成一些依赖Activity本身有关的任务,比如定时更新一下Activity的控件状态等。

    核心特点:该Thread的就是为这个Activity服务的,完成这个特定的Activity交代的任务,主动通知该Activity一些消息和事件,Activity销毁后,该Thread也没有存活的意义了。

2、在Application中被创建

    这种情况下,一般自定义Application类,重载onCreate方法,并在其中创建Thread,当然,也会在onTerminate()方法中销毁Thread,否则,如果Thread没有退出的话,即使整个Application退出了,线程依然会在后台运行着。

    这种情况下,Thread的生命周期即为整个Application的生命周期。所以,在Application中创建的Thread,可以执行一些整个应用级别的任务,比如定时检查一下网络连接状态等等。

    核心特点:该Thread的终极目标是为这个APP的各个Activity服务的,包括完成某个Activity交代的任务,主动通知某个Activity一些消息和事件等,APP退出之后该Thread也没有存活的意义了。

    以上这两种情况下,Thread的生命周期都不应该超出整个应用程序的生命周期,也就是,整个APP退出之后,Thread都应该完全退出,这样才不会出现内存泄漏或者僵尸线程。那么,如果你希望整个APP都退出之后依然能运行该Thread,那么就应该把Thread放到Service中去创建和启动了。

3、在Service中被创建

    这是保证最长生命周期的Thread的唯一方式,只要整个Service不退出,Thread就可以一直在后台执行,一般在Service的onCreate()中创建,在onDestroy()中销毁。

    所以,在Service中创建的Thread,适合长期执行一些独立于APP的后台任务,比较常见的就是:在Service中保持与服务器端的长连接。

    核心特点:该Thread可以为APP提供一些“服务”或者“状态查询”,但该Thread并不需要主动通知APP任何事件,甚至不需要知道APP是谁。


问题十九:AccessibilityService的作用

    辅助功能(AccessibilityService)是一个Android系统提供的一种服务,继承自Service类。AccessibilityService运行在后台,能够监听系统发出的一些事件(AccessibilityEvent),这些事件主要是UI界面一系列的状态变化,比如按钮点击、输入框内容变化、焦点变化等等,查找当前窗口的元素并能够模拟点击等事件。

    这个系统功能主要为一些残障人士用户设计,他们由于各种原因比如视力、年龄、身体等因素导致使用Android设备困难,但是很多android开发者用这个功能来做一些不正常的操作,例如微信抢红包等功能。


三:BroadcaseReceiver知识点回顾 

问题一:什么是 BroadcastReceiver?

1、是四大组件之一, 主要用于接收 app 发送的广播。

2、内部通信实现机制:通过 android 系统的 Binder 机制。


问题二:广播的类型有哪些?

1、无序广播

1.1、也叫标准广播,是一种完全异步执行的广播。

1.2、在广播发出之后,所有广播接收器几乎都会在同一时刻接收到这条广播消息,它们之间没有任何先后顺序,广播的效率较高。

1.3、优点: 完全异步, 逻辑上可被任何接受者收到广播,效率高。

1.4、缺点: 接受者不能将处理结果交给下一个接受者, 且无法终止广播。


2、有序广播

2.1、是一种同步执行的广播。

2.2、在广播发出之后,同一时刻只有一个广播接收器能够收到这条广播消息,当其逻辑执行完后该广播接收器才会继续传递。

2.3、调用 SendOrderedBroadcast() 方法来发送广播,同时也可调用 abortBroadcast() 方法拦截该广播。可通过 <intent-filter> 标签中设置 android:property 属性来设置优先级,未设置时按照注册的顺序接收广播。

2.4、有序广播接受器间可以互传数据。

2.5、当广播接收器收到广播后,当前广播也可以使用 setResultData 方法将数据传给下一个接收器。

2.6、使用 getStringExtra 函数获取广播的原始数据,通过 getResultData 方法取得上个广播接收器自己添加的数据,并可用 abortBroadcast 方法丢弃该广播,使该广播不再被别的接收器接收到。

2.7、总结

1、按被接收者的优先级循序传播 A > B > C ,

2、每个都有权终止广播, 下一个就得不到

3、每一个都可进行修改操作, 下一个就得到上一个修改后的结果.


3、最终广播者

3.1、Context.sendOrderedBroadcast ( intent , receiverPermission , resultReceiver , scheduler , initialCode , initialData , initialExtras ) 时我们可以指定 resultReceiver 为最终广播接收者。

3.2、如果比他优先级高的接受者不终止广播, 那么他的 onReceive 会执行两次。

3.3、第一次是正常的接收。

3.4、第二次是最终的接收。

3.5、如果优先级高的那个终止广播, 那么他还是会收到一次最终的广播。


问题三:常见的广播接收者运用场景

1、开机启动, sd 卡挂载, 低电量, 外拨电话, 锁屏等。

2、比如根据产品经理要求, 设计播放音乐时, 锁屏是否决定暂停音乐。


问题四:广播作为 Android 组件间的通信方式,主要使用场景有哪些

1、APP 内部的消息通信。

2、不同 APP 之间的消息通信。

3、Android 系统在特定情况下与 APP 之间的消息通信。

4、广播使用了观察者模式,基于消息的发布 / 订阅事件模型。广播将广播的发送者和接受者极大程度上解耦,使得系统能够方便集成,更易扩展。

5、BroadcastReceiver 本质是一个全局监听器,用于监听系统全局的广播消息,方便实现系统中不同组件间的通信。

6、自定义广播接收器需要继承基类 BroadcastReceiver ,并实现抽象方法 onReceive ( context, intent ) 。默认情况下,广播接收器也是运行在主线程,因此 onReceiver() 中不能执行太耗时的操作( 不超过 10s ),否则将会产生 ANR 问题。onReceiver() 方法中涉及与其他组件之间的交互时,可以使用发送 Notification 、启动 Service 等方式,最好不要启动 Activity。


问题五:常见的系统广播有哪些

1、Android 系统内置了多个系统广播,只要涉及手机的基本操作,基本上都会发出相应的系统广播,如开机启动、网络状态改变、拍照、屏幕关闭与开启、电量不足等。在系统内部当特定时间发生时,系统广播由系统自动发出。

2、常见系统广播 Intent 中的 Action 为如下值:

2.1、短信提醒:android.provider.Telephony.SMS_RECEIVED

2.2、电量过低:ACTION_BATIERY_LOW

2.3、电量发生改变:ACTION_BATTERY_CHANGED

2.4、连接电源:ACTION_POWER_CO

3、从 Android 7.0 开始,系统不会再发送广播 ACTION_NEW_PICTURE 和 ACTION_NEW_VIDEO ,对于广播 CONNECTIVITY_ACTION 必须在代码中使用 registerReceiver 方法注册接收器,在 AndroidManifest 文件中声明接收器不起作用。

4、从 Android 8.0 开始,对于大多数隐式广播,不能在 AndroidManifest 文件中声明接收器。


问题六:局部广播是什么

1、局部广播的发送者和接受者都同属于一个 APP

2、相比于全局广播具有以下优点:

2.1、其他的 APP 不会受到局部广播,不用担心数据泄露的问题。

2.2、其他 APP 不可能向当前的 APP 发送局部广播,不用担心有安全漏洞被其他 APP 利用。

2.3、局部广播比通过系统传递的全局广播的传递效率更高。

3、Android v4 包中提供了 LocalBroadcastManager 类,用于统一处理 APP 局部广播,使用方式与全局广播几乎相同,只是调用注册 / 取消注册广播接收器和发送广播偶读方法时,需要通过 LocalBroadcastManager 类的 getInstance() 方法获取的实例调用。


问题七:广播的常见属性有哪些?

1、android: exported

    其作用是设置此 BroadcastReceiver 能否接受其他 APP 发出的广播 ,当设为 false 时,只能接受同一应用的的组件或具有相同 user ID 的应用发送的消息。这个属性的默认值是由 BroadcastReceiver 中有无 Intent-filter 决定的,如果有 Intent-filter ,默认值为 true ,否则为 false 。

2、android: permission

    如果设置此属性,具有相应权限的广播发送方发送的广播才能被此 BroadcastReceiver 所接受;如果没有设置,这个值赋予整个应用所申请的权限。


问题八:BroadcastReceiver 的实现原理是什么?

1、Android 中的广播使用了设计模式中的观察者模式:基于消息的发布 / 订阅事件模型。

2、模型中主要有 3 个角色:消息订阅者( 广播接收者 )、消息发布者( 广播发布者 )、消息中心( AMS,即 Activity Manager Service )

3、实现过程

3.1、广播接收者通过 Binder 机制在 AMS( Activity Manager Service ) 注册;

3.2、广播发送者通过 Binder 机制向 AMS 发送广播;

3.3、AMS 根据广播发送者要求,在已注册列表中,寻找合适的 BroadcastReceiver ( 寻找依据:IntentFilter / Permission );

3.4、AMS 将广播发送到 BroadcastReceiver 相应的消息循环队列中;

3.5、广播接收者通过消息循环拿到此广播,并回调 onReceive() 方法。

3.6、需要注意的是:广播的发送和接受是异步的,发送者不会关心有无接收者或者何时收到。


问题九:本地广播注意事项有哪些

1、本地广播无法通过静态注册方式来接受,相比起系统全局广播更加高效。

2、在广播中启动 Activity 时,需要为 Intent 加入 FLAG_ACTIVITY_NEW_TASK 标记,否则会报错,因为需要一个栈来存放新打开的 Activity 。

3、广播中弹出 Alertdialog 时,需要设置对话框的类型为 TYPE_SYSTEM_ALERT ,否则无法弹出。

4、不要在 onReceiver() 方法中添加过多的逻辑或者进行任何的耗时操作,因为在广播接收器中是不允许开启线程的,当 onReceiver() 方法运行了较长时间而没有结束时,程序就会报错。


问题十:Sticky Broadcast 粘性广播

1、如果发送者发送了某个广播,而接收者在这个广播发送后才注册自己的 Receiver ,这时接收者便无法接收到刚才的广播。

2、为此 Android 引入了 StickyBroadcast ,在广播发送结束后会保存刚刚发送的广播( Intent ),这样当接收者注册完 Receiver 后就可以继续使用刚才的广播。

3、如果在接收者注册完成前发送了多条相同 Action 的粘性广播,注册完成后只会收到一条该 Action 的广播,并且消息内容是最后一次广播内容。

4、系统网络状态的改变发送的广播就是粘性广播。

4.1、粘性广播通过 Context 的 sendStickyBroadcast ( Intent ) 接口发送,需要添加权限。

4.2、uses-permission android:name=”android.permission.BROADCAST_STICKY”。

4.、也可以通过 Context 的 removeStickyBroadcast ( Intent intent ) 接口移除缓存的粘性广播。


问题十一:LocalBroadcastManager的实现原理

1、LocalBroadcastManager 内部协作主要是靠这两个 Map 集合:MReceivers 和 MActions ,当然还有一个 List 集合 MPendingBroadcasts ,这个主要就是存储待接收的广播对象。

2、LocalBroadcastManager 高效的原因主要是因为它内部是通过 Handler 实现的,它的 sendBroadcast() 方法含义并非和我们平时所用的一样,它的 sendBroadcast() 方法其实是通过 handler 发送一个 Message 实现的;

3、既然它内部是通过 Handler 来实现广播的发送的,那么相比于系统广播通过 Binder 实现那肯定是更高效了,同时使用 Handler 来实现,别的应用无法向我们的应用发送该广播,而我们应用内发送的广播也不会离开我们的应用;


问题十二:LocalBroadcastManager 产生的原因

1、BroadcastReceiver 设计的初衷是从全局考虑可以方便应用程序和系统、应用程序之间、应用程序内的通信,所以对单个应用程序而言BroadcastReceiver 是存在安全性问题的 ( 恶意程序脚本不断的去发送你所接收的广播 ) 。为了解决这个问题 LocalBroadcastManager 就应运而生了。

2、LocalBroadcastManager 是 Android Support 包提供了一个工具,用于在同一个应用内的不同组件间发送 Broadcast。LocalBroadcastManager 也称为局部通知管理器,这种通知的好处是安全性高,效率也高,适合局部通信,可以用来代替 Handler 更新 UI。


问题十三:如何让自己的广播只让指定的app接收?

    在发送广播的 app 端,自定义定义权限, 那么想要接收的另外 app 端必须声明权限才能收到。

1、权限, 保护层级是普通正常.

2、用户权限

<permission android:name="broad.ok.receiver" android:protectionLevel="normal"/>

<uses-permission android:name="broad.ok.receiver" />

3、发送广播的时候加上权限字符串

    Intent intent = new Intent();

    intent.putExtra("info", "消息内容");

    intent.setAction("myBroadcast.action.call");

    sendBroadcast(intent, "broad.ok.receiver");

    //sendOrderedBroadcast(intent,"broad.ok.receiver");

4、其他app接收者想好获取广播,必须声明在清单文件权限

<uses-permission android:name="broad.ok.receiver"/>


问题十四:广播的优先级对无序广播生效吗?

优先级对无序也生效。


问题十五:BroadcastReceiver能不能执行耗时操作

1、BroadcastReceiver 一般处于主线程。

2、耗时操作会导致 ANR。

3、BroadcastReceiver 启动时间较短。

4、如果一个进程里面只存在一个 BroadcastReceiver 组件。并且在其中开启子线程执行耗时任务,系统会认为该进程是优先级最低的空进程。很容易将其杀死。


四:ContentProvider知识点回顾 

问题一:Android 为什么要设计 ContentProvider

1、封装。对数据进行封装,提供统一的接口,使用者完全不必关心这些数据是在 DB ,XML 、Preferences 或者网络请求来的。当项目需求要改变数据来源时,使用我们的地方完全不需要修改。

2、提供一种跨进程数据共享的方式。

3、应用程序间的数据共享还有另外的一个重要话题,就是数据更新通知机制了。因为数据是在多个应用程序中共享的,当其中一个应用程序改变了这些共享数据的时候,它有责任通知其它应用程序,让它们知道共享数据被修改了,这样它们就可以作相应的处理。

4、更好的数据访问权限管理。ContentProvider 可以对开发的数据进行权限设置,不同的 URI 可以对应不同的权限,只有符合权限要求的组件才能访问到 ContentProvider 的具体操作。

5、ContentProvider 封装了跨进程共享的逻辑,我们只需要 Uri 即可访问数据。由系统来管理 ContentProvider 的创建、生命周期及访问的线程分配,简化我们在应用间共享数据( 进程间通信 )的方式。我们只管通过 ContentResolver 访问 ContentProvider 所提示的数据接口,而不需要担心它所在进程是启动还是未启动。


问题二:如何访问自定义 ContentProvider?

1、ContentResolver 接口的 notifyChange 函数来通知那些注册了监控特定 URI的ContentObserver 对象,使得它们可以相应地执行一些处理。

2、ContentObserver 可以通过 registerContentObserver 进行注册。

3、通过 ContentProvider 的 Uri 访问开放的数据。

3.1、ContenResolver 对象通过 Context 提供的方法 getContenResolver() 来获得。

3.2、ContenResolver 提供了以下方法来操作:insert delete update query 这些方法分别会调用 ContenProvider 中与之对应的方法并得到返回的结果。


问题三:通过 ContentResolver 获取 ContentProvider 内容的基本步骤有哪些?

1、得到 ContentResolver 类对象:ContentResolver cr = getContentResolver ( )。

2、定义要查询的字段 String 数组。

3、使用 cr.query() ; 返回一个 Cursor 对象。

4、使用 while 循环得到 Cursor 里面的内容。


问题四:为什么要用 ContentProvider ?它和 sql 的实现上有什么差别

1、ContentProvider 屏蔽了数据存储的细节 , 内部实现对用户完全透明 , 用户只需要关心操作数据的 uri 就可以了, ContentProvider 可以实现不同 app之间 共享。

2、Sql 也有增删改查的方法, 但是 sql 只能查询本应用下的数据库。

3、而ContentProvider 还可以去增删改查本地文件. xml 文件的读取等。


问题五:Uri是什么

1、为系统的每一个资源给其一个名字,比方说通话记录。

1.1、每一个 ContentProvider 都拥有一个公共的 URI ,这个 URI 用于表示这个 ContentProvider 所提供的数据。

1.2、Android 所提供的 ContentProvider 都存放在 android.provider 包中。

2、将其分为 A,B,C,D 4个部分:

2.1、A:标准前缀,用来说明一个 Content Provider 控制这些数据,无法改变的;"content://";

2.2、B:URI 的标识,用于唯一标识这个 ContentProvider ,外部调用者可以根据这个标识来找到它。它定义了是哪个 ContentProvider 提供这些数据。对于第三方应用程序,为了保证 URI 标识的唯一性,它必须是一个完整的、小写的类名。这个标识在元素的 authorities 属性中说明:一般是定义该 ContentProvider 的包类的名称;

2.3、C:路径( path ),通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义,记得在使用的时候保持一致就可以了;"content://com.bing.provider.myprovider/tablename"。

2.4、D:如果URI中包含表示需要获取的记录的 ID;则就返回该id对应的数据,如果没有 ID,就表示返回全部; "content://com.bing.provider.myprovider/tablename/#" # 表示数据 id 。


问题六:如何访问 asserts 资源目录下的数据库?

把数据库 db 复制到 /data/data/packagename/databases/ 目录下, 然后直接就能访问了。


问题七:多个进程同时调用一个 ContentProvider 的 query 获取数据,ContentPrvoider 是如何反应的呢?

1、一个 ContentProvider 可以接受来自另外一个进程的数据请求。

2、尽管 ContentResolver 与 ContentProvider 类隐藏了实现细节,但是 ContentProvider 所提供的 query(),insert(),delete(),update() 都是在 ContentProvider 进程的线程池中被调用执行的,而不是进程的主线程中。

3、这个线程池是由 Binder 创建和维护的,其实使用的就是每个应用进程中的 Binder 线程池。


问题八:运行在主线程的 ContentProvider 为什么不会影响主线程的UI操作?

1、ContentProvider 的 onCreate() 是运行在 UI 线程的,而 query() ,insert() ,delete() ,update() 是运行在线程池中的工作线程的

2、所以调用这向个方法并不会阻塞 ContentProvider 所在进程的主线程,但可能会阻塞调用者所在的进程的 UI 线程!

3、所以,调用 ContentProvider 的操作仍然要放在子线程中去做。

4、虽然直接的 CRUD 的操作是在工作线程的,但系统会让你的调用线程等待这个异步的操作完成,你才可以继续线程之前的工作。


问题九:对外提供数据共享,那么如何限制对方的使用呢?

1、android:exported 属性非常重要。这个属性用于指示该服务是否能够被其他应用程序组件调用或跟它交互。

2、如果设置为 true,则能够被调用或交互,否则不能。

3、设置为 false 时,只有同一个应用程序的组件或带有相同用户 ID 的应用程序才能启动或绑定该服务。

4、对于需要开放的组件应设置合理的权限,如果只需要对同一个签名的其它应用开放 ContentProvider ,则可以设置 signature 级别的权限。

5、大家可以参考一下系统自带应用的代码,自定义了 signature 级别的 permission :

<permission android:name="com.android.gallery3d.filtershow.permission.READ"

            android:protectionLevel="signature" />

<permission android:name="com.android.gallery3d.filtershow.permission.WRITE"

            android:protectionLevel="signature" />

<provider

    android:name="com.android.gallery3d.filtershow.provider.SharedImageProvider"

    android:authorities="com.android.gallery3d.filtershow.provider.SharedImageProvider"

    android:grantUriPermissions="true"

    android:readPermission="com.android.gallery3d.filtershow.permission.READ"

    android:writePermission="com.android.gallery3d.filtershow.permission.WRITE" />


问题十:如果我们只需要开放部份的 URI 给其他的应用访问呢?

1、可以参考 Provider 的 URI 权限设置,只允许访问部份 URI ,可以参考原生 ContactsProvider2 的相关代码( 注意 path-permission 这个选项 ):

<provider android:name="ContactsProvider2"

    android:authorities="contacts;com.android.contacts"

    android:label="@string/provider_label"

    android:multiprocess="false"

    android:exported="true"

    android:grantUriPermissions="true"

    android:readPermission="android.permission.READ_CONTACTS"

    android:writePermission="android.permission.WRITE_CONTACTS">

    <path-permission

            android:pathPrefix="/search_suggest_query"

            android:readPermission="android.permission.GLOBAL_SEARCH" />

    <path-permission

            android:pathPrefix="/search_suggest_shortcut"

            android:readPermission="android.permission.GLOBAL_SEARCH" />

    <path-permission

            android:pathPattern="/contacts/.*/photo"

            android:readPermission="android.permission.GLOBAL_SEARCH" />

    <grant-uri-permission android:pathPattern=".*" />

</provider>


问题十一:ContentProvider 接口方法运行在哪个线程中呢?

1、ContentProvider 可以在 AndroidManifest.xml 中配置一个叫做 android:multiprocess 的属性,默认值是 false ,表示 ContentProvider 是单例的

2、无论哪个客户端应用的访问都将是同一个 ContentProvider 对象,如果设为 true ,系统会为每一个访问该 ContentProvider 的进程创建一个实例。

3、我们在UI线程调用getContentResolver().query查询数据,而当数据量很大时(或者需要进行较长时间的计算)会不会阻塞UI线程呢?

1、ContentProvider 和调用者在同一个进程,ContentProvider 的方法( query/insert/update/delete 等 )和调用者在同一线程中;

2、ContentProvider 和调用者在不同的进程,ContentProvider 的方法会运行在它自身所在进程的一个 Binder 线程中。

    但是,注意这两种方式在 ContentProvider 的方法没有执行完成前都会 blocked 调用者。


问题十二:ContentProvider 是如何在不同应用程序之间传输数据的?

1、一个应用进程有 16 个 Binder 线程去和远程服务进行交互,而每个线程可占用的缓存空间是 128KB 这样,超过会报异常。

2、ContentResolver 虽然是通过 Binder 进程间通信机制打通了应用程序之间共享数据的通道,但 ContentProvider 组件在不同应用程序之间传输数据是基于匿名共享内存机制来实现的。

3、图例如下:


五:Context知识点回顾

问题一:为什么不能直接Activity mActivity =new Activity()

    作为Android开发者,不知道你有没有思考过这个问题,Activity可以new吗?Android的应用程序开发采用JAVA语言,Activity本质上也是一个对象,那上面的写法有什么问题呢?估计很多人说不清道不明。Android程序不像Java程序一样,随便创建一个类,写个main()方法就能运行,Android应用模型是基于组件的应用设计模式,组件的运行要有一个完整的Android工程环境,在这个环境下,Activity、Service等系统组件才能够正常工作,而这些组件并不能采用普通的Java对象创建方式,new一下就能创建实例了,而是要有它们各自的上下文环境,也就是我们这里讨论的Context。可以这样讲,Context是维持Android程序中各组件能够正常工作的一个核心功能类。


问题二:Context到底是什么?

    Context的中文翻译为:语境; 上下文; 背景; 环境,在开发中我们经常说称之为“上下文”,那么这个“上下文”到底是指什么意思呢?在语文中,我们可以理解为语境,在程序中,我们可以理解为当前对象在程序中所处的一个环境,一个与系统交互的过程。比如微信聊天,此时的“环境”是指聊天的界面以及相关的数据请求与传输,Context在加载资源、启动Activity、获取系统服务、创建View等操作都要参与。

    那Context到底是什么呢?一个Activity就是一个Context,一个Service也是一个Context。Android程序员把“场景”抽象为Context类,他们认为用户和操作系统的每一次交互都是一个场景,比如打电话、发短信,这些都是一个有界面的场景,还有一些没有界面的场景,比如后台运行的服务(Service)。一个应用程序可以认为是一个工作环境,用户在这个环境中会切换到不同的场景,这就像一个前台秘书,她可能需要接待客人,可能要打印文件,还可能要接听客户电话,而这些就称之为不同的场景,前台秘书可以称之为一个应用程序。

    源码注解:Context提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被Android系统所提供。它允许获取以应用为特征的资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。就是说,它描述一个应用程序环境的信息(即上下文);是一个抽象类,Android提供了该抽象类的具体实现类;通过它我们可以获取应用程序的资源和类(包括应用级别操作,如启动Activity,发广播,接受Intent等)。


问题三:Context及其继承类的功能是如何划分的?

类图解析:

   Context类本身是一个纯abstract类,它有两个具体的实现子类:ContextImpl和ContextWrapper。其中ContextWrapper类,如其名所言,这只是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用,同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的Context对象。

    ContextThemeWrapper类,如其名所言,其内部包含了与主题(Theme)相关的接口,这里所说的主题就是指在AndroidManifest.xml中通过android:theme为Application元素或者Activity元素指定的主题。当然,只有Activity才需要主题,Service是不需要主题的,因为Service是没有界面的后台场景,所以Service直接继承于ContextWrapper,Application同理。而ContextImpl类则真正实现了Context中的所以函数,应用程序中所调用的各种Context类的方法,其实现均来自于该类。

   一句话总结:Context的两个子类分工明确,其中   ContextImpl是Context的具体实现类,ContextWrapper是Context的包装类。Activity,Application,Service虽都继承自ContextWrapper(Activity继承自ContextWrapper的子类ContextThemeWrapper),但它们初始化的过程中都会创建ContextImpl对象,由ContextImpl实现Context中的方法。


问题四:一个应用程序有几个Context

    在应用程序中Context的具体实现子类就是:Activity,Service,Application。那么Context数量=Activity数量+Service数量+1。当然如果你足够细心,可能会有疑问:我们常说四大组件,这里怎么只有Activity,Service持有Context,那Broadcast Receiver,Content Provider呢?Broadcast Receiver,Content Provider并不是Context的子类,他们所持有的Context都是其他地方传过去的,所以并不计入Context总数。


问题五:Context的使用场景有哪些?

描述:弹出Toast、启动Activity、启动Service、发送广播、操作数据库等等都需要用到Context。

代码示例:

TextView tv = new TextView(getContext());

ListAdapter adapter = new SimpleCursorAdapter(getApplicationContext(), ...);

AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);getApplicationContext().getSharedPreferences(name, mode);

getApplicationContext().getContentResolver().query(uri, ...);

getContext().getResources().getDisplayMetrics().widthPixels * 5 / 8;

getContext().startActivity(intent);

getContext().startService(intent);

getContext().sendBroadcast(intent);


问题六:Context作用域是什么?

   由于Context的具体实例是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

作用域图解:


问题七:如何获取Context?

    通常我们想要获取Context对象,主要有以下四种方法:

1、View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象。

2、Activity.getApplicationContext,获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。

3、ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。

4、Activity.this 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。


问题八:getApplication()和getApplicationContext()的区别?

    通过代码,打印得出两者的内存地址都是相同的,看来它们是同一个对象。其实这个结果也很好理解,因为前面已经说过了,Application本身就是一个Context,所以这里获取getApplicationContext()得到的结果就是Application本身的实例。

    那么问题来了,既然这两个方法得到的结果都是相同的,那么Android为什么要提供两个功能重复的方法呢?实际上这两个方法在作用域上有比较大的区别。getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法了。


问题九:Context可能引起的内存泄露场景?

错误的单例模式:

    instance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,假如Activity A去getInstance获得instance对象,传入this,常驻内存的Singleton保存了你传入的Activity A对象,并一直持有,即使Activity被销毁掉,但因为它的引用还存在于一个Singleton中,就不可能被GC掉,这样就导致了内存泄漏。

View持有Activity引用:

    有一个静态的Drawable对象当ImageView设置这个Drawable时,ImageView保存了mDrawable的引用,而ImageView传入的this是MainActivity的mContext,因为被static修饰的mDrawable是常驻内存的,MainActivity是它的间接引用,MainActivity被销毁时,也不能被GC掉,所以造成内存泄漏。

问题十:如何正确的使用Context

    一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,所以我们总结出使用Context的正确姿势:

1、当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。

2、不要让生命周期长于Activity的对象持有到Activity的引用。

3、尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。


六:扩展阅读

1、//www.greatytc.com/p/471f3581495b(17 个必须掌握的 BroadcastReceiver 知识点「建议收藏」)

2、//www.greatytc.com/p/74b7d8a36798(23 个重难点突破,带你吃透Service知识点「长达 1W+ 字」)

3、//www.greatytc.com/p/7a6b786ba728(Android 这 13 道 ContentProvider 面试题,你都会了吗?)

4、//www.greatytc.com/p/86c0a4afd28e(Activity 的 36 大难点,你会几个?「建议收藏」)

5、https://juejin.cn/post/6844903474606374925(3分钟看懂Activity启动流程)

6、https://blog.csdn.net/qq_23547831/article/details/51685310(应用内跳转Scheme协议)

7、//www.greatytc.com/p/33729a7a66da(Activity的管理机制)

8、https://blog.csdn.net/cjj2100/article/details/39180061(Android面试,与Service交互方式)

9、https://blog.csdn.net/luoshengyang/article/details/6651971(Android系统匿名共享内存Ashmem)

10、//www.greatytc.com/p/479b78235361(深入分析Android中Activity的onStop和onDestroy()回调延时及延时10s的问题)

11、https://blog.csdn.net/lmj623565791/article/details/37936275(Android屏幕旋转处理AsyncTask和 ProgressDialog的最佳方案)

12、https://blog.csdn.net/mlq8087/article/details/82888201(【细说Activity启动流程】:当你点击一个应用图标后,究竟发生了什么)

13、//www.greatytc.com/p/efecdccbd401?utm_source=oschina-app(AccessibilityService辅助功能基础使用(附微信抢红包教程))

14、//www.greatytc.com/p/b3bd935f8380(AccessibilityService帮你实现Android全局悬浮窗)

15、//www.greatytc.com/p/77f754446009(探究Android中的ActivityLifecycleCallbacks)

16、//www.greatytc.com/p/c5efb41f2ef0(Android各种Context的前世今生)

17、//www.greatytc.com/p/b68de4c95b05(Context都没弄明白,还怎么做Android开发)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,737评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,103评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,710评论 0 326
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,909评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,794评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,557评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,939评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,572评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,852评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,871评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,692评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,490评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,939评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,114评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,409评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,971评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,176评论 2 339

推荐阅读更多精彩内容