Android知识点

Android

[TOC]

JAVA

JVM

ClassLoader

ClassLoader介绍

ClassLoader默认有3个加载器:

  • 核心加载器
  • 扩展加载器
  • 应用加载器(用户加载器)
    • 加载classpath环境变量下的class文件

ClassLoader特征:

  • classloader具有传递性

    • 虚拟机的策略是使用调用者 Class 对象的 ClassLoader 来加载当前未知的类
  • 延迟加载

    • 在调用某个类的静态方法时,首先这个类肯定是需要被加载的,但是并不会触及这个类的实例字段,那么实例字段的类别 Class 就可以暂时不必去加载,但是它可能会加载静态字段相关的类别,因为静态方法会访问静态字段。而实例字段的类别需要等到你实例化对象的时候才可能会加载。
  • 双亲委派

自定义classloader

  • loadclass方法一般不要覆盖,会影响递归的委派机制
  • 实现findclass逻辑,提供class的byte[]字节
  • 通过defineClass分析byte[]生成Class对象

ClassLoader相当于类的命名空间,起到了类隔离的作用。

多线程编程

原子性

可见性

有序性

可见性是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的

volatile关键字

Synchronized实现原理和锁

  • 偏向锁
  • 轻量级锁(尝试自旋重试,避免线程状态切换比较耗时的成本)
  • 重量级锁

syncronized 语义和原理

synchronized的优化方向从无锁-->乐观锁--->悲观锁的过程,针对不同的场景减少线程同步成本

Java对象内存模型

  • 对象头
  • 实例变量
  • 填充区域

对象头在处于锁状态和无锁状态的时候

ObjectMonitor监视器对象

  • 记录个数
  • wait状态线程容器
  • block状态线程容器

乐观锁 & 悲观锁

乐观锁是操作的时候不加锁,假设没有冲突,如果有冲突就重试,直到成功为止。

CAS原理

CAS通过CPU的cmpxchg指令保证了CAS操作的原子性

CAS包含3个操作数

  • 内存位置
  • 预期原值
  • 修改的新值
内存屏障

内存屏障,也称内存栅栏,内存栅障,屏障指令等, 是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。

Volatile原理

缓存一致性协议

当CPU写数据,如果发现操作的是共享变量,并且在其他CPU存在该变量的缓存,则通知其他CPU置该缓存无效

那么其他CPU则需要从内存中重新读取数据,这个时候读取的数据就能保证一致性

Happen-before原则

程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

Reentrantlock & AQS

CAS和volatile是实现concurrent包的基石

CAS保证了读-改-写的原子性操作,volatile保证了并发的可见性

各种Hash容器对比

HashMap &HashTable

  • 计算hash值方式不同,hashmap亦或运算,hashtable取模运算,hashtable的效率更低
  • hashtable使用链表方式解决hash冲突,hashmap在链表增长到一定程序后(8个)支持红黑树
  • 初始桶大小和扩容方式不同,hashmap要求2的整数次幂
  • hashmap允许null,hashtable不允许null
  • hashtable线程安全容器和hashmap非线程安全容器
  • 遍历方式有区别,hashmap是iterator(迭代器)支持fail-fast机制,hashtable是enumeration(枚举)

为什么jdk1.7采用头部插入,而jdk1.8采用尾部插入

修改原因

主要原因是头插法会导致扩容时候的改变元素原本的位置,这样在多线程情况下会导致产生链表成环

从而引起链表遍历死循环

本质是同一个节点的next指针发生了变化,导致出现了互相指向next

orignal: 10->2

t1: 2->10

t2: 2->10->2 形成链表环

enumeration iterator区别

  • 接口不同

  • iterator支持fail-fast: fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

ConcurrentHashMap & HashTable

优化点

  • 读写分离,读数据不加锁,写数据加锁
  • 锁的颗粒度尽量的小,使用分段锁技术
  • 锁分离技术支持多个操作并发

不足点:

  • 需要二次hash映射segment,效率比hashmap低

put操作流程

final V put(K key, int hash, V value, boolean onlyIfAbsent) {       //(1)
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {          //(2)
                HashEntry<K,V>[] tab = table;          //(3)
                int index = (tab.length - 1) & hash;          //(4)
                HashEntry<K,V> first = entryAt(tab, index);          //(5)
                for (HashEntry<K,V> e = first;;) {
                    if (e != null) {
                        K k;
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }            //(6)
                    else {              
                        if (node != null)                 //(7)
                            node.setNext(first);
                        else  //(8)
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;              //(9)
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else   //(10)
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {         //(11)
                unlock();
            }
            return oldValue;
   }
   
代码(1)首先第一步的时候会尝试获取锁,如果获取失败肯定就有其他线程存在竞争,则利用 scanAndLockForPut() 自旋获取锁。

代码(2)每一个Segment对应一个HashEntry[ ]数组。

代码(3)计算对应HashEntry数组的下标 ,每个segment中数组的长度都是2的N次方,所以这里经过运算之后,取的是hash的低几位数据。

代码(4)定位到HashEntry的某一个结点(对应链表的表头结点)。

代码(5)遍历链表。

代码(6)如果链表为空(即表头为空)

代码(7)将新节点插入到链表作为链表头。、

代码(8)根据key和value 创建结点并插入链表。

代码(9)判断元素个数是否超过了阈值或者segment中数组的长度超过了MAXIMUM_CAPACITY,如果满足条件则rehash扩容!

代码(10)不需要扩容时,将node放到数组(HashEntry[])中对应的位置

代码(11)最后释放锁。

NIO

NIO和BIO区别

  • NIO面向缓存区,BIO面向流
  • NIO面向缓存区因此可以在缓冲区中移动,增强了数据处理的灵活性
  • 传统的FileInputStream中也可以获取到channel对象进行NIO处理

Non-blocking 非阻塞IO

三大核心部分

  • Channel(通道)
  • Buffer(缓冲区)
  • Selector(选择器)

数据从通道读取到缓冲区,或者从缓冲区写入通道中,selector用于监听多个通道的事件

stream和channel区别

  • stream是单向的,要不是写,要不是读
  • channel是双向的,即可以写,也可以读
  • channel的读写是可以异步的,可以通过channel.configureBlocking(false)进行配置
  • channel相当于操作系统的内核缓冲区

directbuffer模式

  • diredtbuffer相当于创建了内核缓冲区,因此申请和释放的成本比较高
  • directbuffer的内存回收机制不是JVM控制,因此需要开发者自己维护
  • directbuffer模式相当于内核缓冲区直接写入channel,减少了用户态的数据拷贝,因此性能比较高

大文件处理

  • 分段映射
  • 使用FileChannel.map进行文件映射到虚拟内存中,减少了JVM堆内存的分配
MappedByteBuffer

MappedByteBuffer以及ByteBufer的底层原理

  • mmap映射首先返回指针ptr,这个地址指向进程逻辑空间的一个地址
  • ptr操作文件出现缺页中断,这个时候缺页处理从swap空间寻找相应的页面,如果找到直接读取到物理内存空间
  • 找不到,则将硬盘文件读取到物理内存空间中
  • 如果物理内存空间不足,则通过swap把不使用的页面拷贝到硬盘空间

本质上内存映射是减少了物理文件数据拷贝到内核缓冲区,直接拷贝到用户的缓冲区空间

FileChannel优势

OKIO

ThreadLocal原理

threadlocal并不是用来解决多线程环境下的共享变量问题

而是用来提供线程捏的共享变量,从而在多线程环境下,实现线程间的数据的隔离

  • 获取线程关联的ThreadLocalMap对象
  • 在线程池环境中,因为Thread对象不会被回收,因此ThreadLocal保存的value存在内存泄漏的可能性,因此需要在不使用的时候调用ThreadLocal的remove方法进行移除

为什么ThreadLocal设置value的时候都使用ThreadLocal作为ThreadLocalMap的key却不会冲突

  • ThreadLocal只能保存当前线程的一个副本,使用的空间换时间的方式解决线程安全的同步访问问题
  • 每个ThreadLocalMap中的Entry个数是和使用的ThreadLocal的个数一致,因为每个ThreadLocalMap是单独属于Thread的对象,使用的key是ThreadLocal对象

线程池原理

Android流程

基础

Ashmem 匿名共享内存

linux下一切都是文件,因此可以获取文件描述符fd

系统启动流程

  • Loader层包括boot Rom 和bootloader
  • kenerl层主要是Android内核层
  • Native层主要包括init进程以及fork出来的用户空间的守护进程,HAL层,开机动画等
    • 初始化AndroidRuntime
    • 根据参数启动Zygote进程
    • 启动虚拟机
    • 注册JNI函数
    • 调用JNI函数
    • 创建本地socket服务
    • 预加载系统类和资源
    • 启动SystemServer进程
    • 启动ZygoteServer的selectLoop线程处理子进程的命令
  • Framework层主要指AMS,WMS,PMS等Service的初始化
    • 初始化系统Context
    • 启动Bootstrap,Core,Other系统服务
  • Application层主要指SystemUI,Launcher的启动

应用启动流程

  • 创建子进程
  • 调用子进程main入口函数
  • 调用AMS服务的attachApplication传递应用的ApplicationThread对象给系统AMS服务
  • 启动应用进程的消息循环
  • AMS调用应用进程的bindApplication方法,创建应用Application对象
    • 安装contentProvider组件
  • 调用ActivityStackSupervisor的 attachApplicationLocked
  • 调用应用进程的 app.thread.scheduleLaunchActivity 通知应用进程启动Activity组件
  • 应用进程执行 handleLaunchActivity创建Activity活动组件,执行attch操作
  • Activity attach操作创建PhoneWindow对象,添加开发者提供的视图
  • AMS触发makeVisible启动Activity的显示,调用ViewRootImpl设置视图,调用WMS的addToDisplay申请真实的surface,同时启动绘制流程
  • 同时创建 WindowInputEventReceiver进行事件的监听
  • Native层调用WindowInputEventReceiver的dispatchInputEvent进行事件派发处理

应用事件传递流程

渲染流程

surface跨进程传递

BufferQueue实现了BnGraphicBufferProducer和BnGraphicBufferConsumer接口

BufferQueue本质上提供了生产者和消费者对图形数据缓存区的统一管理

BufferQueue 实现原理是binder和共享内存机制

动画流程分析

动画流程分析

  • 创建AnimationHandler对象,注册基于基于Choreographer的动画回调
  • 同时添加Animation相关的回调监听
  • 调用doFrame执行Vsync信号处理
  • 计算插值,更新View属性,进行动画刷新

设备标识

唯一标识

  • 把碎片化的属性数据拼接串联起来,消除数据孤岛,形成一个设备画像

内存管理

进程类型(5类)

  • 前台进程

  • 可见进程

  • 服务进程

  • 后台进程

  • 空进程

内存LMK规则

  • 通过计算得分给当前进程进行权重赋值
  • 根据权重的赋值对比在mini_free的阈值进行进程回收
  • 阈值范围 -16~15
  • launcher进程值比较特殊,单独定义了 6 ,这样提升了launcher在后台的时候的优先级

UI&&屏幕刷新

ViewRootImpl

视图和窗口管理器的协议实现,是最顶层视图的抽象

SurfaceFlinger

针对图形系统的surface的建立,控制,管理的系统服务,是图形系统和显示系统对接的通道

VSync

surfurcelinger定时发送的数据帧刷新信号

Choreographer

编舞者解决Vsync和绘制不同步的问题,原理就是往Choreographer发送消息,最快也需要等到下一个Vsync信号来的时候才开始处理消息

用来和vsync信号同步,执行vsync的回调执行ViewRootImpltraversavl遍历动作,流程如下:

  • 应用层触发invalidate刷新请求,调用ViewRootImpl#requestLayout
  • ViewRootImpl触发遍历请求,执行scheduleTraversals
  • 调用Choreographer的postCallback方法,把mTraversal遍历操作放入CallbackQueues队列中
  • 调用Choreographer的scheduleFrameLocked,监听Vsync信号
  • 编舞者Choreographer监听Vsync信号之后,接收到Vsync信号之后,发送UI线程消息,UI线程触发监听器接收器的run操作,执行doFrame操作
  • doFrame操作从CallbackQueues取出mTraversals执行run操作触发View的绘制流程
  • View的measure,layout,draw操作

同步消息栅栏

同步消息栅栏,是为了保证VSync信号能及时的处理,是Android消息机制提供的异步信息,用户处理高优先级消息的一种机制。

在这里过程中,我们依然需要尽量保证每个同步消息的处理不要超过16ms,这样才会减少VSync信号处理不及时的概率。

要点

  • 二级缓冲和三级缓冲
    • 缓冲不是越多越好,缓冲越多会导致缓存的图形数据越多,导致实时需要显示的数据被延迟
    • 三级缓冲的目的是为了解决二级缓冲的情况下,CPU&GPU帧率不及显示帧率的情况下,BackBuffer被锁,CPU空闲的情况,利用率不高的情况
    • 三级缓冲是增加了CPU/GPU追赶显示帧率的概率,让后续的页面显示更加顺畅

页面显示时机

  • 创建Activity的attach关联window,同时也创建WindowManager对象供后续添加View对象
  • 调用Activity的makeVisible调用WindowManager添加View对象
  • WindowManager对象调用ViewRootImpl对象执行setView操作,对接WMS服务进行Window对象添加
  • ViewRootImpl的窗口添加操作会调用WMS创建Surface对象,在这个过程之前会启动第一次的requestLayout请求,保证页面的遍历请求比其他消息先执行。

Surface 和Buffer申请流程

View、Canvas与Surface的关系

绘制流程
  • 调用surface lockCanvas 获取canvas
    • 从bufferQueue中获取存放绘制元数据的GraphicBuffer(图像缓存块)
  • 调用view.draw(canvas)进行视图绘制
  • 绘制结束,进行post到消费队列中通知surfaceflinger进行消费

View的绘制流程

深入浅出屏幕刷新原理

Android中View的更新方法:invalidate()和requestLayout()

操作系统

死锁

死锁的处理方式

死锁的必要条件

  • 互斥条件

    资源是进程独占且排它使用

  • 不可剥夺条件

    进程所获得的资源在未使用完毕之前,不能被其他进程强行剥夺

  • 请求和保持条件

    进程申请新资源的同时,继续占用已分配到的资源

  • 循环等待

    环路等待

避免死锁的方法

  • 有序资源分配法(打破循环等待条件)
    • 对它锁必须使用而且同一类的所有资源,必须一次申请完
    • 在申请不同的资源时,必须按照各种设备的编号依次申请(避免形成环路条件)
  • 银行家算法

避免死锁的技术

  • 加锁顺序(线程按照一定的顺序加锁)
  • 加锁时限(线程尝试获取锁的时加上一定的时限,超过时限则放弃对锁的请求)
  • 死锁检测

进程通信

binder细节

BinderProxy创建过程

Binder

使用binder的优势

  • 减少了数据从用户态到内核态的拷贝次数
  • 安全私密,从内核层面添加身份标记(PID/UID)

Binder简述

binder内主要涉及到binder_nodebinder_thread的管理

binder_node和binder_thread都是通过红黑树进行保存

binder的主要过程

  • 创建binder_write_read对象封装binder_transaction_data

  • 提交transaction事务

  • binder驱动根据提交的binder_write_read对象中的transaction信息创建binder_transaction对象

  • 根据transaction对象中的数据信息申请内存映射缓冲区

  • 把client的用户数据拷贝到内存映射的缓冲区

  • binder驱动往binder server端提交binder work到binder线程的todo 队列中

  • binder server端ioctl从阻塞状态唤醒处理binder work

  • binder server端的binder thread执行binder work,往binder server用户态传递请求

  • binder server执行完执行,创建reply 的transaction

ioctl

ioctl是专用于设备输入输出操作的系统调用

ioctl调用传入设备有关的请求码,系统调用的功能取决于请求码

binder驱动执行请求码的实现

binder驱动解析和实现这些请求码

虚拟内存

虚拟内存

binder引用关系
  • client ----> handle 句柄
  • binder driver ----> binder ref
  • server ---> binder node ----> service

用户态和内核态工作

进程 用户态 内核态 说明
client 创建BpBinder对象,关联Service组件handle BpBinder对象handle关联binder ref对象(handle是内核共享对象)
Binder ref对象关联binder node对象(binder node是内核共享对象)
Binder node对象关联service服务组件
server 创建service服务组件
binder驱动创建一个binder node对象对应service服务组件 说明只有当一个binder对象经过ServiceManager对象addService处理之后才会在binder驱动层面创建对应的binder node对对象

Binder数据模型

所有的设计最终会体现在模型上,因此分析基本的数据模型对象能更深刻的了解binder的设计原理

struct binder_proc

描述binder进程的数据对象,从这个对象中我们开始发现一些特征

  • binder是以进程为管理的颗粒度
  • 一个进程可以有多个binder对象,多个binder对象共享一个进程的线程池
struct binder_write_read

描述递交一个transaction的具体数据,在这个对象中我们会封装

  • BC_TRANSACTION 命令协议
  • binder_transaction_data 对象

这个过程的是需要传递给到binder驱动,会拷贝transaction对象

struct binder_work

binder_work表示一个transaction的特征,用于标识一个特定的操作行为

有多种类型的binder work用于标识不同的操作行为

struct binder_transaction

描述一次transaction的client和service的一次通信,主要包含的信息有

  • 需要执行的binder work
  • 内存映射缓冲区的buffer控制对象
  • 请求服务端的code码
  • 发起binder请求的线程,处理binder请求的进程,处理binder请求的线程
struct binder_transaction_data

描述transaction中的信息

  • 请求服务对象的target
  • 请求服务器的code码
  • 请求的数据信息
struct binder_alloc

基于binder_transaction_data的数据信息生成buffer对象(数据缓冲区管理对象),分配page建立地址映射

描述binder进程的内存分配信息

flat_binder_object

用于包装传递的Binder对象,可能是本地对象,或者是句柄

Binder线程模型

Binder服务死亡

每一个binder对象都可以设置IBinder.DeathRecipient (死亡接收者回调)

/frameworks/base/core/java/android/os/Binder.java

final class BinderProxy implements IBinder {
    // Assume the process-wide default value when created
    volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;

    public native boolean pingBinder();
    public native boolean isBinderAlive();
    ......
    ......
    ......
    public native String getInterfaceDescriptor() throws RemoteException;
    public native boolean transactNative(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException;
    public native void linkToDeath(DeathRecipient recipient, int flags)
            throws RemoteException;
/frameworks/native/libs/binder/BpBinder.cpp

status_t BpBinder::linkToDeath(
    const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags)
{
    Obituary ob;
    ob.recipient = recipient;
    ob.cookie = cookie;
    ob.flags = flags;

    LOG_ALWAYS_FATAL_IF(recipient == NULL,
                        "linkToDeath(): recipient must be non-NULL");

    {
        AutoMutex _l(mLock);

        if (!mObitsSent) {
            if (!mObituaries) {
                mObituaries = new Vector<Obituary>;
                if (!mObituaries) {
                    return NO_MEMORY;
                }
                ALOGV("Requesting death notification: %p handle %d\n", this, mHandle);
                getWeakRefs()->incWeak(this);
                IPCThreadState* self = IPCThreadState::self();
                self->requestDeathNotification(mHandle, this);
                self->flushCommands();
            }
            ssize_t res = mObituaries->add(ob);
            return res >= (ssize_t)NO_ERROR ? (status_t)NO_ERROR : res;
        }
    }

    return DEAD_OBJECT;
}

binder驱动管理所有的binder句柄,同时对binder句柄设置该binder句柄的BpBinder的监听对象

/frameworks/native/libs/binder/IPCThreadState.cpp

status_t IPCThreadState::requestDeathNotification(int32_t handle, BpBinder* proxy)
{
    mOut.writeInt32(BC_REQUEST_DEATH_NOTIFICATION);
    mOut.writeInt32((int32_t)handle);
    mOut.writePointer((uintptr_t)proxy);
    return NO_ERROR;
}

status_t IPCThreadState::clearDeathNotification(int32_t handle, BpBinder* proxy)
{
    mOut.writeInt32(BC_CLEAR_DEATH_NOTIFICATION);
    mOut.writeInt32((int32_t)handle);
    mOut.writePointer((uintptr_t)proxy);
    return NO_ERROR;
}

IPC状态负责处理各种命令

status_t IPCThreadState::executeCommand(int32_t cmd)
{
    BBinder* obj;
    RefBase::weakref_type* refs;
    status_t result = NO_ERROR;

    switch ((uint32_t)cmd) {
    case BR_ERROR:
        result = mIn.readInt32();
        break;

    case BR_OK:
        break;

    case BR_ACQUIRE:
        refs = (RefBase::weakref_type*)mIn.readPointer();
        obj = (BBinder*)mIn.readPointer();
        ALOG_ASSERT(refs->refBase() == obj,
                   "BR_ACQUIRE: object %p does not match cookie %p (expected %p)",
                   refs, obj, refs->refBase());
        obj->incStrong(mProcess.get());
        IF_LOG_REMOTEREFS() {
            LOG_REMOTEREFS("BR_ACQUIRE from driver on %p", obj);
            obj->printRefs();
        }
        mOut.writeInt32(BC_ACQUIRE_DONE);
        mOut.writePointer((uintptr_t)refs);
        mOut.writePointer((uintptr_t)obj);
        break;

    case BR_RELEASE:
        refs = (RefBase::weakref_type*)mIn.readPointer();
        obj = (BBinder*)mIn.readPointer();
        ALOG_ASSERT(refs->refBase() == obj,
                   "BR_RELEASE: object %p does not match cookie %p (expected %p)",
                   refs, obj, refs->refBase());
        IF_LOG_REMOTEREFS() {
            LOG_REMOTEREFS("BR_RELEASE from driver on %p", obj);
            obj->printRefs();
        }
        mPendingStrongDerefs.push(obj);
        break;

    case BR_INCREFS:
        refs = (RefBase::weakref_type*)mIn.readPointer();
        obj = (BBinder*)mIn.readPointer();
        refs->incWeak(mProcess.get());
        mOut.writeInt32(BC_INCREFS_DONE);
        mOut.writePointer((uintptr_t)refs);
        mOut.writePointer((uintptr_t)obj);
        break;

    case BR_DECREFS:
        refs = (RefBase::weakref_type*)mIn.readPointer();
        obj = (BBinder*)mIn.readPointer();
        // NOTE: This assertion is not valid, because the object may no
        // longer exist (thus the (BBinder*)cast above resulting in a different
        // memory address).
        //ALOG_ASSERT(refs->refBase() == obj,
        //           "BR_DECREFS: object %p does not match cookie %p (expected %p)",
        //           refs, obj, refs->refBase());
        mPendingWeakDerefs.push(refs);
        break;

    case BR_ATTEMPT_ACQUIRE:
        refs = (RefBase::weakref_type*)mIn.readPointer();
        obj = (BBinder*)mIn.readPointer();

        {
            const bool success = refs->attemptIncStrong(mProcess.get());
            ALOG_ASSERT(success && refs->refBase() == obj,
                       "BR_ATTEMPT_ACQUIRE: object %p does not match cookie %p (expected %p)",
                       refs, obj, refs->refBase());

            mOut.writeInt32(BC_ACQUIRE_RESULT);
            mOut.writeInt32((int32_t)success);
        }
        break;

    case BR_TRANSACTION:
        {
            binder_transaction_data tr;
            result = mIn.read(&tr, sizeof(tr));
            ALOG_ASSERT(result == NO_ERROR,
                "Not enough command data for brTRANSACTION");
            if (result != NO_ERROR) break;

            Parcel buffer;
            buffer.ipcSetDataReference(
                reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                tr.data_size,
                reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
                tr.offsets_size/sizeof(binder_size_t), freeBuffer, this);

            const pid_t origPid = mCallingPid;
            const uid_t origUid = mCallingUid;
            const int32_t origStrictModePolicy = mStrictModePolicy;
            const int32_t origTransactionBinderFlags = mLastTransactionBinderFlags;

            mCallingPid = tr.sender_pid;
            mCallingUid = tr.sender_euid;
            mLastTransactionBinderFlags = tr.flags;

            //ALOGI(">>>> TRANSACT from pid %d uid %d\n", mCallingPid, mCallingUid);

            Parcel reply;
            status_t error;
            IF_LOG_TRANSACTIONS() {
                TextOutput::Bundle _b(alog);
                alog << "BR_TRANSACTION thr " << (void*)pthread_self()
                    << " / obj " << tr.target.ptr << " / code "
                    << TypeCode(tr.code) << ": " << indent << buffer
                    << dedent << endl
                    << "Data addr = "
                    << reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer)
                    << ", offsets addr="
                    << reinterpret_cast<const size_t*>(tr.data.ptr.offsets) << endl;
            }
            if (tr.target.ptr) {
                // We only have a weak reference on the target object, so we must first try to
                // safely acquire a strong reference before doing anything else with it.
                if (reinterpret_cast<RefBase::weakref_type*>(
                        tr.target.ptr)->attemptIncStrong(this)) {
                    error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer,
                            &reply, tr.flags);
                    reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this);
                } else {
                    error = UNKNOWN_TRANSACTION;
                }

            } else {
                error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);
            }

            //ALOGI("<<<< TRANSACT from pid %d restore pid %d uid %d\n",
            //     mCallingPid, origPid, origUid);

            if ((tr.flags & TF_ONE_WAY) == 0) {
                LOG_ONEWAY("Sending reply to %d!", mCallingPid);
                if (error < NO_ERROR) reply.setError(error);
                sendReply(reply, 0);
            } else {
                LOG_ONEWAY("NOT sending reply to %d!", mCallingPid);
            }

            mCallingPid = origPid;
            mCallingUid = origUid;
            mStrictModePolicy = origStrictModePolicy;
            mLastTransactionBinderFlags = origTransactionBinderFlags;

            IF_LOG_TRANSACTIONS() {
                TextOutput::Bundle _b(alog);
                alog << "BC_REPLY thr " << (void*)pthread_self() << " / obj "
                    << tr.target.ptr << ": " << indent << reply << dedent << endl;
            }

        }
        break;

    // 处理binder实体死亡
    case BR_DEAD_BINDER:
        {
            BpBinder *proxy = (BpBinder*)mIn.readPointer();
            proxy->sendObituary();
            mOut.writeInt32(BC_DEAD_BINDER_DONE);
            mOut.writePointer((uintptr_t)proxy);
        } break;

    case BR_CLEAR_DEATH_NOTIFICATION_DONE:
        {
            BpBinder *proxy = (BpBinder*)mIn.readPointer();
            proxy->getWeakRefs()->decWeak(proxy);
        } break;

    case BR_FINISHED:
        result = TIMED_OUT;
        break;

    case BR_NOOP:
        break;

    case BR_SPAWN_LOOPER:
        mProcess->spawnPooledThread(false);
        break;

    default:
        ALOGE("*** BAD COMMAND %d received from Binder driver\n", cmd);
        result = UNKNOWN_ERROR;
        break;
    }

    if (result != NO_ERROR) {
        mLastError = result;
    }

    return result;
}

Native进程

场景

  • 某些场景下,只允许root用户才能进行写操作
  • 通过init.rc root权限启动bin

c++实现方式

  1. 定义RPC通信接口
  2. 定义服务器端接口,实现BnInterface(BnInterface继承于BBinder)
  3. 服务器端实现onTransact方式,执行客户端请求
  4. 定义客户端,实现BpInterface
  5. 客户端在接口内实现remote调用transact调用向服务器端发送请求
  6. 定义服务端实现类,实现2中定义的服务端接口
  7. 在ServiceManager中添加6的服务端实现类
  8. 默认方法的code是从1开始计数

java实现方式(参考ActivityManagerNative方式)

  • 定义IActivityManager接口
  • 继承Binder实现IActivityManager接口
  • 实现proxy类,执行transact方式调用服务
  • service端执行onTransact响应客户端调用请求

JNI原理

网络知识

TCP/IP

tcp握手三次的原因

TCP可靠性定义

  • 完整

  • 有序

  • 无差错

性能优化

稳定性

OOM

NativeCrash

流畅(卡顿)

  • 过度绘制
  • 布局优化(merge-干掉一层,viewstub-布局延迟加载)
  • 9宫格图片使用
  • IO优化
  • 内存优化

工具

Profiler

SystemTrace
MethodTrace

功耗(电量,流量)

网络

  • 后台联网操作释放
  • 网络的本身优化
    • 连接复用
    • 数据压缩
    • IO优化

屏幕

CPU

  • wakeLock锁释放
  • sensor释放
  • 任务执行策略修改(充电时候执行)

APK包瘦身

  • so文件瘦身
  • asset资源瘦身
  • 无用的图片
  • 第三方库的使用
  • 动态运行时资源下载

网络优化

  • IO模型,非堵塞模式,减少无效等待,类似epoll,select方式
  • 线程模型,多线程机制
  • 序列化方式,针对传输数据量优化,另外还有压缩处理
  • 池化技术,连接通道缓存
  • DirectBuffer ,减少用户态切换内核态的数据拷贝

图片优化

  • 尺寸压缩
  • 采样压缩
  • 图片格式webp保存空间小

尺寸压缩和采样压缩都是缩小了图片的尺寸,只是使用的技术不同

StrictMode

  • 线程策略

  • VM策略

    • 泄漏的Activity对象
    • 泄漏的sqlite对象
    • 检测实例的数量

算法

二叉树遍历

public class Tree{
    Object data;
    Tree left;
    Tree right;
    boolean accessed;
}

非递归算法

先序遍历(根左右)
  1. printf当前node节点(node.data)
  2. 压入当前node节点入栈
  3. 循环遍历左子树栈(把所有的左子树压入栈)
  4. 左子树遍历完毕,栈顶出栈
  5. 切换到栈顶树的右子树遍历
  6. 重复1->5过程,所有元素至此遍历完毕
public void preTraversal(Tree node){
    if(node == null){
        return;
    }
    Stack<Tree> treeNodeStatck = new Stack<>();
    while(node != null || !treeNodeStatck.isEmpty()){
        //循环遍历左子树,一直遍历到左子树结束
        while(node != null){
            //打印根节点值
            printf("node data " + node.data);
            treeNodeStatck.push(node);
            node = node.left;
        }
        //左子树遍历完毕,取出根节点
        node = treeNodeStatck.pop();
        //切换到右子树遍历
        node = node.right;
    }
}
中序遍历(左根右)
public void midTraversal(Tree node){
    if(node == null){
        return;
    }
    Stack<Tree> treeNodeStatck = new Stack<>();
    while(node != null || !treeNodeStatck.isEmpty()){
        //循环遍历左子树,一直遍历到左子树结束
        while(node != null){
            treeNodeStatck.push(node);
            node = node.left;
        }
        //子左树遍历结束,打印子左树根节点值
        node = treeNodeStatck.pop();
        printf("node data " + node.data);
        //切换到子左树根节点的右子树遍历
        node = node.right;
    }
    
}
后序遍历(左右根)
public void postTraversal(Tree node){
    if(node == null){
        return;
    }
    Stack<Tree> treeNodeStatck = new Stack<>();
    while(node !=null || !treeNodeStatck.isEmpty()){
        while(node != null){
            treeNodeStatck.push(node);
            node = node.left;
        }
        //子树根节点出栈
        node = treeNodeStatck.pop();
        //是否压入过栈,也就是说是否右子树已经遍历完
        if(node.accessed){
            printf("node data " + node.data);
            node = null;
        }else{
            //暂时压入子树根节点
            treeNodeStatck.push(node);
            node.accessed = true;
            //切换到子根节点的右子树遍历
            node = node.right;
            
        }
    }
    
}
要点
  1. 二叉树的子树依然是二叉树,二叉树的结构是个递归结构。
  2. 因此不管是先序,中序,后序,是遍历的二叉树的子树先后不同,只要存在node代表的树不为Null,那么遍历就不会结束。
  3. 每一颗树都应该看成一颗独立的树,二叉树遍历的本质就是对每一颗子树压栈和出栈的遍历过程。
  4. 任意一点树节点,都需要遍历左子树,和右子树
  5. 后序遍历唯一和其他遍历不同的是需要重新保存一下根节点,待右子树遍历完毕之后,重新访问。

链表反转

public class List{
    Node head;
}
public class Node{
        Node next;
        Object value;
}

链表本身是一个递归结构,每一个next的节点都可以作为一个新的链的开始,也就是作为一个子链。

所以链表反转的步骤即:

  1. 保存头节点,获取子节点作为子链表开始
  2. 保存子节点的下一个节点的子链,同时把当前子节点指向头节点
  3. 同时把当前子节点设置未头节点,循环2直到子节点为null结束
public void revert(List list){
    if(list == null){
        return;
    }
    //获取头结点
    Node head = list.head;
    if(head == null){
        return;
    }
    //获取头结点的下一个节点
    Node next = head.next;
    // 清除头节点的next节点
    head.next = null;
    //如果存在下一个节点,循环遍历
    while(next != null){
        //保存下下个节点
        Node tmp = next.next;
        //下一个节点执行head节点
        next.next = head;
        //下一个节点置为head节点
        head = next;
        //检查下下一个节点
        next = tmp;
    }   
}

查找子串

LRU算法

LRU算法,主要是针对get和put操作,更换容器的元素位置,把get和put操作使用的元素放置在容器的最头部

双向链表对比单链表

双向链表的删除和添加效率是O(1)比单链表效率高

设计模式

分类

  • 创建型模式

    创建型模式关注点是如何创建对象,特点是对象的创建和使用分离

  • 结构型模式

    结构性模式关注点是如何将类或者对象组合成一个更大的结构,特点是职责的增减

  • 行为型模式

    行为模式关注点是如何将类或者对象共同协作完成单个类或者对象无法完成的任务,特点是职责的分配,分配行为

创建型模式

单例模式

该类只有一个实例,且该类能自行创建这个实例的模式

原型模式

使用已经创建的实例作为原型,通过复制该对象来创建一个和原型相同或者类似的新对象

工厂方法

定义一个创建产品对象的工厂接口,将产品对象的实际创建工作延迟到具体子工厂类中

抽象工厂

提供创建一组相关或相互依赖对象的接口,而无需指定创建的产品的具体类型

构造者模式

对构造过程和显示分离,使同一种构造过程可以产生不同的构造表现

结构性模式

代理模式

对某个对象提供一个代理以控制该对象的访问。对于访问对象不适合或者不能直接饮用目标对象,代理对象作为访问对象和目标对象的中介。

适配器模式

将一个类的接口转换成客户希望的另外一个接口,使得原来由于接口不兼容而不能一起功能的类可以一起工作。

桥接模式

将抽象和实现分离,使抽象和实现可以独立变化。

通过组合关系代替继承关系,从而降低了抽象和实现这两个可变维度的耦合度。

本质上是通过抽象的框架对具体的实现类对象进行组合对象,同时抽象的框架可以变化,具体的实现类对象也可以变化

装饰模式

不改变现有对象结构的情况下,动态的给该对象增加一些职责(即增加其额外的功能)的模式

外观模式

通过为多个复杂的子系统提供一致的接口,从而使这些子系统更加容易被访问的模式。

该模式对外有一个统一的接口,外部应用程序不用关心内部子系统的具体的细节。

降低系统访问的复杂性。

享元模式

运用共享技术有效的支持大量细粒度对象的复用。

涉及到享元工厂共享状态非共享状态

组合模式

部分-整体模式,将对象组合成树状的层次结构的模式,使用户对单个对象和组合对象具有一致的访问性

结构模式中的对比

装饰模式 && 代理模式

  • 代理模式屏蔽了实际被代理对象的细节,那也就意味着,被代理对象实际是在编译期间就确认了。

    而装饰模式针对被装饰者是在运行时构造装饰对象的时候参与的,那也就意味着装饰者可以修改不同的被装饰对象。(动态代理也是通过构造时传递参数的,这条有点偏颇)

  • 从代理的意图上,代理对象和被代理对象的角色可能是不同的角色,而装饰对象和被装饰对象是相同的角色

  • 对实现上,装饰关注在功能的如何扩展上,而代理关注在功能的如何调用上

  • 装饰模式可以存在递归的装饰,而代理模式一般不会存在递归的代理调用

行为模式

模板模式

定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重新定义该算法的某些特定步骤。

策略模式

该模式定义一系列算法,并将算法封装起来,使他们可以相互替换,且算法的替换不会影响算法的客户。

命令模式

将请求封装成一个对象,使发出请求的责任和执行请求的责任分隔开。

这样两者可以通过命令对象进行沟通,也方便将命令对象储存,传递,调用,增加和管理。

涉及元素命令对象,接收者,调用者

职责链模式

避免请求发送者和多个请求的处理者耦合在一起,将所有的请求处理者连成一条链,可将请求沿着这条链传递,直到有对象处理它为止。

状态模式

状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足单一职责原则

观察者模式

多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

中介者模式

定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变他们之间的交互。中介者模式是迪米特法则的典型应用

迭代器模式

提供一个对象来顺序的访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

访问者模式

访问者不同意味着节点的处理不同

作用于某种数据结构中各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下,可以添加作用于这些元素新的操作,即不同的处理逻辑

设计原则

面向对象原则

  • 单一职责原则

    如果一个类承担的职责过多,等于把职责耦合在一起。

    一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。

    这种耦合性会导致脆弱的设计,即修改一个职责的时候,可能会导致正常职责发生故障。

    角度:复用性,重用性

  • 开闭原则

    对修改关闭,对扩展开放,尽量通过扩展软件实体的行为来实现变化,而不是修改已有的代码来实现。

    角度: 修改旧代码可能引入错误;修改旧代码可能导致多个业务耦合,产生业务依赖;

    可维护性,扩展性

    衍生: 分层设计,模块化,组件化

  • 里氏替换原则

    要求继承是严格的is-a关系,所有使用基类的地方必须透明的使用其子类对象。

  • 依赖倒置原则

    程序依赖于抽象接口,不要依赖于具体实现。

    上层模块不应该依赖底层模块,他们都应该依赖于抽象。

    降低耦合度的要求。

  • 接口分离原则

    一个类对另一个类的依赖应该建立在最小的接口上。

    一个接口代表一个角色,不应当将不同的角色都交给一个接口,没有关系的接口合并在一起,这是对觉得和接口的污染。

质量

开发质量

  • 易理解
  • 重用性
  • 易扩展
  • 易维护
  • 可测试

运行质量

  • 正确性
  • 性能
  • 鲁棒性(容错性)
  • 安全性
  • 易操作

常用方法

依赖反转

  • 依赖反转用来减弱对象间的耦合问题
  • 依赖反转的实现有依赖注入依赖查询
  • 依赖反转将创建对象实例的控制权从代码控制剥离到IOC容器控制,侧重于原理

依赖注入

  • 依赖注入分为设值方法注入构造注入
  • 创建对象实例时,为这个对象注入属性值或者其他对象实例(类似AOP的类型间声明),侧重于实现

设计工具

UML图

鲁棒图

检查用例规约是否正确和完善

场景-目标-决策

功能树(功能组)

架构

组件化

组件化优势

  • 业务解耦,各个业务专注于自身业务的实现,而不需要关心别的模块业务,减少业务之间耦合和污染
  • 业务解耦的特性,利于开发的分工,开发小组或者开发人员可以独立开发自己的特定模块
  • 面向交付,组件化的模块独立性,具有自然的复用性,很方便的进行配置化交付
  • 模块的独立性,很方便的进行独立的调试和测试

主流框架

LeakCanary

  • 添加Activity或者Fragment监听
  • 监听组件的生命周期
  • 在生命周期中监听Refrence处理通知
  • 在RefrenceQueue中对监听的组件进行引用判断
  • 如果没有回收,尝试GC,GC之后依然没有回收,认为有内存泄漏可能性
  • 调用Debug打印heap prof分析数据
  • 对heap prof进行分析,调用显示服务进行显示

Flutter

路由框架

  • 一对一的类映射关系理解比较容易,特别是页面跳转,使用起来也很方便
  • 组件之间的服务调用时,调用方需要持有接口类,需要将接口定义下沉到base层,面向接口编程,遵循依赖倒置原则
  • 路由本质是类查找,需要通信的组件必须打包在同一个app内部才能获取到

缺点

  • 路径比较多的时候,如何管理

事件总线

事件总线的本质是转发调用请求

组件总线只负责通信,即转发调用请求和返回执行结果。

不需要下沉接口,面向通信协议编程

优点

  • 解耦,消息的发送者和消息的接收者之前解耦
  • 提供一对多,多对对通信

缺点

  • 消息难以溯源
  • 消息发送比较随意,没有约束

美团modular-event-bus

  • 根据定义的事件类型通过APT处理生成接口类
  • 通过接口类调用动态代理实现事件总线管理逻辑

优化点在于:

  • 通过对事件类型的APT处理,规范了模块对外的事件定义(事件类型的定义本身基于模块+事件名,因此很方便溯源)
  • 链式的调用方式,也是现在大部分框架比较常用的方式,实现业务逻辑的内聚性,相关的代码不会散落各处

缺点:

  • 因为通过APT的处理,规范了模块对外的事件定义,本质上也相当于需要依赖于接口,模块调用产生了接口依赖,解耦性受到影响

ASM框架

AOP 的利器:ASM 3.0 介绍

CodeModel & JavaPoet

用于生成java文件的框架库

AOP框架

图片框架

网络框架

ORM优缺点

产品方面

小程序

小程序是一种全新的连接用户和服务的方式

从各家小程序的开发路径来看,都是基于用户(客户)的正向反馈,进行产品的增量迭代

2A3R定义:AARRR是获取用户(Acquisition)、提高活跃度(Activation)、提高留存率(Retention)、获取收入(Revenue)、自传播(Refer),这个五个单词的缩写,对应这一个产品生命周期中的5个重要环节。

关键词:

  • 轻量化

  • 服务直达,触手可得

  • 降低开发门槛(提供第三方平台托管,插件,解决方案,模版小程序)

  • 充实能力

  • 丰富场景,有用户的地方即是场景(线上线下场景,数据能力的精细化运营,搜索,语音服务)

  • 管控(平台审核可控,触达用户方式可控)

VS微信

微信社交的生态,微信小程序具有天然的分享属性,这个对比厂商的快应用来说是一个天然的优势

功能设计的影响

拉新
账号体系(登录/注册)

简洁高效的登录/注册机制对于用户的获取起来至关重要的影响

活跃 && 留存 && 自传播
  • 桌面图标
  • 模块消息唤醒
  • 桌面负一屏卡片
  • 搜索
  • 短信联系人
  • LBS推送
  • 扫一扫
  • 分享裂变 (公众号跳转,社交关系分享,自传播)
精细运营(获取收入能力)
  • 数据上报能力
  • 用户画像分析
  • 业务数据分析
  • 推广监测

VS React Native && Weex

RN和Weex是跨平台应用的开发框架,从而使得Web人员基于前端技术栈可以开发移动应用

而小程序是平台,本质上提供新的交互方式的能力实现方案,是一个标准化的产品化平台产品

RN Weex跨平台开发框架可以理解是小程序的基础,小程序本身除了提供跨平台的开发框架之后,还需要负责

  • 提供多个应用运行的隔离环境

  • 提供多个应用的权限管理机制

  • 提供统一的资源管理平台

  • 提供基于特定平台的JSAPI和场景化的服务

  • 提供开发者的技术和工具支持

  • 不提供自定义的扩展能力,而是统一提供标准化的接口和组件(标准化)

小程序提供了场景化的服务能力(运营能力)

  • 社交关系的分享

  • 内部产品的相互引流能力

  • 数据化的精细运营

  • 模板化消息推送能力

RN Weex 特点

  • 语法上和JS更亲近
  • 提供基于开发者的自定义扩展能力(定制化)

VS Javascript

使用javascript的原因是因为动态语言(脚本语言)的特征,从而可以实现动态化加载。

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