1.基础

博文
博文

一、Activity

1.什么是activity?

Activity一个应用程序的组件,它提供一个屏幕来与用户交互,以便做一些诸如打电话、发邮件和看地图之类的事情,原话如下:
An [Activity](http://developer.android.com/reference/android/app/Activity.html) is an application component that provides a screen with which users can interact in order to do something, such as dial the phone, take a photo, send an email, or view a map.

2.activity四种状态

running / paused /stopped / killed

running:处于活动状态,用户可以点击屏幕,屏幕会做出相应的响应,处于
activity栈顶的一个状态。

paused:失去焦点的状态,或者是被一个非全屏的activity所占据,又或是被一个
透明的activity放置再栈顶,只是失去了和用户交互的能力,所有的状态信息和成
员变量都还在,除非在内存非常紧张的情况下会被回收。

stopped:一个activity被另一个activity完全覆盖的时候,被覆盖的activtiy的就会
处于stopped状态,所有的状态信息和成员变量都还在,除非在内存非常紧张的
情况下会被回收。

killed:activity已经被系统回收,所有保存的状态信息和成员变量都将不存在了。

3.activity生命周期
Activity生命周期.gif
  • Activity启动➡️ onCreate()➡️ onStart()➡️ onResume()

    onCreate():activity被创建的时候调用,生命周期的第一个调用方法,创建
    activity的时候一定要重写该方法,可以做一些初始化的操作,比如布局资源的加
    载或者一些数据的加载。

    onStart():表明此时activity正在启动,已经处于用户可见的状态,当然此时还没
    有处于前台的显示,就是说此时用户还不能与用户进行交互操作;总结就是一个
    可以看见但是无法触摸的状态。

    onResume():已经在前台进行展示,可以与用户进行交互了。

  • 点击Home键回到手机桌面(Activity不可见):onPause()➡️ onstop()

    onPause():activity处于一个暂停的状态,可见但是不可交互

    onstop():整个activity表明已经完全停止,或者完全被覆盖的状态,完全不可
    见,处于后台的状态,如果处于内存紧张的状态下就有可能会被系统回收掉。

  • 当我们再次回到原Activity的时候:onRestart()➡️ onStart()➡️ onResume()

    onRestart():表示activity正在重新启动,是由不可见状态变为可见状态的时候调用

  • 退出Activity时:onPause()➡️ onStop()➡️ onDestory()
    onDestory():当前activity正在销毁,是生命周期中的最后一个方法,可以做一
    些回收工作以及资源的释放

1.onStart和onResume、onPause和onStop从描述上来看差不多,对我们来说有什么实质性的不同呢?
在实际使用过程中的确是没有什么具体的区分,甚至我们可以只保留其中的一对,比如onStart和onStop,
但是从代码回调的意义层面来说,onStart和onStop是从activity是否可见的角度来回调的,而onResume和onPause是从activity是否处于前台这个角度来回调的,除了这个区别,实际使用过程中并无其他明显区别。

2.假设当前Activity为A,如果这是用户打开一个新的Activity B,那么B的onResume和A的onPause哪个先执行呢?
当启动一个新的Activity时,旧Activity的onPause会先执行,然后才会启动新的activity。例如从A activity 点击按钮跳转到B Activity时,生命周期的回调方法如下图所示:


image.png

3.当系统配置发生改变后,Activity会被销毁,会调用onPause()➡️ onStop()➡️ onDestory(),但是同时由于activity是异常状态下终止的,系统会调用onSaveInstanceState来保存当前activity的状态,这个方法一定是在onStop之前被回调的,但是可能在onPause之前也可能是在之后调用,并没有一定的时序。当这个Activity被重新创建之后,系统会调用onCreate、onStart、onRestoreInstanceState(调用时序在onStart之后),系统调用onRestoreInstanceState会把activity销毁时调用的onSaveInstanceState方法保存的Bundle对象作为参数传递给onRestoreInstanceState和onCreate方法,因此我们可以通过onRestoreInstanceState和onCreate方法来判断activity是否被重建了,那么我们就可以取出之前保存的数据并恢复。
系统调用onSaveInstanceState和onRestoreInstanceState后会自动为我们做一些恢复工作,比如说文本框的输入内容,listview的滚动位置等。

4.不想让系统重建Activity,可以在AndroidManifest.xml中给activity配置configChanges属性
常用的属性有以下几个:
loacl :设备的本地位置发生了改变,一般指切换了系统语言
keyboardHidden: 键盘的可访问性发生了改变,比如用户调出了键盘
orientation: 屏幕方向发生了改变,这个是最常用的,比如旋转了手机屏幕
screenSize:当屏幕的尺寸信息发生了改变,比如屏幕旋转,这个选项比较特殊,当编译选项中的minSdkVersion和targetSdkVersion均低于13时,此选项不会导致activity重启,否则大于13时就会导致activity重启,所以当这个配置大于13时,需要配置configChanges 为 screenSize

4.进程优先级

可见进程:处于可见但是用户不可点击的进程

前台进程:处于前台正在与用户交互的,或者前台activity绑定的service

后台进程:前台进程点击了home键回到桌面后,这个进程就会转变为后台进
程,不会立马被kill掉,会根据内存的情况来做回收。

服务进程:在后台开启的service服务

空进程:五个进程中优先级最低,是一个没有活跃的组件,只是出于缓存的目的
而保留

二、Activity任务栈

一个用来管理Activity的容器,后进先出的栈管理模式,一个应用中可以有多个任
务栈,也可以只有一个任务栈,这得根据启动模式来决定。

三、Activity启动模式

1.standard

每次启动activity都会重新创建一个activity的实例,然后将它加到任务栈中,不会
去考虑任务栈是否有该activity的实例,默认的情况就是此种启动模式。如果activity A启动了Activity B(B是标准模式),B就会进入到A所在的栈中,如果用ApplicationContext去启动standard模式的Activity会报错,因为ApplicationContext并没有所谓的任务栈。

2.singleTop(栈顶复用模式)

如果需要创建的activity实例刚好处于任务栈的栈顶,那么就不会再去创建一个新
的activity实例了,而是复用这个栈顶的activity,同时它的onNewIntent方法会被回调,但是他的onCreate()、 onStart()不会被调用,因为他并没有改变。如果新的activity实例已经存在但是不是位于栈顶,那么新的activity任然会重新重建。

3.singleTask(栈内复用模式)

是一个单例模式,当需要创建一个activity的时候,会去检测整个任务栈中是否存
在该activity实例,如果存在,会将其置于任务栈的栈顶,并将处于该activity以上
的activity都移除销毁,这时会回调一个onNewIntent()方法。

  • 补充
    <activity android:name=".MainActivity" android:launchMode="singleTask" android:taskAffinity="com.demo.singletask"/>
    默认情况下taskAffinity的值就是包名,taskAffinity相同就说明是在同一个栈中,TaskId即一样
    taskAffinity属性主要和singleTask启动模式或者allowTaskReparenting属性配对使用。

当taskAffinity和singleTask启动模式配对使用的时候,它是具有该模式的activity的目前任务栈的名字,待启动的activity会运行在名字和taskAffinity相同的任务栈中
当taskAffinity和allowTaskReparenting结合,allowTaskReparenting用于配置是否允许该activity可以更换从属task,通常情况二者连在一起使用,用于实现把一个应用程序的Activity移到另一个应用程序的Task中


image.png
4. singleInstance

singleTask的升级版,整个系统中只有一个实例,启动一singleInstanceActivity
时,系统会创建一个新的任务栈,并且这个任务栈只有他一个Activity,一般用于
多个应用之间的跳转。

  • singleTask与singleTask的却别:
    一个系统中可能存在多个singleTask Activity实例,
    一个系统中只能存在一个singleInstance Activity实例。

设置activity的启动模式
1.在AndroidManifest.xml中设置
<activity
android:name="com.zsw.demo.activity.CallProgramAcitivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:theme="@style/MaterialNoTitle"
android:launchMode="singleTask"/>

2.通过在Intent中设置标志位
Intent intent = new Intent();
intent.setClass(this,MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);


image.png

四、scheme跳转协议

android中的scheme是一种页面内跳转协议,是一种非常好的实现机制,通过定义自己的scheme协议,可以非常方便的跳转app中的各个页面;通过scheme协议,服务器可以定制化的告诉app跳转哪个页面,可以通过消息栏定义化跳转页面,可以通过H5页面跳转页面等。

二、Fragment

1.Fragment为什么被称为第五大组件

首先在使用上是不属于其他四大组件的,它有自己的生命周期,同时它可以灵活
动态的加载到activity中去,同时它并不是像activity一样完全独立的,它必须依附
于activity,而且还需要加载到activity中去。

2.Fragment加载到Activity中的两种方式

静态加载:只需要把Fragment当成一个普通UI控件放到界面Layout中就完事了

动态加载:得先通过FragmentManage来获取FragmentTransaction(事务),然后使用add或者replace的方式加载fragment,最后commit提交事物。

3.FragmentPagerAdapter与FragmentStatePagerAdapter的区别

FragmentPagerAdapter适用于页面较少的情况下,在源码中destoryItem()函数中的最后一行可以看到,调用的是detach方法,该方法的作用是将fragment与activity的关联取消掉,并没有做内存回收处理,所以会更加的消耗内存


FragmentPagerAdapter_destoryItem.png

FragmentStatePagerAdapter适用于页面较多的情况下,查看源码可发现调用的是remove的方法,会进行内存回收处理。


FragmentStatePagerAdapter_destoryItem.png
4.commit和commitAllowingStateLoss区别

commit:该方法必须在存储状态之前调用,也就是onSaveInstanceState(),如果在存储状态之后调用就会报出:
Can not perform this action after onSaveInstanceState错误信息

commitAllowingStateLoss:允许在存储状态之后再调用,但是这是危险的,因为如果activity需要从其状态恢复,那么提交就会丢失,因此,只有在用户可以意外的更改UI状态的情况下,才可以使用该提交。

以下是部分的核心源码:

commit与commitAllowingStateLoss源码对比.png
5.Fragment的生命周期

下面列出两张图,第一张可用于面试记忆,第二张可以详细的摸索清楚activity与
fragment生命周期的走向。

Fragment生命周期.png
fragment与activity生命周期关联图.png

onAttach:将fragment与activity关联
onCreate:初次创建fragment时调用,只是用来创建fragment,此时的activity并没有创建完成
onCreateView:系统在fragment首次绘制界面的时候调用
onViewCreated:fragment的界面已经绘制完毕。

Activity - onCreate:初始化activity中的布局

onActivityCreate:activity渲染绘制完毕

Activity - onStart:activity可见但不可交互操作
onStart:fragment可见但不可交互操作

Activity - onResume:activity已经在前台进行展示,可以与用户进行交互了
onResume:fragment已经在前台进行展示,可以与用户进行交互了

onPause:fragment处于一个暂停的状态,可见但是不可交互
Activity - onPause:activity处于一个暂停的状态,可见但是不可交互

onStop:fragment不可见
Activity - onStop:activity不可见

onDestoryView:与onCreateView关联,fragment即将结束会被保存
onDestory:销毁Fragment,通常按Back键退出或者Fragment被回收时调用此方法
onDetach:会将fragment从activity中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护

Activity - onDesroty:activity销毁

3.Fragment之间的通信

在Fragment中获取Activity中的方法:getActivity
在Activity中获取Fragment中的方法:接口回调
在Fragment中获取Fragment中的方法:先使用getActivity获取到activity,然后使
用getFragmentById

三、Service

1.service是什么

service(服务)是一个可以在用户后台执行长时间操作,而没有用户界面的应用组件
注意:service是运行在主线程中的,绝对是不能做耗时操作的。
请勿将服务与子线程混为一谈

2.两种启动service的方式
  • startService
    1、定义一个类继承Service
    2、在Manifest.xml文件中配置该service
    3、使用Context中的startService(Intent)方法启动该Service
    4、不使用时,使用stopService(Intent)方法停止该服务
    5、onCreate()方法只会调用一次, onStartCommand()方法可以重复调用
    6、服务一旦启动,服务即可在后台无限期的运行,即便启动服务的组件被销毁
    了也不会受影响,除非手动关闭,已启动的服务通常是执行单一操作,而且不会
    将结果返回给调用方

  • bindService
    1、创建BindService服务端,继承自service并在类中,创建一个实现IBinder接
    口的实例对象并提供公共方法给客户端使用
    2、从onBind()回调方法返回此Binder实例
    3、在客户端中,从onServiceConncted()回调方法接收Binder,并使用提供的方法调用绑定服务
    4、当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。
    绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、
    获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应
    用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取
    消绑定后,该服务即会被销毁

参考博文

四、Broadcast receiver

1.广播定义

在Android中,Broadcast是一种广泛运用在应用程序之间传输信息的机制,我们
要发送的广播内容是一个Intent,这个Itent中可以携带我们需要传输的数据。

2.广播的使用场景

1、同一app具有多个进程的不同组件之间的消息通信
2、不同app之间的组件之间消息通信

3.广播种类

1、普通广播(Normal Broadcast):Context.sendBroadcast
2、系统广播(System Broadcast):Android中内置了多个系统广播,只要涉及到手
机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播
2、有序广播(Ordered Broadcast):Context.sendOrderedBroadcast
3、本地广播(Local Broadcast):只在app内传播

4.广播的实现

1、静态注册:在AndroidManifest中注册就可以,注册完成就一直运行,如果它
所依赖的activity销毁了,仍然能够接受广播,甚至app 进程杀死仍然能够接受广

2、动态注册:跟随activity生命周期,在代码中调用Context.registerReceiver方法

// 选择在Activity生命周期方法中的onResume()中注册
@Override
  protected void onResume(){
      super.onResume();

    // 1. 实例化BroadcastReceiver子类 &  IntentFilter
     mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver();
     IntentFilter intentFilter = new IntentFilter();

    // 2. 设置接收广播的类型
    intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);

    // 3. 动态注册:调用Context的registerReceiver()方法
     registerReceiver(mBroadcastReceiver, intentFilter);
 }


// 注册广播后,要在相应位置记得销毁广播
// 即在onPause() 中unregisterReceiver(mBroadcastReceiver)
// 当此Activity实例化时,会动态将MyBroadcastReceiver注册到系统中
// 当此Activity销毁时,动态注册的MyBroadcastReceiver将不再接收到相应的广播。
 @Override
 protected void onPause() {
     super.onPause();
      //销毁在onResume()方法中的广播
     unregisterReceiver(mBroadcastReceiver);
     }
}

⚠️特别注意

动态广播最好在Activity 的 onResume()注册、onPause()注销。
原因:
对于动态广播,有注册就必然得有注销,否则会导致内存泄露
重复注册、重复注销也不允许

5.广播内部实现机制

1、自定义广播接受者BroadcastReceiver,并复写onRecvice()方法;
2、接收者通过Binder机制向AMS(Activity Manager Service)进行注册;
3、广播发送着通过Binder机制向AMS发送广播;
4、AMS查找符合相应条件(IntentFilter/Permission等)的BrocastReceiver,将广播发送到BrocasrReceiver(一般是Activity)相应的循环队列中;
5、消息循环执行拿到该广播,回调BrocastReceiver的onRecerve()方法

6.LocalBroadcastManager详解

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

2.既然它内部是通过Handler来实现广播的发送,那么相比与系统使用Binder实现
自然是更加高效了,同时使用Handler来实现,别的应用无法向我们的应用发送
广播,而我们应用内发送的广播也不会离开我们的广播

参考博文

五、Webview

1.WebView安全漏洞

Android API level 16以及之前的版本存在远程代码执行安全漏洞,该漏洞源于程
序没有正确限制使用addJavascriptInterface方法,远程攻击者可通过使用
Java Reflection API 利用该漏洞执行任意java对象的方法

2.WebView的性能优化

如果WebView是使用xml进行加载的往往在关闭Activity的时候WebView的资源并不会随着Activity的销毁而销毁。

网上推荐的方法解决大体分为2种:
一种是使用代码动态的创建WebView,对传入WebView中的Context使用弱引用,
动态添加WebView的意思是在布局创建一个ViewGroup用来放置
WebView,Activity创建时add进来,在Activity停止时remove掉

第二种是为WebView新开一个进程,不过这样的话使用起来会比较麻烦,效果还
是挺简单粗暴的,据说QQ微信也在使用这种方式:参考

六、Binder

一、Linux内核的基础知识

1.进程隔离/虚拟地址控件

在操作系统中,为了保护某些进程之间的互不干扰,设计了一个叫进程隔离的技术,就是为了避免进程A可以去操作进程B而实现的;进程的隔离实现用到了虚拟地址空间,就是说进程A的虚拟地址空间和进程B的虚拟空间是不一样的,这样,就防止了进程A的数据可以写到进程B中;所以操作系统中不同的进程之间数据是不共享的,如果需要进程间的数据通信,就需要某种进程间通信的机制来完成,在android中就是使用Binder这个机制来完成的。

不同进程间的引用叫做句柄,相同进程间的应用叫做指针,也就是对进程空间中虚拟地址的引用

2.系统调用

对内核有保护机制,对于应用程序只可以访问某些许可的资源,不许可的资源是不可以访问的。

3.Binder驱动

在android系统中,是运行在内核之中的;负责各个用户进程通过Binder通信的内核来交互的模块叫Binder驱动

二、Binder通信机制介绍

1.为什么使用Binder

1)Android使用的Linux内核拥有着非常多的跨进程通信机制
2)性能:在移动设备上(性能受限制的设备,比如要省电),广泛地使用跨进
程通信对通信机制的性能有严格的要求,Binder相对出传统的Socket方式,更加
高效。Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,共
享内存方式一次内存拷贝都不需要,但实现方式又比较复杂。
3)安全:传统的进程通信方式对于通信双方的身份并没有做出严格的验证,比
如Socket通信ip地址是客户端手动填入,很容易进行伪造,而Binder机制从协议
本身就支持对通信双方做身份校检,因而大大提升了安全性

2.Binder通信模型

Binder通信图.png

客户端进程只不过是持有了我们一个服务端的一个代理

3.到底什么是Binder??
1)通常意义下,binder指的是一种通信机制
2)对于Server进程来说,Binder指的是Binder本地对象;
对于Client对象来说,Binder指的是Binder代理对象
3)对于传输过程而言,Binder是可以进行跨进程传递的对象

博文参考
博文参考
博文参考

七、Handler

1、处理过程:
从handler中获取一个消息对象,把数据封装到消息对象中,通过handler的
send…方法把消息push到MessageQueue队列中。
Looper对象会轮询MessageQueue队列,把消息对象取出。
通过dispatchMessage分发给Handler,再回调用Handler实现的handleMessage
方法处理消息

[图片上传中...(Handler流程图.png-523e3e-1515822798479-0)]
Handler流程图.png

2、Handler中为什么要使用ThreadLocal来获取Lopper呢?
因为在不同线程访问同一ThreadLocal时,不管是set方法还是get方法对
ThreadLocal所做的读写操作仅限与各自线程内部,这样就可以使每一个线程有
单独唯一的Lopper。
ThreadLocal博文

3、Handler引起的内存泄漏以及解决办法
原因:在java中,非静态的内部类和匿名内部类都会隐式的持有一个外部类的引
用,所以原因就是匿名内部类持有外部类的引用,导致外部Activity无法释放

解决办法:
1、最直接的思路就是避免使用非静态内部类。使用Handler的时候,放在一个新建的文件中来继承Handler或者使用静态的内部类来替代。静态内部类不会隐含的持有外部类的引用,因此这个activity也就不会出现内存泄漏问题。
2、如果你需要在Handler内部调用外部Activity的方法,你可以让这个Handler持有这个Activity的弱引用,这样便不会出现内存泄漏的问题了。
3、另外,对于匿名类Runnable,我们同样可以设置成静态的,因为静态内部类不会持有外部类的引用。
4、注意:如果使用Handler发送循环消息,最好是在Activity的OnDestroy方法中调用mLeakHandler.removeCallbacksAndMessages(null);移除消息。(这不是解决内存泄漏的方法)
博文

八、AnsycTask

1.Asynctask是什么?
它是Android提供的一个抽象类,他本质上就是一个封装了线程池和handler的异
步框架,主要是来执行异步任务的,由于它内部继承了handler,所以他可以在工
作线程和UI线程中随意切换。

注意:Asynctask能够让你避免使用线程类thread和handler直接处理后台操作,
他可以把运算好的结果交给UI 线程来显示,不过Asynctask只能做一些耗时较短
的操作,如果我们要做耗时较长的操作,我们还是尽量使用线程池。

2.Asynctask使用
它主要有三个重要参数,五个重要方法

  • 三个参数,三种泛型分别代表:
public abstract class AsyncTask<Params, Progress, Result> { }

第一个:“启动任务执行的输入参数”。
第二个:“后台任务执行的进度”。
第三个:“后台计算结果的类型”。
  • 五个方法:
第一种:execute(params…params):执行一个异步任务,需要我们在代码中调用
此方法,触发异步任务的执行(必须在UI线程中调用)

第二种:onPreExecute(),在execute(Params… params)被调用后立即执行,一
般用来在执行后台任务前对UI做一些标记(也可以理解为初始化数据)。

第三种:doInBackground(Params… params),在onPreExecute()完成后立即执
行,用于执行较为费时的操作,此方法将接收输入参数和返回计算结果。在执行
过程中可以调用publishProgress(Progress… values)来更新进度信息。

第四种:onProgressUpdate(Progress… values),在调用
publishProgress(Progress… values)时,此方法被执行,直接将进度信息更新到
UI组件上。

第五种:onPostExecute(Result result),当后台操作结束时,此方法将会被调
用,计算结果将做为参数传递到此方法中,直接将结果显示到UI组件上。

3.Asynctask的内部原理

1、Asynctask的本质是一个静态的线程池,Asynctask派生出的子类可以显现不
同的异步任务,这些任务都是提交到静态的线程池中执行。

2、线程池中的工作线程执行doinbackground(mparams)方法来执行异步操作。

3、当任务状态改变后,工作线程会向UI线程发送消息,Asynctask内部的
internalHandler相应这些消息,并调用相关的回调。

4.Asynctask的注意事项

一、内存泄露。和handler造成的问题一样。非静态内部类持有外部类的匿名引
用,导致外部activity像被内存回收,由于非静态内部类还持有外部类的引用,导
致外部类不能被系统回收,造成内存泄露。

解决方法: 
1、谁为静态内部类。 
2、非静态内部类使用弱引用。 
3、在destroy方法中finish。

5.为什么AsyncTask只会执行一次
AsyncTask定义了一个mStatus变量,表示异步任务的运行状态,分别是PENDING、RUNNING、FINISHED,当只有PENDING状态时,AsyncTask才会执行,这样也就保证了AsyncTask只会被执行一次

二、生命周期

如果在外部类被销毁的时候,没有执行onCancelled方法,这有可能让我们的
activity在销毁之前导致崩溃。因为Asynctask正在处理某些view。但是activity不
存在了,所以就会崩溃。

三、结果丢失 
假如说,activity被系统回收或者横竖屏切换,导致activity被重新创建,而之前运
行的Asynctask会持有之前activity的引用,但是这个引用已经无效,所以这个时
候,你调用onpostExecute()方法去更新界面就会失败,因为结果已经丢失。

5.为什么AsyncTask只能执行一次?
应该说同一时间段内,只执行一次,防止对同一件事进行操作,造成的混乱。我
们知道异步的线程执行的顺序很大程度上是不确定的,有可能先执行的最后执行
完成,后执行的反而先完成。如果在同一时间段内,执行多个线程这里就会出现
混乱。

6.什么是并行,什么是串行,两者的区别是什么
在android1.6版本之前,Asynctask都是串行,也就是他会把所有任务,一串一串
的放在线程池中。但是在1.6到2.3的版本中,它改成了并行。为了维护系统的稳
定,2.3版本后又改回了串行,但是还是可以执行并行的,建议使用串行

来自博文
推荐

九、HandlerThread

1.由来
当我们需要做一个耗时操作时,自然而然会启动一个子线程去执行,当子线程执
行完成之后,就会自动关闭掉;如果过了一会我们又想执行一个耗时操作呢,那
就又得重新开启一个子线程,这样重复的来回开启是非常消耗性能的;而在
android中就为我们提供了HandlerThrea来完成这样的操作。

2.特点

  • HandlerThread本质上是一个线程类,它继承了Thread;
  • HandlerThread有自己内部的Lopper对象,可以进行looper循环;
  • HandlerThread中是不可以处理界面更新的,还是得交给Handler处理;
  • 优点是不会堵塞,减少了对性能的消耗;缺点是不能同时进行多任务
    的处理,需要等待进行处理,处理效率比较低;
  • 与线程池重并发不同,HandlerThread是一个串行队列,HandlerThread背后只
    有一个线程

推荐博文

十、intentService

1.IntentService是什么
IntentService是继承并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统的Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要我们手动去控制或stopSelf()。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandlerIntent回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个。

2.IntentService使用方法
创建IntentService时,只需要实现onHandlerIntent方法和构造方法,onHandlerIntent为异步方法,可以执行耗时操作。

十一、View绘制

首先是measure(测量) 其次 layout(布局) 最后 draw(绘制)

1.measure

测量中首先得知道测量规则,MeasureSpec这个类

MeasureSpec这个类包括测量模式(specModel)
和测量模式下的规格大小(specSize)

MeasureSpec表示形式是32位的int值
前两位是specModel 后30位是specSize

我们都知道SpecMode的尺子类型有很多,不同的尺子有不同的功能,而SpecSize刻度是固定的一种,所以SpecMode又分为三种模式

UNSPECIFIED:父容器不对View有任何大小的限制,这种情况一般用于系统内部,表示一种测量状态
EXACTLY:父容器检测出View所需要的精确大小,这时候View的值就是SpecSize
AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值

measure是final修饰的,需要重写的方式是onMeasure()

measure.png
2.layout
3.draw
4.Android视图工作机制中的重绘

一、invalidate()和requestLayout()

invalidate()和requestLayout(),常用于View重绘和更新,其主要区别如下

invalidate方法只会执行onDraw方法
requestLayout方法只会执行onMeasure方法和onLayout方法,并不会执行onDraw方法。
所以当我们进行View更新时,若仅View的显示内容发生改变且新显示内容不影响View的大小、位置,则只需调用invalidate方法;若View宽高、位置发生改变且显示内容不变,只需调用requestLayout方法;若两者均发生改变,则需调用两者,按照View的绘制流程,推荐先调用requestLayout方法再调用invalidate方法

二、invalidate()和postInvalidate()

invalidate方法用于UI线程中重新绘制视图
postInvalidate方法用于非UI线程中重新绘制视图,省去使用handler

博文

十二、事件分发

1、简要的谈谈Android的事件分发机制?

当点击事件发生时,首先Activity将TouchEvent传递给Window,再从Window传递给顶层View。TouchEvent会最先到达最顶层 view 的 dispatchTouchEvent ,然后由 dispatchTouchEvent 方法进行分发,如果dispatchTouchEvent返回true ,则整个事件将会被销毁,如果dispatchTouchEvent返回 false ,则交给上层view的 onTouchEvent 方法来开始处理这个事件,如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给自身的 onTouchEvent 来处理,如果 interceptTouchEvent 返回 false ,那么事件将继续传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。如果事件传递到某一层的子 view 的 onTouchEvent 上了,且这个方法返回了 false ,那么这个事件会从这个 view 往上传递,都是 onTouchEvent 来接收。而如果传递到最顶view的 onTouchEvent 也返回 false 的话,这个事件就会消失,直到onTouchEvent返回true为止。

2、为什么View有dispatchTouchEvent方法?

因为View可以注册很多事件的监听器,如长按、滑动、点击等,它也需要一个管理者来分发

3、ViewGroup中可能有很多个子View,如何判断应该分配给哪一个?

根据源码可知,它会分配给在点击范围内的子View

4、当点击时,子View重叠应该如何分配?

一般分配给最上层的子View,这是由于安卓的渲染机制导致的

博文

1.简介

Android事件分发机制的发生在View与View之间或者ViewGroup与View之间具有镶嵌的视图上,而且视图上必须为点击可用。当一个点击事件产生后,它的传递过程遵循如下顺序:Activity->Window->View,即事件先传递给Activity,再到Window,再到顶级View,才开始我们的事件分发

2.概念

Android事件分发机制主要由三个重要的方法共同完成的

dispatchTouchEvent:用于进行点击事件的分发
onInterceptTouchEvent:用于进行点击事件的拦截
onTouchEvent:用于处理点击事件
这里需要注意的是View中是没有onInterceptTouchEvent()方法的

dispatchTouchEvent

  • return true:表示该View内部消化掉了所有事件
  • return false:表示事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费
  • return super.dispatchTouchEvent(ev):默认事件将分发给本层的事件拦截onInterceptTouchEvent方法进行处理

onInterceptTouchEvent

  • return true:表示将事件进行拦截,并将拦截到的事件交由本层控件的onTouchEvent进行处理
  • return false:表示不对事件进行拦截,事件得以成功分发到子View
  • return super.onInterceptTouchEvent(ev):默认表示不拦截该事件,并将事件传递给下一层View的dispatchTouchEvent

onTouchEvent

  • return true:表示onTouchEvent处理完事件后消费了此次事件
  • return fasle:表示不响应事件,那么该事件将会不断向上层View的onTouchEvent方法传递,直到某个View的onTouchEvent方法返回true
  • return super.dispatchTouchEvent(ev):表示不响应事件,结果与return false一样

十三、ListView

参考

十四、构建


Android构建流程.png

业务需求很繁杂时,为了避免开发一个版本就人为的打包一次的耗时费力的事件,自动化构建apk是我们努力的目标,Jenkins提供了这样一个功能,他可以持续的集成构建我们的apk,同时需要配置一些gradle文件
ProGuard工具是用于压缩、优化和混淆我们的代码,主要作用是可以移除代码中的无用类,字段,方法和属性同时可以混淆(是对即将要发布的程序进行重新的组织和处理,这个代码不容易被反编译)
ProGuard技术功能:
压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性(Attribute)。
优化(Optimize):对字节码进行优化,移除无用的指令。
混淆(Obfuscate):使用a,b,c,d这样简短而无意义的名称,对类、字段和方法进行重命名。
预检(Preveirfy):在Java平台上对处理后的代码进行预检,确保加载的class文件是可执行的。
ProGuard工作原理:
Entry Point是在ProGuard过程中不会被处理的类或方法。在压缩的步骤中,ProGuard会从上述的Entry Point开始递归遍历,搜索哪些类和类的成员在使用,对于没有被使用的类和类的成员,就会在压缩段丢弃,在接下来的优化过程中,那些非Entry Point的类、方法都会被设置为private、static或final,不使用的参数会被移除,此外,有些方法会被标记为内联的,在混淆的步骤中,ProGuard会对非Entry Point的类和方法进行重命名。以便预检测来保证我们整个代码的稳定性

十五、Okhttp

1.Okhttp简单使用
① 创建一个OkHttpClient对象
②创建一个request对象,通过内部类Builer调用生成Request对象
③创建一个call对象,调用execute/enqueue

//首先创建一个Handler
private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                    imageView.setImageBitmap((Bitmap) msg.obj);
                    break;
            }
        }
};

//然后使用OkHttp发送网络请求,并将结果通过Handler发送到主线程
public void sendRequestByOkHttp() {
        //创建Request对象,并传入请求地址
        Request request = new Request.Builder().url( "http://tnfs.tngou.net/img/ext/160321/e57d5816cb72d7486aa6dbf19a7d0c6c.jpg").build();
        //构造Call对象--其实是AsyncCall对象
        Call call = client.newCall(request);
        //调用Call.enqueue方法进行异步请求
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                //网络请求失败
                Log.d("lenve", "onFailure: " + e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                //网络请求成功,将请求的图片信息显示的ImageView控件上
                Bitmap bitmap = BitmapFactory.decodeStream(   response.body().byteStream());
                Message msg = mHandler.obtainMessage();
                msg.what = 1;
                msg.obj = bitmap;
                mHandler.sendMessage(msg);
            }
        });
    }

源码解析:
发送一个请求会到RealCall的execute()和enqueue(CallBack callBack) 方法中,首先看一下这俩个方法的实现。


@Override 
public Response execute() throws IOException {
  synchronized (this) {// 判断这个Call有没有执行过,每一个Call只能执行一次
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  try {
    client.dispatcher().executed(this);// 最终交给client的任务调度器处理
    Response result = getResponseWithInterceptorChain();// 使用拦截器处理返回结果
    if (result == null) throw new IOException("Canceled");
    return result;
  } finally {
    client.dispatcher().finished(this);//通知dispatcher任务已经执行完了
  }
}

@Override 
public void enqueue(Callback responseCallback) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  client.dispatcher().enqueue(new AsyncCall(responseCallback));// 还是交给dispatcher处理
}

AsynCall 是RealCall的内部类,实现NameRunnable接口,NameRunable为Runnable的实现,在run()方法里会执行execute()抽象方法,所以在线程池运行AsynCall时会调用AsynCall的execute()方法,下面看一下AsynCall的execute()方法


okhttp网络请求流程.png

博文

2.如何使用OkHttp进行异步网络请求,并根据请求结果刷新UI

3.可否介绍一下OkHttp的整个异步请求流程

4.OkHttp对于网络请求都有哪些优化,如何实现的

5.OkHttp框架中都用到了哪些设计模式

OkHttp的底层是通过Java的Socket发送HTTP请求与接受响应的(这也好理解,HTTP就是基于TCP协议的),但是OkHttp实现了连接池的概念,即对于同一主机的多个请求,其实可以公用一个Socket连接,而不是每次发送完HTTP请求就关闭底层的Socket,这样就实现了连接池的概念。而OkHttp对Socket的读写操作使用的OkIo库进行了一层封装。

博文

十六、retrofit

1.retrofit使用

① 在retrofit中通过一个接口作为http请求的api接口
public interface GitHubService {
  @GET(“repos/{owner}/{repo}/contributors”)
  Call<List<Contributor>> repoContributors(
      @Path(“owner”) String owner,
      @Path(“repo”) String repo);
}
② 创建一个Retrofit实例
public static final Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("[https://api.github.com/](https://api.github.com/)")
            .build();

③ 调用api接口
GitHubService gitHubService=GitHubService.retrofit.create(GitHubService.class);
Call<List<Contributor>> call = gitHubService.repoContributors(“square”, “retrofit”);

List<Contributor> result = call.execute().body();
另外,有人可能也会选择设定请求的时间,让它成为异步请求,同时在执行完毕后提供 callback。
call.enqueue(new Callback<List<Contributor>>() {
  @Override
  public void onResponse(Response<List<Contributor>> response, Retrofit retrofit) {
    // handle success
  }
  @Override
  public void onFailure(Throwable t) {
    // handle failure
  }
});

2.动态代理
① 首先,通过method把他转换成ServiceMethod;
② 然后通过ServiceMethod,args获取到OkhttpCall对象
③ 最后再把okhttpCall进一步封装并返回Call对象

3.源码剖析
① 创建一个retrofit对象
② 通过Retrofit.create()方法是怎么把我们所定义的接口转化成接口实例并使用接口中的方法
③ 最终的网络请求调用的okhttp

十七、volley

1.首先需要获取到一个RequestQueue对象
RequestQueue mQueue = Volley.newRequestQueue(mContext); 
2.创建一个StringRequest对象
StringRequest stringRequest = new StringRequest("http://www.baidu.com",  
                        new Response.Listener<String>() {  
                            @Override  
                            public void onResponse(String response) {  
                                Log.d("TAG", response);  
                            }  
                        }, new Response.ErrorListener() {  
                            @Override  
                            public void onErrorResponse(VolleyError error) {  
                                Log.e("TAG", error.getMessage(), error);  
                            }  
                        });
3.将StringRequest对象添加到RequestQueue里面
mQueue.add(stringRequest);

使用总结:通过newRequestQueue(...)函数新建并启动一个请求队列RequestQueue后,只需要往这个RequestQueue不断add Request即可

![volley网络框架流程图.png](https://upload-images.jianshu.io/upload_images/2191038-654fcc9045a62f86.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

1.首先我们初始化一个RequestQueue,因为初始化RequestQueue非常的消耗性能,所以只需要初始化一次即可
2.在newRequestQueue中我们会发现,如果Android版本大于2.3会调用HttpUrlConnection的HurlStack,如果Android版本小于2.3会调用一个HttpClient的HttpClientStack
3.接下来Volley会创建RequestQueue,并调用他的start方法
4.接着会创建1个缓存调度线程(CacheDispatcher)和4个网络调度线程(netDispatcher),并分别调用他们的start方法,所以会默认开启5个后台线程,线程优先级都为10
5.然后分别去轮询各自的消息队列
6.Volley是如何把请求的数据回调回主线程中的?

使用Handler.postRunnable(Runnable)方法回调回主线程中进行处理,ExecutorDelivery的构造方法中可以看到这段代码

博文
推荐博文

十八、ButterKnife
其实就使用一个依托Java的注解机制来实现辅助代码生成的框架

1.使用
  • 绑定一个view
  • 给一个view添加点击事件
  • 给多个view添加点击事件
  • 给ListView 设置 setItemClickListener
2.原理

⚠️ 首先我们应该避免一个误区,butterknife中并不是用的反射的原理来实现的,
因为用反射的话性能会很差

自己用反射实现原理:通过反射获得类中所有注解的属性,并且获得注解当中资
源中Layout的值,最后通过反射findViewById获取到这个View,并赋值给Activity
当中的属性。
很大的缺点是在运行时会大量的使用到反射,而反射其实会影响app的性能,特
别是运行时的性能,容易造成卡顿;又会产生很多的临时变量,临时变量又会引
起垃圾回收,在UI优化中,大量频繁的变量的垃圾回收会造成UI卡顿,UI卡顿也
是一种性能的标志之一。

而我们的butterknife没有使用这样的方法。butterknife使用的是java的注解处理技
术,也就是在代码编译的时候,编译成字节码的时候,它就处理好了注解操作

注意:java的注解处理技术是在编译阶段执行的,它的原理是通过读入我们的源
代码,解析注解然后生产新的java代码,而新生产的java代码当中,最后被编译
成java字节码,由于注解解释器它是不能改变读入的java类的。这就是butterknife的注解原理。

梳理ButterKnife原理流程

  • 开始它会扫描java代码中所有的ButterKnife注解
  • ButterKnifeProcssor ---><className>$$ViewBinder
  • 调用bind方法加载生成的ViewBinder类
3.绑定view的时候,为什么不能private和static??

因为性能问题,如图我们我们把这个view设置成private,那么这个框架他就不能
通过注解的方式来获取了,只能通过反射来获取,此时不管你的性能有多快,都
会影响性能。这是必须注意并且避免的。这也就是和其他注解方式不同的一点。

来自博文

十九、Glide加载图片

public static RequestManager with(Context context) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(context);
    }
RequestManager其实是一个图片加载请求的管理类,实现了LifecycleListener接口,这个接口管理生命周期,需要和activity或fragment所绑定,这样我们可以把我们的request绑定到我们的activity、组件当中,然后我们就可以通过生命周期方法对request请求进行暂停,恢复和清除操作
RequestManagerRetriever这个实例其实是将RequestManager和activity或fragment进行绑定的一个大管家的类,这样就能通过生命周期来进行回调管理

Glide原理的核心是为bitmap维护一个对象池。对象池的主要目的是通过减少大对象内存的分配以重用来提高性能
glide与picasso对比
glide原理

二十、ANR

1.什么是ANR?

Application Not Responding

在主线程中做耗时的操作,导致长时间无法对用户的操作做出响应,Activity最长
为5秒,BrocastReceiver最长为10秒,Service最长为20秒。

2.原因:应用程序的响应性是由Activity Manager和Window Manager系统服务监视的,当它监测到组件在相应的响应的时间没有响应就会弹出ARN的提示。
  • 哪些操作是在主线程中呢?
    Activity中所有生命周期的回调都是在主线程中。

    Service默认是执行在主线程中的。

    BrocastReceiver中的onReceve()回调执行在主线程中。

    没有使用子线程的Lopper的Handler的handlerMessage,post(Runner)是执行在
    主线程中的。

    AsyncTask中除了doInBackground,其他方法都是执行在主线程中的。

3.处理:
  • 使用AnsycTask处理耗时IO操作
  • 使用Thread或者HandlerThread提高优先级
  • 使用Handler来处理工作线程的耗时任务
  • Activity的onCreate()和onResume()回调中尽量避免耗时代码

来自博文

二十一、OOM

1.什么是oom?

当前占用的内存加上我们申请的内存资源超过了Dalvik虚拟机的最大内存限制就
会抛出的out of memory 异常。

博文

二十二、bitmap

1.recycle
2.LRU
3.计算inSampleSize
4.缩略图
5.三级缓存

二十三、UI卡顿问题

1.UI卡顿原理

60fps --> 16ms
60fps:1秒60桢,1000/16
16ms:android中每隔16ms会进行一个渲染,16ms以内完成不了就会导致界面
的卡顿

每次dalvik虚拟机进行GC的时候,所有的线程都会暂停,所以说在16ms内正在
渲染的时候正好遇到了大量的GC操作,就会导致渲染时间不够,从而导致UI卡顿.

overdraw:过度绘制,在屏幕上,它的某个像素在同一桢的时间内绘制了很多
次,经常出现在多层次的UI结构中。可以在手机中的GPU选项中进行观察,有蓝
色、淡绿、淡红、红色、深红色,减少红色尽量出现蓝色。

2.UI卡顿原因分析

1.人为的在UI线程中做轻微的耗时操作,导致UI线程卡顿;
2.布局Layout过于复杂,无法在16ms内完成渲染;
3.同一时间动画执行的次数过多,导致CPU或GPU负载过重;
4.View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU
负载过重;
5.View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染;
6.内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作;
7.冗余资源及逻辑等导致加载和执行缓慢;
8.臭名昭著的ANR;

3.总结

1.布局优化;尽量使用include、merge、ViewStub标签,尽量不存在冗余嵌套及过于复杂布局(譬如10层就会直接异常),尽量使用GONE替换INVISIBLE,使用weight后尽量将width和heigh设置为0dp减少运算,Item存在非常复杂的嵌套时考虑使用自定义Item View来取代,减少measure与layout次数等。

2.列表及Adapter优化;尽量复用getView方法中的相关View,不重复获取实例导致卡顿,列表尽量在滑动过程中不进行UI元素刷新等。

3.背景和图片等内存分配优化;尽量减少不必要的背景设置,图片尽量压缩处理显示,尽量避免频繁内存抖动等问题出现。

4.自定义View等绘图与布局优化;尽量避免在draw、measure、layout中做过于耗时及耗内存操作,尤其是draw方法中,尽量减少draw、measure、layout等执行次数。

5.避免ANR,不要在UI线程中做耗时操作,遵守ANR规避守则,譬如多次数据库操作等

来自博文

二十四、内存泄漏

1.java内存泄漏基础知识

该被释放的对象没有被释放,一直被某个或者某个实例所持有,导致不能垃圾回收

博文

二十五、内存管理

博文

二十六、冷启动优化

1.什么是冷启动

冷启动:当后台不存在该应用的任何进程或者服务时,用户点击icon图标启动,我们称之为冷启动。

热启动:当后台存在该应用的进程或者服务时,用户点击icon图标启动,我们称之为热启动。
一般是用户按了home键回到桌面,或者返回键没有杀进程,或者app本身做了进程重启的机制。

2.冷启动流程
3.如何对冷启动的时间进行优化

我们主要优化冷启动时间,只要冷启动时间优化了,热启动其实也跟着优化了。
冷启动时间分布如下:
application启动时间+欢迎页停留时间
按用户体验的启动时间应该是:
application启动时间+欢迎页停留时间+进入主页后显示主题的时间;

博文

二十七、其他优化

1.android中不用静态变量存储数据
2.关于sp的安全问题
3.内存对象序列化
4.避免ui线程中做繁重的操作

博文

二十七、mvc/mvp/mvvm

博文1
博文2
博文3

二十八、插件化

二十九、热修复
原理
三十、进程保活

1.Android进程的优先级
  • 前台进程
  • 可见进程
  • 服务进程
  • 后台进程
    *空进程
2.Android进程的回收策略
  • Low momery killer:通过一些比较复杂的评分机制,对进程进行打分,然后将分数高的进程判定为bad进程,杀死并释放内存。

  • OOM_ODJ:判读进程优先级

3.进程保活方案
  • 利用系统广播进行拉活
  • 利用系统的Service机制拉活
  • 利用native进程拉活(5.0以后无法实现)
  • 利用 JobScheuler机制拉活(5.0以后新加)
  • 利用账号同步机制拉活

博文
保活

三十一、UIL

三十二、Lint

1.什么是Android lint 检查

Android Lint 是一个静态代码分析工具,它能够对你的Android项目中潜在的bug、可优化的代码、安全性、性能、可用性、可访问性、国际化等进行检查。

2.工作流程

他有个lint tool 工具,它会把我们的Android源代码和lint xml配置文件打包成一个文件之后,输出一个lint output ,并展现出具体哪行代码有问题,我们可以定位到具体问题所在,改写代码,保证上线时的代码质量

3.配置lint

1.配置lint.xml
2.java代码和xml布局文件如何忽略lint检查
3.自定义lint
1)原因

三十三、Kotlin

1.kotlin到底是什么
  • 是一种基于JVM的编程语言
  • 是对java的一种扩展
  • kotlin支持函数式编程
  • kotlin类与java类能相互调用
2.kotlin的环境搭建
  • Android studio安装kotlin插件
  • 编写kotlinActivity测试
  • kotlin会自动配置kotlin

三十四、LruCache

LruCache源码分析
DiskLruCache

LruCache是一个针对内存层面的缓存,基于LRU算法(最近最少使用)实现的。
内部主要是使用LinkHashMap来存储数据,LinkHashMap是继承HashMap的,主要是重写了get方法,当获取数据的时候会把自己添加到节点的头部,保证处于链表的最前面;不常用的数据就会排在最后面,当缓存的数据满了之后,一般是申请进程空间的1/8,就会清楚掉排在最后的这些数据,trimToSize()方法中执行

public V get(Object key) {
        //使用HashMap的getEntry方法,验证了我们所说的查询效率为O(1)
        LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);//在找到节点后调用节点recordAccess方法 往下看
        return e.value;
    }

        //LinkedHashMapEntry
        void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {//accessOrder该值默认为false 但是  在LruCache中设置为true
                lm.modCount++;
                remove();//该方法将本身节点移除链表
                addBefore(lm.header);//将自己添加到节点头部 保证最近使用的节点位于链表前边
            }
        }

三十五、算法

八大算法

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

推荐阅读更多精彩内容