-
MVVM双向数据绑定原理
-
从视图到模型
通过change事件监听视图数据的变化,改变data中的值,实现模型改变,这个比较好理解 -
从模型到视图
重点我们需要知道模型改变会如何作用于视图
1.数据劫持
通过Object.defineProperty()为data添加get()和set()方法,监听data的变化
2.建立好视图和模型的对应关系
3.发布者-订阅者模式
把对应关系放在数组中,若data变化之后,找到那一对对应关系,然后把视图中的值更改为最新data中的值
-
Java线程池实现原理详解

其实java线程池的实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。
线程池的使用,可以更规范的管理和创建销毁线程,也可以更多样化的去使用线程,减小我们的系统开支。
简要描述如何自己写ARouter路由框架
1.先从注解模块开始,我们先定义注册路由地址的注解
2.继承AbstractProcesser自定义注解处理器解析注解。
3.根据拿到的注解外部类的信息编译时自动生成代码,代码的作用是将注解的外部类的路由信息跟路径保存在一个map中。
4.跳转时通过传入的path路径根据map中匹配到的Activity或Fragment进行跳转。
-
Android多进程的实现
使用多进程的场景
一般有2种情况,需要使用多进程。
内存不够,扩大内存。
一些业务,希望在单独进程运行。
比如,QQ的一些插件功能(微视),希望在一个独立进程运行,当它遇到崩溃时,不会影响QQ退出(毕竟进程不同)
<service
android:name=".RemoteService"
android:process=":remote">
</service>
<activity
android:name=".RemoteActivity"
android:process="com.chenxf.ipc.remote">
</activity>
有两种声明方式,一个加冒号,一个完整的名字,区别如下:
:remote: 以冒号开头是一种简写,系统会在当前进程名前附件当前包名,完整的进程名为:com.chenxf.ipc:remote,同时以冒号开头的进程属于当前应用的私有进程,其它应用的组件不能和它跑在同一进程。
com.chenxf.ipc.remote:这是完整的命名方式,不会附加包名,其它应用如果和该进程的ShareUID、签名相同,则可以和它跑在同一个进程,实现数据共享。
多进程的优缺点
- 优点
- 增加内存。
- 业务隔离。一些子业务,放子进程,如果崩溃了,不会影响主app退出。
- 缺点
- 静态成员和单例模式失效
- 线程同步机制失效
- SharedPreferences 可靠性降低
- Application 被多次创建
参考: https://blog.csdn.net/newchenxf/article/details/90370042
如何让两个线程循环交替打印
使用Object的wait和notify实现
public class StrangePrinter2 {
Object odd = new Object(); // 奇数条件锁
Object even = new Object(); // 偶数条件锁
private int max;
private AtomicInteger status = new AtomicInteger(1); // AtomicInteger保证可见性,也可以用volatile
public StrangePrinter2(int max) {
this.max = max;
}
public static void main(String[] args) {
StrangePrinter2 strangePrinter = new StrangePrinter2(100);
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(strangePrinter.new MyPrinter("偶数Printer", 0));
executorService.submit(strangePrinter.new MyPrinter("奇数Printer", 1));
executorService.shutdown();
}
class MyPrinter implements Runnable {
private String name;
private int type; // 打印的类型,0:代表打印奇数,1:代表打印偶数
public MyPrinter(String name, int type) {
this.name = name;
this.type = type;
}
@Override
public void run() {
if (type == 1) {
while (status.get() <= max) { // 打印奇数
if (status.get() % 2 == 0) { // 如果当前为偶数,则等待
synchronized (odd) {
try {
odd.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} else {
System.out.println(name + " - " + status.getAndIncrement()); // 打印奇数
synchronized (even) { // 通知偶数打印线程
even.notify();
}
}
}
} else {
while (status.get() <= max) { // 打印偶数
if (status.get() % 2 != 0) { // 如果当前为奇数,则等待
synchronized (even) {
try {
even.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} else {
System.out.println(name + " - " + status.getAndIncrement()); // 打印偶数
synchronized (odd) { // 通知奇数打印线程
odd.notify();
}
}
}
}
}
}
}
参考: https://www.jianshu.com/p/2286499b4774
怎么中止一个线程,Thread.Interupt一定有效吗
中断线程最好的,最受推荐的方式是,使用共享变量(shared variable)发出信号,告诉线程必须停止正在运行的任务。线程必须周期性的核查这一变量(尤其在冗余操作期间),然后有秩序地中止任务。
Thread.interrupt()方法不会中断一个正在运行的线程。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。
如何理解Java字节码结构
要运行一段Java源码,必须先将源码转换为class文件,class文件就是编译器编译之后供虚拟机解释执行的二进制字节码文件,可以通过IDE工具或者命令行去将源码编译成class文件。
比如如下字节码,里面是一堆的16进制字节。

详情参考:https://blog.csdn.net/u011810352/article/details/80316870
https://www.jianshu.com/p/fa53b4169df9
setOnTouchListener,onClickListener和onTouchEvent的关系
(1)dispatchTouchEvent(MotionEvent event)返回true,表示本次事件被消耗,然后会有新事件传入。若返回false则不会有新事件传入。
(2)mOnTouchListener.onTouch方法返回的是true,onTouchEvent将不被执行。只有前者返回false,后者才会执行。
(3)只要onTouchEvent方法中的DOWN与UP事件都执行了,就会执行setOnClickListener中的onClick或者回调View.onClick,只是后者优先执行。
(4)总体优先级 setTouchListener > onTouchEvent > onClick > setClickListener
-
float和double类型为什么会出现精度丢失的情况
首先得从计算机本身去讨论这个问题。我们知道,计算机并不能识别除了二进制数据以外的任何数据。无论我们使用何种编程语言,在何种编译环境下工作,都要先 把源程序翻译成二进制的机器码后才能被计算机识别。以上面提到的情况为例,我们源程序里的2.4是十进制的,计算机不能直接识别,要先编译成二进制。但问 题来了,2.4的二进制表示并非是精确的2.4,反而最为接近的二进制表示是2.3999999999999999。原因在于浮点数由两部分组成:指数和尾数,这点如果知道怎样进行浮点数的二进制与十进制转换,应该是不难理解的。如果在这个转换的过程中,浮点数参与了计算,那么转换的过程就会变得不可预 知,并且变得不可逆。我们有理由相信,就是在这个过程中,发生了精度的丢失。而至于为什么有些浮点计算会得到准确的结果,应该也是碰巧那个计算的二进制与 十进制之间能够准确转换。而当输出单个浮点型数据的时候,可以正确输出,如
double d = 2.4;
System.out.println(d);
输出的是2.4,而不是2.3999999999999999。也就是说,不进行浮点计算的时候,在十进制里浮点数能正确显示。这更印证了我以上的想法,即如果浮点数参与了计算,那么浮点数二进制与十进制间的转换过程就会变得不可预知,并且变得不可逆。
解决方案:
BigDecimal其中一个构造函数以双精度浮点数作为输入,另一个以整数和换算因子作为输入,还有一个以小数的 String 表示作为输入。要小心使用 BigDecimal(double) 构造函数,因为如果不了解它,会在计算过程中产生舍入误差。请使用基于整数或 String 的构造函数。
-
NIO比IO优秀在哪里
NIO的N有两种解释,一种是Nonblock——非阻塞。还有一种解释是New,这种解释就非常通俗易懂,新的IO。NonBlock其实是对它的特性进行描述,NonBlock对应的就是Block。传统的IO其实就是一种阻塞的IO,大家在使用传统IO的时候应该都注意到了,在流的读写过程中,当前线程是会被挂起的,最典型的应用就是在Socket编程中,Socket一旦建立起InputStream或者OutputStream这种流的关系以后,如果另一方没有传递过来数据,或者我这边数据没有写完,那么整个线程的资源都会被占用,下面的所有内容都不会被执行,直到我显式地去close这个流,或者说有一端出现了IOException。那么这个NIO它提出的是一种非阻塞的IO,也就是说我们可以在一种不需要阻塞线程的情况下去读写数据,而达到一些更高效的IO操作。
NIO看似有很多的功能点,其实核心组件只有三个——channel、buffer和selector。NIO的框架中没有输入流和输出流的概念,它们统称为channel。它也分为读和写,但是读和写都是基于channel而言的,它不再进一步分为两个对象,而是既能读也能写。
阻塞IO和非阻塞IO
Java IO流都是阻塞的,这意味着,当一条线程执行read()或者write()方法时,这条线程会一直阻塞直到读取到了一些数据或者要写出去的数据已经全部写出,在这期间这条线程不能做任何其他的事情。
java NIO的非阻塞模式(Java NIO有阻塞模式和非阻塞模式,阻塞模式的NIO除了使用Buffer存储数据外和IO基本没有区别)允许一条线程从channel中读取数据,通过返回值来判断buffer中是否有数据,如果没有数据,NIO不会阻塞,因为不阻塞这条线程就可以去做其他的事情,过一段时间再回来判断一下有没有数据。NIO的写也是一样的,一条线程将buffer中的数据写入channel,它不会等待数据全部写完才会返回,而是调用完write()方法就会继续向下执行。
-
如何实现浏览器的前进和后退功能?
我们使用两个栈,X和Y,首次浏览的页面,依次压入栈X。当点击回退时,依次从X栈中取出数据,并依次放入Y栈。点击前进时,从Y栈中取出数据,并依次放入X栈。当X栈中没有数据时,就说明不能再回退了;当Y中没有数据时,就说明不能再前进了。
-
什么是双端队列 & 阻塞队列?
ArrayDeque 是基于数组实现的可动态扩容的双端队列,也就是说你可以在队列的头和尾同时插入和弹出元素。当元素数量超过数组初始化长度时,则需要扩容和迁移数据。

由于阻塞队列本身是线程安全的,队列可以安全地从一个线程向另外一个线程传递数据,所以我们的生产者/消费者直接使用线程安全的队列就可以,而不需要自己去考虑更多的线程安全问题。这也就意味着,考虑锁等线程安全问题的重任从 你 转移到了 队列 上,降低了我们开发的难度和工作量。
-
apt 编译时注解处理器
1.什么是APT?
APT即为Annotation Processing Tool,它是javac的一个工具,中文意思为编译时注解处理器。APT可以用来在编译时扫描和处理注解。通过APT可以获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。注意,获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。APT的核心是AbstractProcessor类,关于AbstractProcessor类后面会做详细说明。
2.哪里用到了APT?
APT技术被广泛的运用在Java框架中,包括Android项以及Java后台项目,除了上面我们提到的ButterKnife之外,像EventBus 、Dagger2以及阿里的ARouter路由框架等都运用到APT技术,因此要想了解以、探究这些第三方框架的实现原理,APT就是我们必须要掌握的。
参考: https://blog.csdn.net/qq_20521573/article/details/82321755
-
为什么要重写equals和hashCode?
因为这两个方法都跟对象的比较有关,所以如果在程序中要做对象比较,那大概率要重写这两个方法了。因为equals默认的比较逻辑就是对象的地址进行比较,两个对象内存地址肯定不同,所以无论如何两个对象通过eqals比较肯定返回false。
但在实际编程中,我们经常会遇到去重,或者将对象放到有序集合中,或者将对象存入无重复的集合中,这时如果没有重写equals和hashCode,则无法达到需求。
举个例子,系统中同时存在两个对象,对象A和对象B,其姓名和身份证号一模一样。此时,在系统内存中是两个对象,但其内容一致分明是一个人同时产生了两条重复信息。如果使用默认的equals方法比较,则这两个对象永远不相等,永远不能比出来是一条相同的重复信息。所以,要重写equals和hashCode方法来达到以上需求效果。
-
canvas可以画Bitmap么?
Canvas的drawBitmap有两个构造方法
(1) public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
第一个参数为要绘制的bitmap对象,第二个参数为图片左上角的x坐标值,第三个参数为图片左上角的y坐标的值,第三个参数为Paint对象。
(2) public void drawBitmap(Bitmap bitmap, Rect src, RectF dst,Paint paint)
第一个参数为要绘制的bitmap对象,第二个参数为要绘制的Bitmap对象的矩形区域,第三个参数为要将bitmap绘制在屏幕的什么地方,第四个参数为Paint对象。
-
为什么 Activity.finish() 之后 10s 才 onDestroy ?
在新跳转的Activity完成最终的界面绘制和显示之后,有这么一句代码 Looper.myQueue().addIdleHandler(new Idler()) 。IdleHandler 不知道大家是否熟悉,它提供了一种机制,当主线程消息队列空闲时,会执行 IdleHandler 的回调方法。Activity 的 onStop/onDestroy 是依赖 IdleHandler 来回调的,正常情况下当主线程空闲时会调用。但是由于某些特殊场景下的问题,导致主线程迟迟无法空闲,onStop/onDestroy 也会迟迟得不到调用。但这并不意味着 Activity 永远得不到回收,系统提供了一个兜底机制,当 onResume 回调 10s 之后,如果仍然没有得到调用,会主动触发。
参考:https://blog.csdn.net/augfun/article/details/110413455
-
什么情况下会发生栈溢出/堆溢出
栈溢出(StackOverflowError)
栈是线程私有的,他的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口灯信息。局部变量表又包含基本数据类型,对象引用类型(局部变量表编译器完成,运行期间不会变化)。
public class JvmTest {
private int i = 0;
public void a(){
System.out.println(i++);
a();
}
public static void main(String[] args) {
JvmTest j = new JvmTest();
j.a();
}
}
堆溢出(OutOfMemoryError:java heap space)
heap space表示堆空间,堆中主要存储的是对象。如果不断的new对象则会导致堆中的空间溢出。
public class JvmTest {
public static void main(String[] args) {
List<String> aList = new ArrayList<String>();
try{
while(true){
aList.add("asdasdasdas");
}
}catch(Throwable e){
System.out.println(aList.size());
e.printStackTrace();
}
}
}
网友评论