01.Android之基础组件问题

目录介绍

  • 1.0.0.1 说下Activity的生命周期?屏幕旋转时生命周期?异常条件会调用什么方法?
  • 1.0.0.2 后台的Activity被系统回收怎么办?说一下onSaveInstanceState()和onRestoreInstanceState()方法特点?
  • 1.0.0.3 如何避免配置改变时Activity重建?优先级低的Activity在内存不足被回收后怎样做可以恢复到销毁前状态?
  • 1.0.0.4 app切换到后台,当前activity会走onDestory方法吗?一般在onstop方法里做什么?什么情况会导致app会被杀死?
  • 1.0.0.5 Activity的启动过程是有几种方式?从桌面launcher上点击应用图标会干啥,调用startActivty()又会做什么?
  • 1.0.0.6 说下Activity的四种启动模式?singleTop和singleTask的区别以及应用场景?任务栈的作用是什么?
  • 1.0.0.7 两个Activity之间怎么传递数据?intent和bundle有什么区别?为什么有了intent还要设计bundle?
  • 1.0.0.8 知道哪些Activity启动模式的标记位?flag是干什么用的,什么时候用到?
  • 1.0.1.0 同一程序不同的Activity是否可以放在不同的Task任务栈中?
  • 1.0.1.1 介绍一下Service,启动Service有几种方式,生命周期是怎样的?说一下onStartCommand()的作用?service如何杀不死?
  • 1.0.1.2 一个Activty先start一个Service后,再bind时会回调什么方法?此时如何做才能回调Service的destory()方法?
  • 1.0.1.3 bindService是一个异步的过程吗?绑定service大概需要经历那些过程?
  • 1.0.1.4 是否能在Service进行耗时操作?如果非要可以怎么做,如何避免service线程卡顿?service里面可以弹土司吗?
  • 1.0.1.5 Activity如何与Service通信?Service的生命周期与启动方法有什么区别?
  • 1.0.2.0 是否了解ActivityManagerService,它发挥什么作用,说一下AMS启动流程?
  • 1.0.2.1 Android中哪些事件需要用到广播?广播的生命周期是怎样的?
  • 1.0.2.3 广播有几种形式?他们分别有什么特点,如何使用广播?广播是怎么实现不同进程之间通信的?
  • 1.0.2.8 Fragment与Activity之间是如何传值的?Fragment与Fragment之间是如何传值的?
  • 1.0.2.9 Activity创建Fragment的方式是什么?FragmentPageAdapter和FragmentPageStateAdapter的区别?
  • 1.0.3.0 fragment 特点?说一下Fragment的生命周期?如何解决getActivity为null的异常问题?
  • 1.0.3.1 在fragment中为什么有时getActivity()会为null?Fragment试图为什么有的时候会重叠,怎么产生的,又如何解决?
  • 1.0.3.2 为什么fragment传递数据不用构造方法传递?FragmentManager , add 和 replace 有什么区别?
  • 1.0.3.9 Activitiy启动流程中performLaunchActivity的作用?
  • 1.0.4.0 Intent是什么?Intent可以传递哪些数据?传递对象的时候为什么要实例化?
  • 1.0.4.1 mipmap系列中xxxhdpi、xxhdpi、xhdpi、hdpi、mdpi和ldpi存在怎样的关系?
  • 1.0.4.2 res目录和assets目录的区别?R文件是如何生成的,主要有什么作用?
  • 1.0.4.3 Context是什么?Context有哪些类型,分别作用是什么?Context下有哪些子类?哪些场景只能用activity上下文?
  • 1.0.4.4 ActivityThread的main()的流程大概是怎么样的?
  • 1.0.5.0 序列化的方式有哪些?效率对比有何优势?如何做性能上分析的?
  • 1.0.5.9 界面的刷新为什么需16.6ms?画面的显示需要哪些步骤?界面保持不变时还会16.6ms刷新一次屏幕吗?
  • 1.0.6.0 Android中日志级别有哪几种?开发中需要注意什么问题,打印日志源码分析原理是什么?

好消息

  • 博客笔记大汇总【15年10月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计500篇[近100万字],将会陆续发表到网上,转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!所有的笔记将会更新到GitHub上,同时保持更新,欢迎同行提出或者push不同的看法或者笔记!

1.0.0.1 说下Activity的生命周期?屏幕旋转时生命周期?异常条件会调用什么方法?

  • 在Activity的生命周期涉及到七大方法,分别是:
    • onCreate()表示Activity 正在创建,常做初始化工作,如setContentView界面资源、初始化数据
    • onStart()表示Activity 正在启动,这时Activity 可见但不在前台,无法和用户交互
    • onResume()表示Activity 获得焦点,此时Activity 可见且在前台并开始活动
    • onPause()表示Activity 正在停止,可做 数据存储、停止动画等操作
    • onStop()表示activity 即将停止,可做稍微重量级回收工作,如取消网络连接、注销广播接收器等
    • onDestroy()表示Activity 即将销毁,常做回收工作、资源释放
    • onRestart()表示当Activity由后台切换到前台,由不可见到可见时会调用,表示Activity 重新启动
  • 屏幕旋转时生命周期
    • 屏幕旋转时候,如果不做任何处理,activity会经过销毁到重建的过程。一般这种效果都不是想要的。比如视频播放器就经常会涉及屏幕旋转场景。技术博客大总结
    • 第一种情况:当前的Activity不销毁【设置Activity的android:configChanges="orientation|keyboardHidden|screenSize"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法】
      <activity
          android:name=".activity.VideoDetailActivity"
          android:configChanges="orientation|keyboardHidden|screenSize"
          android:screenOrientation="portrait"/>
      
      • 执行该方法
      //重写旋转时方法,不销毁activity
      @Override
      public void onConfigurationChanged(Configuration newConfig) {
          super.onConfigurationChanged(newConfig);
      }
      
    • 第二种情况:销毁当前的Activity后重建,这种也尽量避免。【不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,默认首先销毁当前activity,然后重新加载】
  • 异常条件会调用什么方法
    • 当非人为终止Activity时,比如系统配置发生改变时导致Activity被杀死并重新创建、资源内存不足导致低优先级的Activity被杀死,会调用 onSavaInstanceState() 来保存状态。该方法调用在onStop之前,但和onPause没有时序关系。
    • 有人会问,onSaveInstanceState()与onPause()的区别,onSaveInstanceState()适用于对临时性状态的保存,而onPause()适用于对数据的持久化保存。
    • 当异常崩溃后App又重启了,这个时候会走onRestoreInstanceState()方法,可以在该方法中取出onSaveInstanceState()保存的状态数据。
  • 什么时候会引起异常生命周期
    • 资源相关的系统配置发生改变或者资源不足:例如屏幕旋转,当前Activity会销毁,并且在onStop之前回调onSaveInstanceState保存数据,在重新创建Activity的时候在onStart之后回调onRestoreInstanceState。其中Bundle数据会传到onCreate(不一定有数据)和onRestoreInstanceState(一定有数据)。技术博客大总结
    • 防止屏幕旋转的时候重建,在清单文件中添加配置:android:configChanges="orientation"

1.0.0.2 后台的Activity被系统回收怎么办?说一下onSaveInstanceState()和onRestoreInstanceState()方法特点?

  • 后台的Activity被系统回收怎么办?
    • Activity中提供了一个 onSaveInstanceState()回调方法,这个方法会保证一定在活动被回收之前调用,可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。onSaveInstanceState()方法会携带一个Bundle类型的参数,Bundle提供了一系列的方法用于保存数据,比如可以使用putString()方法保存字符串,使用putInt()方法保存整型数据。每个保存方法需要传入两个参数,第一个参数是键,用于后面从 Bundle中取值,第二个参数是真正要保存的内容。技术博客大总结
  • 说一下onSaveInstanceState()和onRestoreInstanceState()方法特点?
    • Activity的 onSaveInstanceState()和onRestoreInstanceState()并不是生命周期方法,它们不同于onCreate()、onPause()等生命周期方法,它们并不一定会被触发。
      //保存数据
      @Override
      protected void onSaveInstanceState(Bundle outBundle) {
          super.onSaveInstanceState(outBundle);
          outBundle.putBoolean("Change", mChange);
      }
      
      //取出数据
      @Override 
      protected void onRestoreInstanceState(Bundle savedInstanceState) {
          super.onRestoreInstanceState(savedInstanceState);
          mChange = savedInstanceState.getBoolean("Change");
      }
      
      //或者在onCreate方法取数据也可以
      //onCreate()方法其实也有一个Bundle类型的参数。这个参数在一般情况下都是null,
      //但是当活动被系统回收之前有通过 onSaveInstanceState()方法来保存数据的话,这个参就会带有之前所保存的全部数据
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          if (savedInstanceState != null) {
              String data = savedInstanceState.getString("data");
          }
      }
      
  • 什么时候会触发走这两个方法?
    • 当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity,onSaveInstanceState() 会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。除非该activity是被用户主动销毁的,通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。
  • onSaveInstanceState()被执行的场景有哪些?
    • 系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activityA是否会被销毁,因此系统都会调用onSaveInstanceState(),让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则当用户按下HOME键时
      • 长按HOME键,选择运行其他的程序时
      • 锁屏时
      • 从activity A中启动一个新的activity时
      • 屏幕方向切换时

1.0.0.3 如何避免配置改变时Activity重建?优先级低的Activity在内存不足被回收后怎样做可以恢复到销毁前状态?

  • 如何避免配置改变时Activity重建
    • 为了避免由于配置改变导致Activity重建,可在AndroidManifest.xml中对应的Activity中设置android:configChanges="orientation|screenSize"。此时再次旋转屏幕时,该Activity不会被系统杀死和重建,只会调用onConfigurationChanged。因此,当配置程序需要响应配置改变,指定configChanges属性,重写onConfigurationChanged方法即可。
    • 使用场景,比如视频播放器横竖屏切换播放视频,就需要设置这种属性。具体可以看我封装的视频播放器库,地址:https://github.com/yangchong211/YCVideoPlayer
  • 优先级低的Activity在内存不足被回收后怎样做可以恢复到销毁前状态
    • 优先级低的Activity在内存不足被回收后重新打开会引发Activity重建。Activity被重新创建时会调用onRestoreInstanceState(该方法在onStart之后),并将onSavaInstanceState保存的Bundle对象作为参数传到onRestoreInstanceState与onCreate方法。因此可通过onRestoreInstanceState(Bundle savedInstanceState)和onCreate((Bundle savedInstanceState)来判断Activity是否被重建,并取出数据进行恢复。但需要注意的是,在onCreate取出数据时一定要先判断savedInstanceState是否为空。
  • 如何判断activity的优先级?技术博客大总结
    • 除了在栈顶的activity,其他的activity都有可能在内存不足的时候被系统回收,一个activity越处于栈底,被回收的可能性越大.如果有多个后台进程,在选择杀死的目标时,采用最近最少使用算法(LRU)。

1.0.0.4 app切换到后台,当前activity会走onDestory方法吗?一般在onstop方法里做什么?什么情况会导致app会被杀死,这时候会走onDestory吗?

  • app切换到后台,当前activity会走onDestory方法吗?
    • 不会走onDestory方法,会先后走onPause和onStop方法。
  • 一般在onstop方法里做什么?
    • 比如。写轮播图的时候,会在onstop方法里写上暂停轮播图无限轮播,在onStart方法中会开启自动无限轮播。
    • 再比如,写视频播放器的时候,当app切换到后台,则需要停止视频播放,也是可以在onstop中处理的。关于视频播放器,可以看我这个开源项目:视频播放器
  • 什么情况会导致app会被杀死,这时候会走onDestory吗?
    • 系统资源不足,会导致app意外被杀死。应用只有在进程存活的情况下才会按照正常的生命周期进行执行,如果进程突然被kill掉,相当于System.exit(0); 进程被杀死,根本不会走(activity,fragment)生命周期。只有在进程不被kill掉,正常情况下才会执行ondestory()方法。
  • activity被回收如何恢复
    • 当系统内存不足时, activity会被回收,我们其实可以覆写onSaveInstanceState()方法。onSaveInstanceState()方法接受一个Bundle类型的参数, 开发者可以将状态数据存储到这个Bundle对象中,这样即使activity被系统摧毁,当用户重新启动这个activity而调用它的onCreate()方法时,上述的Bundle对象会作为实参传递给onCreate()方法,开发者可以从Bundle对象中取出保存的数据, 然后利用这些数据将activity恢复到被摧毁之前的状态。

1.0.0.5 Activity的启动过程是有几种方式?从桌面launcher上点击应用图标会干啥,调用startActivty()又会做什么?

  • Activity的启动过程是怎样的,有几种方式?
    • 注意是启动过程,不是生命周期。技术博客大总结
    • app启动的过程有两种情况,第一种是从桌面launcher上点击相应的应用图标,第二种是在activity中通过调用startActivity来启动一个新的activity。
  • 从桌面launcher上点击应用图标会干啥,调用startActivty()又会做什么?
    • 创建一个新的项目,默认的根activity都是MainActivity,而所有的activity都是保存在堆栈中的,启动一个新的activity就会放在上一个activity上面,而我们从桌面点击应用图标的时候,由于launcher本身也是一个应用,当我们点击图标的时候,系统就会调用startActivitySately(),一般情况下,我们所启动的activity的相关信息都会保存在intent中,比如action,category等等。
    • 我们在安装这个应用的时候,系统也会启动一个PackaManagerService的管理服务,这个管理服务会对AndroidManifest.xml文件进行解析,从而得到应用程序中的相关信息,比如service,activity,Broadcast等等,然后获得相关组件的信息。
    • 当我们点击应用图标的时候,就会调用startActivitySately()方法,而这个方法内部则是调用startActivty(),而startActivity()方法最终还是会调用startActivityForResult()这个方法。而在startActivityForResult()这个方法。因为startActivityForResult()方法是有返回结果的,所以系统就直接给一个-1,就表示不需要结果返回了。
    • 而startActivityForResult()这个方法实际是通过Instrumentation类中的execStartActivity()方法来启动activity,Instrumentation这个类主要作用就是监控程序和系统之间的交互。而在这个execStartActivity()方法中会获取ActivityManagerService的代理对象,通过这个代理对象进行启动activity。启动会就会调用一个checkStartActivityResult()方法,如果说没有在配置清单中配置有这个组件,就会在这个方法中抛出异常了。
    • 当然最后是调用的是Application.scheduleLaunchActivity()进行启动activity,而这个方法中通过获取得到一个ActivityClientRecord对象,而这个ActivityClientRecord通过handler来进行消息的发送,系统内部会将每一个activity组件使用ActivityClientRecord对象来进行描述,而ActivityClientRecord对象中保存有一个LoaderApk对象,通过这个对象调用handleLaunchActivity来启动activity组件,而页面的生命周期方法也就是在这个方法中进行调用。

1.0.0.6 说下Activity的四种启动模式?singleTop和singleTask的区别以及应用场景?任务栈的作用是什么?

  • Activity的四种启动模式
    • standard标准模式:每次启动一个Activity就会创建一个新的实例
    • singleTop栈顶复用模式:如果新Activity已经位于任务栈的栈顶,就不会重新创建,并回调 onNewIntent(intent) 方法
    • singleTask栈内复用模式:只要该Activity在一个任务栈中存在,都不会重新创建,并回调 onNewIntent(intent) 方法。如果不存在,系统会先寻找是否存在需要的栈,如果不存在该栈,就创建一个任务栈,并把该Activity放进去;如果存在,就会创建到已经存在的栈中
    • singleInstance单实例模式:具有此模式的Activity只能单独位于一个任务栈中,且此任务栈中只有唯一一个实例
  • singleTop和singleTask的区别以及应用场景
    • singleTop:同个Activity实例在栈中可以有多个,即可能重复创建;该模式的Activity会默认进入启动它所属的任务栈,即不会引起任务栈的变更;为防止快速点击时多次startActivity,可以将目标Activity设置为singleTop
    • singleTask:同个Activity实例在栈中只有一个,即不存在重复创建;可通过android:taskAffinity设定该Activity需要的任务栈,即可能会引起任务栈的变更;常用于主页和登陆页
  • singleTop或singleTask的Activity在以下情况会回调onNewIntent()
    • singleTop:如果新Activity已经位于任务栈的栈顶,就不会重新创建,并回调 onNewIntent(intent) 方法
    • singleTask:只要该Activity在一个任务栈中存在,都不会重新创建,并回调 onNewIntent(intent) 方法
  • 任务栈的作用是什么?技术博客大总结
    • 它是存放 Activity 的引用的,Activity不同的启动模式,对应不同的任务栈的存放;可通过 getTaskId()来获取任务栈的 ID,如果前面的任务栈已经清空,新开的任务栈ID+1,是自动增长的;首先来看下Task的定义,Google是这样定义Task的:Task实际上是一个Activity栈,通常用户感受的一个Application就是一个Task。从这个定义来看,Task跟Service或者其他Components是没有任何联系的,它只是针对Activity而言的。

1.0.0.7 两个Activity之间怎么传递数据?intent和bundle有什么区别?为什么有了intent还要设计bundle?

  • 两个Activity之间怎么传递数据?
    • 基本数据类型可以通过Intent传递数据
    • 把数据封装至intent对象中
      Intent intent = new Intent(content, MeActivity.class);
      intent.putExtra("goods_id", goods_id);
      content.startActivity(intent);
      
    • 把数据封装至bundle对象中技术博客大总结
    • 把bundle对象封装至intent对象中
      Bundle bundle = new Bundle();
      bundle.putString("malename", "李志");
      intent.putExtras(bundle);
      startActivity(intent); 
      
  • intent和bundle有什么区别?
    • Intent传递数据和Bundle传递数据是一回事,Intent传递时内部还是调用了Bundle。
      public @NonNull Intent putExtra(String name, String value) {
          if (mExtras == null) {
              mExtras = new Bundle();
          }
          mExtras.putString(name, value);
          return this;
      }
      
  • 为什么有了intent还要设计bundle?
    • 两者比较
      • Bundle只是一个信息的载体,内部其实就是维护了一个Map<String,Object>。
      • Intent负责Activity之间的交互,内部是持有一个Bundle的。
    • bundle使用场景
      • Fragment之间传递数据;比如,某个Fragment中点击按钮弹出一个DialogFragment。最便捷的方式就是通过Fragment.setArguments(args)传递参数。
      public static void showFragmentDialog(String title, String content, boolean is_open, AppCompatActivity activity) {
          ServiceDialogFragment mainDialogFragment = new ServiceDialogFragment();
          Bundle bundle = new Bundle();
          bundle.putString("title", title);
          bundle.putString("content", content);
          bundle.putBoolean("is_open",is_open);
          mainDialogFragment.setArguments(bundle);
          mainDialogFragment.show(activity.getSupportFragmentManager());
      }
      
      @Override
      public void onCreate(Bundle savedInstanceState) {
          setLocal(Local.CENTER);
          super.onCreate(savedInstanceState);
          Bundle bundle = getArguments();
          if (bundle != null) {
              title = bundle.getString("title");
              content = bundle.getString("content");
              is_open = bundle.getBoolean("is_open");
          }
      }
      

1.0.0.8 知道哪些Activity启动模式的标记位?flag是干什么用的,什么时候用到?

  • 常见的标记为:
    • FLAG_ACTIVITY_SINGLE_TOP:对应singleTop启动模式
    • FLAG_ACTIVITY_NEW_TASK :对应singleTask模式

1.0.1.0 同一程序不同的Activity是否可以放在不同的Task任务栈中?

  • 同一程序不同的Activity是否可以放在不同的Task任务栈中?
    • 可以的。比如:启动模式里有个Singleinstance,可以运行在另外的单独的任务栈里面。用这个模式启动的activity,在内存中只有一份,这样就不会重复的开启。
    • 也可以在激活一个新的activity时候,给intent设置flag,Intent的flag添加FLAG_ACTIVITY_NEW_TASK,这个被激活的activity就会在新的task栈里面

1.0.1.1 介绍一下Service,启动Service有几种方式,生命周期是怎样的?说一下onStartCommand()的作用?service如何杀不死?

  • Service分为两种
    • 本地服务,属于同一个应用程序,通过startService来启动或者通过bindService来绑定并且获取代理对象。如果只是想开个服务在后台运行的话,直接startService即可,如果需要相互之间进行传值或者操作的话,就应该通过bindService。
    • 远程服务(不同应用程序之间),通过bindService来绑定并且获取代理对象。
  • 对应的生命周期如下:
    • context.startService() ->onCreate()- >onStartCommand()->Service running--调用context.stopService() ->onDestroy()
    • context.bindService()->onCreate()->onBind()->Service running--调用>onUnbind() -> onDestroy()
  • 注意
    • Service默认是运行在main线程的,因此Service中如果需要执行耗时操作(大文件的操作,数据库的拷贝,网络请求,文件下载等)的话应该在子线程中完成。
  • Service生命周期解释技术博客大总结
    • onCreate():服务第一次被创建时调用
    • onStartComand():服务启动时调用
    • onBind():服务被绑定时调用
    • onUnBind():服务被解绑时调用
    • onDestroy():服务停止时调用
  • 说一下onStartCommand()的作用?
  • service如何杀不死?
    • 1.onStartCommand方法,返回START_STICKY(粘性)当service因内存不足被kill,当内存又有的时候,service又被重新创建
    • 2.设置优先级,在服务里的ondestory里发送广播 在广播里再次开启这个服务,双进程守护

1.0.1.2 一个Activty先start一个Service后,再bind时会回调什么方法?此时如何做才能回调Service的destory()方法?

  • 关于service中onDestroy()什么时候会被执行?
    • 当调用了startService()方法后,又去调用stopService()方法,这时服务中的onDestroy()方法就会执行,表示服务已经销毁了。
    • 类似地,当调用了 bindService()方法后,又去调用unbindService()方法,onDestroy()方法也会执行,这两种情况都很好理解。
  • 一个Activty先start一个Service后,再bind时会回调什么方法?

  • 先start后bind操作service,此时如何做才能回调Service的destory()方法?
    • 完全有可能对一个服务既调用了startService()方法,又调用了bindService()方法的,这种情况下该如何才能让服务销毁掉呢?根据Android系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。
    • 这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行这样就把服务的生命周期完整地走了一遍。技术博客大总结

1.0.1.3 bindService是一个异步的过程吗?绑定service大概需要经历那些过程?

1.0.1.4 是否能在Service进行耗时操作?如果非要可以怎么做,如何避免service线程卡顿?service里面可以弹土司吗?

  • 是否能在Service进行耗时操作?
    • 默认情况,如果没有显示的指定service所运行的进程,Service和Activity是运行在当前app所在进程的mainThread(UI主线程)里面。
    • service里面不能执行耗时的操作(网络请求,拷贝数据库,大文件),在Service里执行耗时操作,有可能出现主线程被阻塞(ANR)的情况。
  • 如果非要可以怎么做,如何避免service线程卡顿?
    • 需要在子线程中执行 new Thread(){}.start();
  • service里面可以弹土司吗?
    • 可以,但是有条件。一般很少这样做……技术博客大总结
    • 条件是,service里面弹toast需要添加到主线程里执行。
      @Override  
      public void onCreate(){  
          handler = new Handler(Looper.getMainLooper());                          
          System.out.println("service started");  
          handler.post(new Runnable() {    
              @Override    
              public void run() {    
                  Toast.makeText(getApplicationContext(), "Test",Toast.LENGTH_SHORT).show();
              }
          });
      }
      

1.0.1.5 Activity如何与Service通信?Service的生命周期与启动方法有什么区别?

  • Activity如何与Service通信?
    • 方法一:
      • 添加一个继承Binder的内部类,并添加相应的逻辑方法。重写Service的onBind方法,返回我们刚刚定义的那个内部类实例。Activity中创建一个ServiceConnection的匿名内部类,并且重写里面的onServiceConnected方法和onServiceDisconnected方法,这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用,在onServiceConnected方法中,我们可以得到一个刚才那个service的binder对象,通过对这个binder对象进行向下转型,得到我们那个自定义的Binder实例,有了这个实例,做可以调用这个实例里面的具体方法进行需要的操作了
    • 方法二
      • 通过BroadCast(广播)的形式,当我们的进度发生变化的时候我们发送一条广播,然后在Activity的注册广播接收器,接收到广播之后更新视图

1.0.2.0 是否了解ActivityManagerService,它发挥什么作用,说一下AMS启动流程?

  • ActivityManagerService是Android中最核心的服务,主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块类似。
  • https://blog.csdn.net/dutedehuai/article/details/53495185

1.0.2.1 Android中哪些事件需要用到广播?广播的生命周期是怎样的?

  • Android中哪些事件需要用到广播?
    • Android中:系统在运行过程中,会产生会多事件,那么某些事件产生时,比如:电量改变、收发短信、拨打电话、屏幕解锁、开机,系统会发送广播,只要应用程序接收到这条广播,就知道系统发生了相应的事件,从而执行相应的代码。使用广播接收者,就可以收听广播
  • 广播的生命周期是怎样的?
    • a.广播接收者的生命周期非常短暂的,在接收到广播的时候创建,onReceive()方法结束之后销毁;
    • b.广播接收者中不要做一些耗时的工作,否则会弹出 Application No Response错误对话框;
    • c.最好也不要在广播接收者中创建子线程做耗时的工作,因为广播接收者被销毁后进程就成为了空进程,很容易被系统杀掉;
    • d.耗时的较长的工作最好放在服务中完成;

1.0.2.3 广播有几种形式?他们分别有什么特点,如何使用广播?广播是怎么实现不同进程之间通信的?

  • 广播有几种形式
    • 普通广播:一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们接收的先后是随机的。
    • 有序广播:一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递,所以此时的广播接收器是有先后顺序的,且优先级(priority)高的广播接收器会先收到广播消息。有序广播可以被接收器截断使得后面的接收器无法收到它。
    • 本地广播:发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收本应用程序发出的广播。
    • 粘性广播:这种广播会一直滞留,当有匹配该广播的接收器被注册后,该接收器就会收到此条广播。
  • 广播的两种注册形式技术博客大总结
    • 广播的注册有两种方法:一种在活动里通过代码动态注册,另一种在配置文件里静态注册。两种方式的相同点是都完成了对接收器以及它能接收的广播值这两个值的定义;不同点是动态注册的接收器必须要在程序启动之后才能接收到广播,而静态注册的接收器即便程序未启动也能接收到广播,比如想接收到手机开机完成后系统发出的广播就只能用静态注册了。
  • 动态注册
    • 需要使用广播接收者时,执行注册的代码,不需要时,执行解除注册的代码。安卓中有一些广播接收者,必须使用代码注册,清单文件注册是无效的。
      public class MainActivity extends Activity {
          private IntentFilter intentFilter;
          private NetworkChangeReceiver networkChangeReceiver;
          @Override
          protected void onCreate(Bundle savedInstanceState) {
                 super.onCreate(savedInstanceState);
                 setContentView(R.layout.activity_main);
                 intentFilter = new IntentFilter();
                 intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
                 networkChangeReceiver = new NetworkChangeReceiver();
                 registerReceiver(networkChangeReceiver, intentFilter);
          }
          @Override
          protected void onDestroy() {
                 super.onDestroy();
                 unregisterReceiver(networkChangeReceiver);
          }
          class NetworkChangeReceiver extends BroadcastReceiver {
                  @Override
                  public void onReceive(Context context, Intent intent) {
                       Toast.makeText(context, "network changes",Toast.LENGTH_SHORT).show();
                  }
          }
      }
      
  • 静态注册
    • 可以使用清单文件注册。广播一旦发出,系统就会去所有清单文件中寻找,哪个广播接收者的action和广播的action是匹配的,如果找到了,就把该广播接收者的进程启动起来。

1.0.2.8 Fragment与Activity之间是如何传值的?Fragment与Fragment之间是如何传值的?

  • Fragment与Activity之间是如何传值的?
    • 1.Activity向Fragment传值:
      • 步骤:
      • 要传的值,放到bundle对象里;
      • 在Activity中创建该Fragment的对象fragment,通过调用
      • fragment.setArguments()传递到fragment中;
      • 在该Fragment中通过调用getArguments()得到bundle对象,就能得到里面的值。
    • 2.Fragment向Activity传值:
      • 第一种:
        • 在Activity中调用getFragmentManager()得到fragmentManager,,调用findFragmentByTag(tag)或者通过findFragmentById(id)
        • FragmentManager fragmentManager = getFragmentManager();
        • Fragment fragment = fragmentManager.findFragmentByTag(tag);
      • 第二种:
        • 通过回调的方式,定义一个接口(可以在Fragment类中定义),接口中有一个空的方法,在fragment中需要的时候调用接口的方法,值可以作为参数放在这个方法中,然后让Activity实现这个接口,必然会重写这个方法,这样值就传到了Activity中
  • Fragment与Fragment之间是如何传值的?
    • 第一种:
      • 通过findFragmentByTag得到另一个的Fragment的对象,这样就可以调用另一个的方法了。
    • 第二种:
    • 第三种:
      • 通过setArguments,getArguments的方式。

1.0.2.9 Activity创建Fragment的方式是什么?FragmentPageAdapter和FragmentPageStateAdapter的区别?

  • Activity创建Fragment的方式是什么?
    • 静态创建具体步骤
      • 首先我们同样需要注册一个xml文件,然后创建与之对应的java文件,通过onCreatView()的返回方法进行关联,最后我们需要在Activity中进行配置相关参数即在Activity的xml文件中放上fragment的位置。
    • 动态创建具体步骤
      • (1)创建待添加的碎片实例
      • (2)获取FragmentManager,在活动中可以直接通过调用 getSupportFragmentManager()方法得到。
      • (3)开启一个事务,通过调用beginTransaction()方法开启。
      • (4)向容器内添加或替换碎片,一般使用repalce()方法实现,需要传入容器的id和待添加的碎片实例。
      • (5)提交事务,调用commit()方法来完成。
  • FragmentPageAdapter和FragmentPageStateAdapter的区别?
    • FragmnetPageAdapter在每次切换页面时,只是将Fragment进行分离,适合页面较少的Fragment使用以保存一些内存,对系统内存不会多大影响
    • FragmentPageStateAdapter在每次切换页面的时候,是将Fragment进行回收,适合页面较多的Fragment使用,这样就不会消耗更多的内存

1.0.3.0 fragment 特点?说一下Fragment的生命周期?如何解决getActivity为null的异常问题?

  • fragment 特点
    • Fragment可以作为Activity界面的一部分组成出现;
    • 可以在一个Activity中同时出现多个Fragment,并且一个Fragment也可以在多个Activity中使用;
    • 在Activity运行过程中,可以添加、移除或者替换Fragment;
    • Fragment可以响应自己的输入事件,并且有自己的生命周期,它们的生命周期会受宿主Activity的生命周期影响。
  • Fragment从创建到销毁整个生命周期中涉及到的方法依次为:
    • onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume()->onPause()->onStop()->onDestroyView()->onDestroy()->onDetach(),其中和Activity有不少名称相同作用相似的方法,而不同的方法有:
      • onAttach():当Fragment和Activity建立关联时调用
      • onCreateView():当Fragment创建视图时调用
      • onActivityCreated():当与Fragment相关联的Activity完成onCreate()之后调用
      • onDestroyView():在Fragment中的布局被移除时调用
      • onDetach():当Fragment和Activity解除关联时调用
  • 如何解决getActivity为null的异常问题技术博客大总结
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        activity = (PhoneNumActivity) context;
    }
    
    @Override
    public void onDetach() {
        super.onDetach();
        activity = null;
    }
    

1.0.3.1 在fragment中为什么有时getActivity()会为null?Fragment试图为什么有的时候会重叠,怎么产生的,又如何解决?

  • getActivity()空指针:
    • 这种情况一般发生在在异步任务里调用getActivity(),而Fragment已经onDetach(),此时就会有空指针,解决方案是在Fragment里使用一个全局变量mActivity,在onAttach()方法里赋值,这样可能会引起内存泄漏,但是异步任务没有停止的情况下本身就已经可能内存泄漏,相比直接crash,这种方式显得更妥当一些。
  • Fragment视图重叠:
    • 在类onCreate()的方法加载Fragment,并且没有判断saveInstanceState==null或if(findFragmentByTag(mFragmentTag) == null),导致重复加载了同一个Fragment导致重叠。(PS:replace情况下,如果没有加入回退栈,则不判断也不会造成重叠,但建议还是统一判断下)
    @Override 
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    // 在页面重启时,Fragment会被保存恢复,而此时再加载Fragment会重复加载,导致重叠 ;
        if(saveInstanceState == null){
        // 或者 if(findFragmentByTag(mFragmentTag) == null)
           // 正常情况下去 加载根Fragment 
        } 
    }
    

1.0.3.2 为什么fragment传递数据不用构造方法传递?FragmentManager , add 和 replace 有什么区别?

  • 为什么fragment传递数据不用构造方法传递?
    • activity给fragment传递数据一般不通过fragment的构造方法来传递,会通过setArguments来传递,因为当横竖屏会调用fragment的空参构造函数,数据丢失。
  • FragmentManager , add 和 replace 有什么区别?
    • 使用FragmentTransaction的时候,它提供了这样两个方法,一个add,一个replace,add和replace影响的只是界面,而控制回退的,是事务。
    • add 是把一个fragment添加到一个容器container里。replace是先remove掉相同id的所有fragment,然后在add当前的这个fragment。技术博客大总结
    • 在大部分情况下,这两个的表现基本相同。因为,一般,咱们会使用一个FrameLayout来当容器,而每个Fragment被add 或者 replace 到这个FrameLayout的时候,都是显示在最上层的。所以你看到的界面都是一样的。但是,使用add的情况下,这个FrameLayout其实有2层,多层肯定要比一层的来得浪费,所以还是推荐使用replace。当然有时候还是需要使用add的。比如要实现轮播图的效果,每个轮播图都是一个独立的Fragment,而他的容器FrameLayout需要add多个Fragment,这样他就可以根据提供的逻辑进行轮播了。而至于返回键的时候,这个跟事务有关,跟使用add还是replace没有任何关系。
    • replace()方法会将被替换掉的那个Fragment彻底地移除掉,因此最好的解决方案就是使用hide()和show()方法来隐藏和显示Fragment,这就不会让Fragment的生命周期重走一遍了。

1.0.3.9 Activitiy启动流程中performLaunchActivity的作用?Activity启动流程中handleResumeActivity的作用?

  • Activitiy启动流程中performLaunchActivity的作用?
    • 从ActivityClientRecord中获取到待启动的Activity的组件信息
    • 使用类加载器创建Activity对象
    • 通过LoadedApk的方法创建Applicayiton对象,该对象唯一,不会重复创建。
    • 会创建ContextImpl并且建立Context和Activity的联系,以及创建PhoneWindow,建立Window和Activity的联系。
    • 调用Activity的onCreate()
  • Activity启动流程中handleResumeActivity的作用?
    • 执行onStart()、onResume()—利用Instrucmentation
    • 获取Window
    • 创建DecorView、设置为不可见INVISIBLE、建立DecorView和Activity的联系。
    • 获取Activity的WindowManager
    • 调用WindowManager.addView(decorView, ...)将DecorView添加到WM中,完成显示的工作。
    • image
  • 何时将DecorView设置为VISIBLE?并且显示出来?技术博客大总结
    • 也是在handleResumeActivity中
    • 现将DecorView设置为不可见
    • wm.addView(): 将DecorView添加到Window总
    • 然后执行makeVisible让DecorView可见
    • image

1.0.4.0 Intent是什么?Intent可以传递哪些数据?传递对象的时候为什么要实例化?

  • Intent是一种运行时绑定(run-time binding)机制,它能在程序运行过程中连接两个不同的组件。
    • 举例:比如,有一个Activity希望打开网页浏览器查看某一网页的内容,那么这个Activity只需要发出 WEB_SEARCH_ACTION给Android
    • Android就会根据Intent的请求内容,查询各组件注册时声明的 IntentFilter,找到网页浏览器的Activity来浏览网页
  • Intent可以传递的数据基本数据类型的数据,数组,还有集合,还有序列化的对象
    • 序列化:表示将一个对象转换成可存储或可传输的状态
    • Android中序列化对象方式:技术博客大总结
      • 第一种:JAVA中的Serialize机制,译成串行化、序列化……,其作用是能将数据对象存入字节流当中,在需要时重新生成对象。主要应用是利用外部存储设 备保存对象状 态,以及通过网络传输对象等。
      • 第二种:在Android系统中,定位为针对内存受限的设备,因此对性能要求更高,另外系统中采用了新的IPC(进程间通信)机制,必然要求使用性能更出色的对象传输方式。

1.0.1.2 Activity如与Service通信?Service的生命周期与启动方法由什么区别?

可以通过bindService的方式,先在Activity里实现一个ServiceConnection接口,并将该接口传递给bindService()方法,在ServiceConnection接口的onServiceConnected()方法
里执行相关操作。

Service的生命周期与启动方法由什么区别?
    startService():开启Service,调用者退出后Service仍然存在。
    bindService():开启Service,调用者退出后Service也随即退出。

Service生命周期:
    只是用startService()启动服务:onCreate() -> onStartCommand() -> onDestory
    只是用bindService()绑定服务:onCreate() -> onBind() -> onUnBind() -> onDestory
    同时使用startService()启动服务与bindService()绑定服务:onCreate() -> onStartCommnad() -> onBind() -> onUnBind() -> onDestory

1.1.0.4 广播有哪些注册方式?有什么区别?广播发送和接收原理是什么[binder如何运作的]?

  • 广播有哪些注册方式?
    • 静态注册:常驻系统,不受组件生命周期影响,即便应用退出,广播还是可以被接收,耗电、占内存。
    • 动态注册:非常驻,跟随组件的生命变化,组件结束,广播结束。在组件结束前,需要先移除广播,否则容易造成内存泄漏。
  • 广播发送和接收原理是什么[binder如何运作的]?
    • 继承BroadcastReceiver,重写onReceive()方法。
    • 通过Binder机制向ActivityManagerService注册广播。
    • 通过Binder机制向ActivityMangerService发送广播。
    • ActivityManagerService查找符合相应条件的广播(IntentFilter/Permission)的BroadcastReceiver,将广播发送到BroadcastReceiver所在的消息队列中。
    • BroadcastReceiver所在消息队列拿到此广播后,回调它的onReceive()方法。

1.0.4.1 mipmap系列中xxxhdpi、xxhdpi、xhdpi、hdpi、mdpi和ldpi存在怎样的关系?

  • 表示不同密度的图片资源,像素从高到低依次排序为xxxhdpi>xxhdpi>xhdpi>hdpi>mdpi>ldpi,根据手机的dpi不同加载不同密度的图片

1.0.4.2 res目录和assets目录的区别?

  • assets:不会在 R文件中生成相应标记,存放到这里的资源在打包时会打包到程序安装包中。(通过 AssetManager 类访问这些文件)
  • res:会在 R 文件中生成 id标记,资源在打包时如果使用到则打包到安装包中,未用到不会打入安装包中。
  • res/anim:存放动画资源
  • res/raw:和 asset下文件一样,打包时直接打入程序安装包中(会映射到 R文件中)

1.0.4.3 Context是什么?Context有哪些类型,分别作用是什么?Context下有哪些子类?哪些场景只能用activity上下文?

  • Context是什么?
    • Context是一个抽象基类。在翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。
  • Context有哪些类型,分别作用是什么?
    • Context下有两个子类,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。
    • ContextWrapper又有三个直接的子类,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity,所以Activity和Service以及Application的Context是不一样的,只有Activity需要主题,Service不需要主题。
  • Context下有哪些子类,主要是干什么的?
    • Context一共有三种类型,分别是Application、Activity和Service。
    • 这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。
    • 不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是系统级别吐司),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

1.0.4.4 ActivityThread的main()的流程大概是怎么样的?

  • ActivityThread的main()的流程大概是怎么样的?
    • image

1.0.5.0 序列化的方式有哪些?效率对比有何优势?如何做性能上分析的?

  • 序列化的方式有哪些
    • Parcelable
      • Parcelable是Android特有的一个实现序列化的接口,在Parcel内部包装了可序列化的数据,可以在Binder中自由传输。序列化的功能由writeToParcel方法来完成,最终通过Parcel的一系列write方法完成。反序列化功能由CREAOR来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化的过程。
    • Serializable
      • Serializable是Java提供的一个序列化接口,是一个空接口,用于标示对象是否可以支持序列化,通过ObjectOutputStrean及ObjectInputStream实现序列化和反序列化的过程。注意可以为需要序列化的对象设置一个serialVersionUID,在反序列化的时候系统会检测文件中的serialVersionUID是否与当前类的值一致,如果不一致则说明类发生了修改,反序列化失败。因此对于可能会修改的类最好指定serialVersionUID的值。

1.0.5.9 界面的刷新为什么需16.6ms?画面的显示需要哪些步骤?界面保持不变时还会16.6ms刷新一次屏幕吗?

  • 界面的刷新为什么需16.6ms?
    • 系统每16.6ms会发出一个VSYNC信号,发出信号后,才会开始进行测量、布局和绘制。
    • 发出VSYNC信号时,还会将此时显示器的buffer缓冲区的数据取出,并显示在屏幕上。
  • 画面的显示需要哪些步骤?
    • CPU计算数据(View树遍历并执行三大流程:测量、布局和绘制),然后将数据交给GPU“
    • GPU渲染处理,然后将数据放到Buffer中。
    • 显示屏(display)从buffer中取出数据,并进行显示。
  • 界面保持不变时还会16.6ms刷新一次屏幕吗?技术博客大总结
    • 对于底层显示器,每间隔16.6ms接收到VSYNC信号时,就会用buffer中数据进行一次显示。所以一定会刷新。
  • 界面刷新的本质流程
    • 通过ViewRootImpl的scheduleTraversals()进行界面的三大流程。
    • 调用到scheduleTraversals()时不会立即执行,而是将该操作保存到待执行队列中。并给底层的刷新信号注册监听。
    • 当VSYNC信号到来时,会从待执行队列中取出对应的scheduleTraversals()操作,并将其加入到主线程的消息队列中。
    • 主线程从消息队列中取出并执行三大流程: onMeasure()-onLayout()-onDraw()

1.0.6.0 Android中日志级别有哪几种?开发中需要注意什么问题,打印日志源码分析原理是什么?

  • Android中日志级别有哪几种?
    • 1.Log.v 的输出颜色为黑色的,输出大于或等于VERBOSE日志级别的信息,也就是可见级别,一般是最低的信息提示
    • 2.Log.d的输出颜色是蓝色的,也就是调式级别,一般不会中止程序,一般是程序员为了调试而打印的log
    • 3.Log.i的输出为绿色,输出大于或等于INFO日志级别的信息,也就是信息界级别,不会中止程序,一般是系统中执行操作的信息提示
    • 4.Log.w的输出为橙色, 输出大于或等于WARN日志级别的信息,也就是警告级别,一般不会中止程序,但是可能会影响程序执行结果
    • 5.Log.e的输出为红色,仅输出ERROR日志级别的信息,也就是错误级别,一般会中止程序运行,是最严重的Log级别。
    • 解释:
      • verbose
      • debug调试
      • info信息
      • warn警告
      • error误差
  • 通过查看源代码我们发现Log类中所有的静态日志方法Log.v(),Log.d(),Log.i(),Log.w(),Log.e()等方法都是底层都是调用了println方法,然后在源码中查看,其实其内部调用的是println_native方法,也就是通过JNI调用底层的c++输出日志。

关于其他内容介绍

01.关于博客汇总链接

02.关于我的博客

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

推荐阅读更多精彩内容