本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
更多安卓进阶文章,请看:安卓进阶
RxJava全局观赏
RxJava已经诞生了好多年了,对应变化风云诡谲的移动互联网行业来说,已经属于老框架了。虽然学习成本不低,但是熟练之后真叫人打从心里说声爽。那它到底爽在哪里呢,我个人总结为以下几点:
1.不管复杂的业务也可以用一条链连到底,将每个逻辑操作步骤划分到每一个操作符中 2.线程切换一个操作符搞定,完全省去了繁琐的回调,多次线程切换从代码的角度看流程依然十分直观 3.多条业务流程可以拼接一起,多线程可以合并操作。
简单一句就是:删繁就简三秋树。当然也有人反映不好用,这种也要根据不同的业务技术场景做取舍,如人饮水冷暖自知,见仁见智,这里就不赘述。
RxJava原理流程总览
在为RxJava删繁就简的能力惊叹之余,好奇的我们肯定控制不住探索其原理的欲望。“为通过链式操作符就可以一路走到底?为啥线程可以链式切换呢?等等…”
我最近春节在家,终于有机会可以好好系统探索下RxJava的原理源码,下载了RxJava1.x 的源码仔细斟酌一番,拨开代码的重重迷雾,慢慢的抓住了它简约而不简单的设计原理。
为什么说是简约而不简单呢?简约是因为它 的原理并不复杂,不高深,不简单是因为它能把一切复杂都隐藏在流畅的链式中。
话不多说,进入主题。
经过分析,我觉得用手机包装流水线来形容RxJava总的工作流程还是比较恰当的(大家觉得有不妥的欢迎指出讨论~): 1.先搭建生产流水线 2.启动流水线包装 3.用户一层层拆开包装最后拿到手机(最后的结果)。
这里各个比喻对应的代码对象:
Observable:流水线的某一道工序 OnSubscribe:一道工序中的工人 OnSubscribe的call方法:包装Subscriber subscribe方法:启动流水线 Subscriber:一层包装盒 Subscriber的onNext:用户拆开包装
具体阐述下以上比喻的意思:
1.先搭建生产流水线
其实大部分操作符,都是新建一个Observable对象,然后将上游的Observable对象包装起来,传入一个新的OnSubscribe,比如:
public final Observable map(Func1 func) {
return unsafeCreate(new OnSubscribeMap(this, func));
}
public final Observable filter(Func1 predicate) {
return unsafeCreate(new OnSubscribeFilter(this, predicate));
}
public final Observable lift(final Operator operator) {
return unsafeCreate(new OnSubscribeLift(onSubscribe, operator));
}
public final Observable subscribeOn(Scheduler scheduler, boolean requestOn) {
if (this instanceof ScalarSynchronousObservable) {
return ((ScalarSynchronousObservable)this).scalarScheduleOn(scheduler);
}
return unsafeCreate(new OperatorSubscribeOn(this, scheduler, requestOn));
}
最后都是调用了create方法创建Observable,把当前Observable传入给新的Observable持有,以保持链式 (有点类似链表持有上一个节点的指针)。为什么要这样呢,因为RxJava是的链式是基于代理模式做的,也就是说基于一层一层Observable的包装。
那包装的是什么呢?就是OnSubscribe,那OnSubscribe包装的意义是什么呢?其实就是包装如何包装Subscriber的逻辑。
比如map,传入的是OnSubscribeMap(OnSubscribe的基类),它的call代码如下,
@Override
public void call(final Subscriber o) {
MapSubscriber parent = new MapSubscriber(o, transformer);
o.add(parent);
source.unsafeSubscribe(parent);
}
当map这个操作符创建出来的Observable被调用subscribe被调用的时候,就会该OnSubscribeMap的call方法,可以看代码发现这里创建了一个MapSubscriber对象,然后调用上游的Observable的unsafeSubscribe方法,传入该MapSubscriber对象作为参数。
所以当你开心地用RxJava一个个操作符把链写得老长的时候,里面的逻辑就是不断一层层包装Observable,每个Observable持有一个自己的OnSubscribe,具体类型由对应的操作符确定。
这就是我说的第一个流程搭建流水线 ,总的来说就是从上往下不断创建Observable,并连成链,即后一个Observable持有上游Observable的引用 。
Observable之所以说是流水线的某一道工序,是因为它是这条链最基本的串联元素,而OnSubscribe之所以说是一道工序中的工人,是因为它决定了Subscriber是如何被包装的。
2.启动流水线包装
启动的开关正是链尾的subscribe方法。看下Observable的subscribe方法:
public final Subscription subscribe(Subscriber subscriber) {
return Observable.subscribe(subscriber, this);
}
subscribe(Subscriber subscriber, Observable observable)方法,方法比较长,最重要的就是
RxJavaHooks.onObservableStart(observable, observable.onSubscribe).call(subscriber);
RxJava1.x中,RxJavaHooks.onObservableStart其实没有做什么操作,返回的就是原来的observable对象的onSubscribe,所以这里就是调用observable对象的onSubscribe的call方法,传入subscriber对象。
public interface OnSubscribe extends Action1> {
// cover for generics insanity
}
OnSubscribe是一个继承Action1的接口,Action1是一个只有call方法的接口,所以这里call的逻辑由具体的OnSubscribe对象确定。
还记得还是那个面说的map操作生成的OnSubscribeMap对象的call方法逻辑么?它的call方法中创建了一个MapSubscriber对象,然后调用上游的Observable的unsafeSubscribe方法,并传入该MapSubscriber对象作为参数。
这里要注意的是,在创建了一个MapSubscriber对象的时候,会传入当前Observable调用的subscribe方法的参数Subscriber对象,保存该对象的引用actual ,以保持链式:
public MapSubscriber(Subscriber actual, Func1 mapper) {
this.actual = actual;
this.mapper = mapper;
}
所以假如链尾的Observable是map操作符创建的,则subscribe执行的时候,会执行Observable对象中的OnSubscribeMap对象的call方法,生成一个MapSubscriber对象并持有我们代码中链的最后传入的 Subscriber对象,然后让上游的Observable对象调用subscribe方法,并传入这个MapSubscriber对象。所以这里就是从下往上递归调用Observable对象的subscribe方法,从而生成一条Subscriber对象的链(也可以理解为一层层包装)。
在这里,经过subscribe方法的启动,已经开始加工包装,最后生产出了一条Subscriber对象的链,即我们的手机包装盒。
3.用户拆开手机包装盒
这个流程,可以用杨宗纬的《洋葱》一段经典歌词来阐述:“一层一层一层地剥开我的心~~”
上一步操作从下到上生成了Subscriber对象的链,链的尾部就是最上游的Observable中的:
Observable.create(object : OnSubscribe {
override fun call(t: Subscriber) {
t.onStart()
t.onNext(1)
}
})
这里第二行的的t: Subscriber,现在第四行调用了 t.onNext(1),又以之前的map操作符生成的MapSubscriber对象为例:
public void onNext(T t) {
R result;
try {
result = mapper.call(t);
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
unsubscribe();
onError(OnErrorThrowable.addValueAsLastCause(ex, t));
return;
}
actual.onNext(result);
}
这里先使用mapper.call(t)对传进去的Subscriber对象进行了变换,即map操作中指定的变换方法,这个下一节再谈。先注意这里最后调用了 actual.onNext(result);,而actual就是Subscriber链的下一个Subscriber对象,而除了map以外,大部分的Subscriber对象的onNext方法也有这样的逻辑 ,所以可以知道,这里Subscriber链在递归调用,也可以看作一层一层一层地打开,就仿佛是拆开手机包装盒。
流程总结
看前面的叙述各位可能还是有点雾里看花,总结一下:前面三个小节各对应一个流程,从RxJava调用代码来说,就是先从上到下把各个变换的Observable连成链(拼装流水线) ,然后在最后subscribe的时候,又从下到上通过每个Observable的OnSubscribe从最下的Subscriber对象开始连成链(流水线开始工作包装Subscriber),直到顶端,当顶端的Subscriber对象调用了onNext方法的时候,又从上往下调用Subscriber链的onNext(用户一层层拆开包装盒),里面执行了每个操作的变换逻辑 。
举个例子进一步说明以上流程:
Observable.create(object : OnSubscribe {
override fun call(t: Subscriber) {
t.onStart()
t.onNext(1)
}
})
.map(object : Func1 {
override fun call(t: Int): String {
Log.d(TAG, Thread.currentThread().name)
return t.toString()
}
})
.map(object : Func1 {
override fun call(t: String): Book {
Log.d(TAG, Thread.currentThread().name)
return Book(t)
}
})
.subscribe(object : Subscriber() {
override fun onStart() {
}
override fun onNext(t: Book) {
Log.d(TAG, Thread.currentThread().name)
Log.d(TAG, t.toString())
}
override fun onComplete() {
}
override fun onError(t: Throwable) {
Log.d(TAG, t.message)
}
})
为了简单,这里只使用了map操作符。
以下是一个简单的流程图:
RxJava操作符原理解析
如果上面的总流程分析能理解的话,那么下面的操作符的理解就不难了。
普通的变换操作符
这里举map的例子。这里的变换处于从上往下递归执行Subscriber链onNext阶段(用户拆手机包装盒) 前面提到map中生成的MapSubscriber对象的onNext方法:
public void onNext(T t) {
R result;
try {
result = mapper.call(t);
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
unsubscribe();
onError(OnErrorThrowable.addValueAsLastCause(ex, t));
return;
}
//调用下游的Subscriber的onNext方法
actual.onNext(result);
}
注意到第四行代码 result = mapper.call(t);,这里的mapper其实就是我们写的map操作的变换方法:
.map(object : Func1 {
override fun call(t: String): Book {
Log.d(TAG, Thread.currentThread().name)
return Book(t)
}
})
这里面的Func1回调接口,所以经过这样call方法的调用,就实现了map的操作变换,然后执行 actual.onNext(result);,即将变换后的结果交给下游的Subscriber的onNext方法。
如果理解了上面的流程图,是不是理解map易如反掌呢?
线程切换操作符
线程切换主要两个操作符:subscribeOn和observeOn
线程切换是我觉得RxJava最牛逼的地方,不过了解了原理之后觉得也不复杂高深,主要还是在上面的总流程中的对应节点使用了常见的切换线程方式。
subscribeOn
作用: 将subscribe Observer的执行放在对应的线程。
subscribeOn最终会执行到:
public final Observable subscribeOn(Scheduler scheduler, boolean requestOn) {
if (this instanceof ScalarSynchronousObservable) {
return ((ScalarSynchronousObservable)this).scalarScheduleOn(scheduler);
}
return unsafeCreate(new OperatorSubscribeOn(this, scheduler, requestOn));
}
注意最后执行了:
return unsafeCreate(new OperatorSubscribeOn(this, scheduler, requestOn));
根据前面的分析,这里就是创建新的Observable对象,并传入一个OnSubscribe实例对象,这里是OperatorSubscribeOn对象。
根据上面的说明,这里要看call方法:
public void call(final Subscriber subscriber) {
final Worker inner = scheduler.createWorker();
SubscribeOnSubscriber parent = new SubscribeOnSubscriber(subscriber, requestOn, inner, source);
subscriber.add(parent);
subscriber.add(inner);
inner.schedule(parent);
}
可以看到第四行SubscribeOnSubscriber parent = new SubscribeOnSubscriber(subscriber, requestOn, inner, source);,所以它创建的就是Subscriber对象就是SubscribeOnSubscriber,注意这里第二行final Worker inner = scheduler.createWorker();和最后一行 inner.schedule(parent);,
这里的方法调用栈比较长就不赘述,直接说下,这里worker里面就是执行线程切换的,里面封装线程池或者Handler, 通过schedule方法就可以将SubscribeOnSubscriber包装成一个Runnable放入线程池中执行,执行的方法是SubscribeOnSubscriber的call方法。
而SubscribeOnSubscriber的call方法:
public void call() {
Observable src = source;
source = null;
t = Thread.currentThread();
src.unsafeSubscribe(this);
}
和其他的Subscriber一样,也是传入上游Observable的subscribe方法中。
回忆上面讲的总流程,在第二个流程从下往上包装Subscriber链(加工包装)的时候,subscribeOn就是将从它当前这个节点开始将后面的一系列的Subscriber的成链以及从上往下执行各个Subscriber对象的onNext放到指定的线程执行。
常见的一种描述subscribeOn作用的说法:“将该subscribeOn语句的上游放在对应的线程中”,其实并不准确 ,因为如果只使用了subscribeOn而没有使用observeOn的话,整条链的变换过程都会执行在subscribeOn指定的线程的。RxJava官方的解释才是准确的 :
Asynchronously subscribes Observers to this Observable on the specified {@link Scheduler}.
在刚才的示例代码中加入subscribeOn:
Observable.create(object : OnSubscribe {
override fun call(t: Subscriber) {
t.onStart()
t.onNext(1)
}
})
.map(object : Func1 {
override fun call(t: Int): String {
Log.d(TAG, Thread.currentThread().name)
return t.toString()
}
})
//这里切换线程
.subscribeOn(Schedulers.io())
.map(object : Func1 {
override fun call(t: String): Book {
Log.d(TAG, Thread.currentThread().name)
return Book(t)
}
})
.subscribe(object : Subscriber() {
override fun onStart() {
}
override fun onNext(t: Book) {
Log.d(TAG, Thread.currentThread().name)
Log.d(TAG, t.toString())
}
override fun onComplete() {
}
override fun onError(t: Throwable) {
Log.d(TAG, t.message)
}
})
用刚才的流程图来表示的话,subscribeOn切换线程差不多是这样子的:
红色部分为放入指定线程的逻辑 。
observeOn
observeOn最终会走到:
public final Observable observeOn(Scheduler scheduler, boolean delayError, int bufferSize) {
if (this instanceof ScalarSynchronousObservable) {
return ((ScalarSynchronousObservable)this).scalarScheduleOn(scheduler);
}
return lift(new OperatorObserveOn(scheduler, delayError, bufferSize));
}
这里使用了lift方法:
public final Observable lift(final Operator operator) {
return unsafeCreate(new OnSubscribeLift(onSubscribe, operator));
}
这里和map本质还是一样的,创建一个新的额Observable并传入一个新的OnSubscribe对象(OnSubscribeLift),那主要就是要看这个OnSubscribeLift的call做了什么:
Subscriber st = RxJavaHooks.onObservableLift(operator).call(o);
try {
// new Subscriber created and being subscribed with so 'onStart' it
st.onStart();
parent.call(st);
call最主要的就是这几行,和map基本差不多,就是使用operator对传入从下游传入的Subscribeder进行转换,所以关键看OperatorObserveOn的call做了什么转换:
ObserveOnSubscriber parent = new ObserveOnSubscriber(scheduler, child, delayError, bufferSize);
parent.init();
return parent;
主要看这里,OperatorObserveOn中创建了一个ObserveOnSubscriber,最后返回。
注意这里和OperatorSubscribeOn的不同,OperatorSubscribeOn是在call方法就把新建的Subscriber对象包装为Runnbale放入线程池中执行,将上游Observable对他的subscribe调用放到了指定线程 。
而OperatorObserveOn是将ObserveOnSubscriber对象作为参数传入了上游的OnSubscribe的call方法,然后整个从下往上的包装Subscribe还是在原来的线程中执行,那这里关键点就是看ObserveOnSubscriber的onNext做了什么操作:
if (!queue.offer(NotificationLite.next(t))) {
onError(new MissingBackpressureException());
return;
}
schedule();
重点就是这几行。第一行的t是onNext返回的Subscriber对象,NotificationLite.next这里正常情况下返回的还是t,而queue是一个队列,这里将t入列,然后执行了schedule(),该方法是将当前的ObserveOnSubscriber对象包装为Runnable,放入线程池中 ,然后在指定线程执行其call方法 主要代码如下:
...
final Queue q = this.queue;
final Subscriber localChild = this.child;
...
for (;;) {
...
Object v = q.poll();
boolean empty = v == null;
if (checkTerminated(done, empty, localChild, q)) {
return;
}
if (empty) {
break;
}
localChild.onNext(NotificationLite.getValue(v));
...
}
可以看到这里就是循环从队列中取出元素,然后再传入下游的Subscriber的onNext方法。
总结observeOn的操作:
在从下往上包装Subscriber链的时候(用户拆开手机包装盒),在调用observeOn的地方插入一个ObserveOnSubscriber对象,该对象可以在之后从上往下递归调用Subscriber链的时候,将该ObserveOnSubscriber对象之后的所有的onNext方法放到指定线程中执行。
现在在实例中添加observeOn:
Observable.create(object : OnSubscribe {
override fun call(t: Subscriber) {
t.onStart()
t.onNext(1)
}
})
.map(object : Func1 {
override fun call(t: Int): String {
Log.d(TAG, Thread.currentThread().name)
return t.toString()
}
})
.subscribeOn(Schedulers.io())
//将当前调用之后的onNext放入指定线程
.observeOn(Schedulers.main())
.map(object : Func1 {
override fun call(t: String): Book {
Log.d(TAG, Thread.currentThread().name)
return Book(t)
}
})
.subscribe(object : Subscriber() {
override fun onStart() {
}
override fun onNext(t: Book) {
Log.d(TAG, Thread.currentThread().name)
Log.d(TAG, t.toString())
}
override fun onComplete() {
}
override fun onError(t: Throwable) {
Log.d(TAG, t.message)
}
})
用流程图来表示:
红色为subscribeOn指定线程执行部分,绿色为observeOn指定线程执行部分。
动手实战
纸上得来终觉浅,绝知此事要躬行。为了加强理解,我根据RxJava源码自己写了一个demo级别的RxJava源码,使用Kotlin写的,简称RxKotlin(当然现在确实已经有知名的RxKotlin开源库了~) ,流程和RxJava一致,并且有简单的操作符(当然细节就不一样啦),各个类命名也和RxJava保持一致。如果各位觉得RxJava的源码不好理解,也可以参考我的demo。
为什么要看RxJava的源码呢?除了满足自己的探索欲望之外,通过学习RxJava源码,就可以学习到如何运用RxJava的设计思想,通过封装代码编写自己的响应式编程框架,以后我们就可以写出自己的RxPay、RxLogin之类的了。
Demo Github地址
彩蛋:
最后留个小习题,通过本文的学习,应该就可以能回答这个问题了:
如果一次RxJava链式调用中多次使用subscribeOn,为什么只有第一个subscribeOn生效呢呢?
原创不易,如果你觉得好,随手点赞,也是对笔者的肯定~
你可能感兴趣的:(Android进阶)
《Android进阶之光》读书笔记
soleil雪寂
读书笔记 # Android进阶之光
文章目录第1章Android新特性1.1.Android5.0新特性1.2.RecyclerView1.1.4.3种Notification1.1.5.Toolbar与Palette1.1.6.Palette1.2.Android6.0新特性1.2.2.运行时权限机制1.3.Android7.0新特性第2章MaterialDesign2.2.DesignSupportLibrary常用控件详解第3
《Android进阶之光》— Android 书籍
王睿丶
Android 永无止境 《Android进阶之光》 Android书籍 Android phoenix 移动开发
文章目录第1章Android新特性1第2章MaterialDesign48第3章View体系与自定义View87第4章多线程编程165第5章网络编程与网络框架204第6章设计模式271第7章事件总线308第8章函数响应式编程333第9章注解与依赖注入框架382第10章应用架构设计422第11章系统架构与MediaPlayer框架460出版年:2017-7简介:《Android进阶之光》是一本And
《android进阶之光》——多线程编程(上)
TAING要一直努力
读书笔记
今天了解了下多线程编程,知识点如下:进程与线程:进程是什么?线程是什么?进程可以看作是程序的实体,是线程的容器,是受操作系统管理的基本运行单元,例如exe文件就是一个进程。线程是进程运行的一些子任务,是操作系统调度的最小单元,各线程拥有自己的计数器,堆栈,局部变量等,也可以访问线程间共享的内存。线程的状态有哪些?新创建,可运行,等待,超时等待,阻塞,终止怎么创建一个线程?-三种方法第一种,MyTr
android进阶之光!Android面试必备的集合源码详解,系列篇
程序员Sunbu
程序员 Android
前言面试:如果不准备充分的面试,完全是浪费时间,更是对自己的不负责。文末会给大家分享下我整理的Android面试专题及答案其中大部分都是大企业面试常问的面试题,可以对照这查漏补缺,当然了,这里所列的肯定不可能覆盖全部方式,不过对大家找工作肯定是有帮助!本月飞机到达上海,到今天第6天了,四家大公司华为,小米,映客,抖音,还有二家中小型公司。有几家已经面了几轮,下周还要面,挂了几家,不过目前已经选择了
Android AMS
shuizhizhiyin
android
Android进阶:一口气读完《Android进阶解密》-掘金AndroidAMS(ActivityManagerService)实现的功能**管理应用程序的生命周期:**启动、停止、暂停、恢复和销毁应用程序。**控制应用程序的启动顺序:**确保应用程序以正确的顺序启动,以避免冲突。**维护应用程序状态:**跟踪应用程序的当前状态(如正在运行、已暂停等)。**管理应用程序权限:**授予和撤销应用程
嵌入式面经111道面试题全解析C/C++可参考
爱打球的程小员许乔丹
嵌入式 面试 c++
高级系列专栏:嵌入式想通关嵌入式面试,请看:《111道嵌入式面试题目录及答案链接》想多掌握几个嵌入式项目,请看:《6个嵌入式项目交流分享(附源码)》安卓(安卓系统开发也要掌握)想通关安卓面试,请看:《150道安卓高频面试题目录及答案链接》想通关安卓系统面试,请看:《140道安卓系统Framework面试题目录及答案链接》想进阶安卓开发,请看:《Android进阶知识体系解析_15大安卓进阶必备知识
Android进阶之路 - ViewPager2 比 ViewPager 强在哪?
远方那座山
Android进阶之路 Android ViewPager2 ViewPager ViewPager1 2区别
我记得前年(2022)面试的时候有被问到ViewPager和ViewPager2有什么区别?当时因为之前工作一直在开发售货机相关的项目,使用的技术要求并不高,所以一直没去了解过ViewPager2~去年的时候正好有相关的功能需求,索性直接用ViewPager2进行了Tip:很多人可能比较关注俩者区别、变更,那么我们结论先行,然后再接着验证结论先行关于它们的区别,我仅从我个人理解的角度来讲(不知不觉
安卓源码分析-Launcher请求到根Activity启动
Ricardo.Lvqf
安卓源码分析
安卓源码分析-Launcher请求到Activity启动本文基于刘望舒大佬著作《Android进阶解密》结合最新源码整理,丰富而成。内部包含我个人的理解,可能有误本文基于安卓源码版本9.0.0_r3文章目录安卓源码分析-Launcher请求到Activity启动Launcher(所谓的桌面)。但我们点击桌面上的图标后,launcher会调用startActivitySafely请求Ams来启动该应
Android进阶 --- ActivityManagerService模块分析
帅气好男人_Jack
android源码
Android进阶—ActivityManagerService模块分析简介作为一名合格的Android开发工程师,我们不仅要会使用四大组件,并且要知晓其背后运作的原理!当我们学会了其背后的运作原理,才能更好的帮助我们进行开发工作,知晓每个组件是如何与系统进行交互、数据传递以及调用逻辑,当遇bug难题,才能更好的对症下药及时解决。本博文是由android8.1.0源码分析后的结果AMS模块重点结构
Android进阶之路 - 通过业务(Activity)栈管理业务流程
远方那座山
Android进阶之路 # 项目开发知识点归纳 Android 业务栈管理 业务流程管理
关于业务栈的管理方式,我在去年刚接触当前项目的时候就想记录一下,但是一直晃晃悠悠拖到了现在,索性在春节前以其收尾也是不错。其实这篇内容在项目中肯定经常用得到,但是关于标题命名我却不知道如何描述…在实际业务中为了形成业务闭环,经常需要对一条完整的业务线进行管理,而承载业务的组件一般都是Activity,所以也可以说是对Activity的管理关于Activity管理的篇章,我早期曾写过类似的一篇And
Android进阶知识:ANR的定位与解决
hudawei996
android
1、前言ANR对于Android开发者来说一定不会陌生,从刚开始学习Android时的一不注意就ANR,到后来知道主线程不能进行耗时操作注意到这点后,程序出现ANR的情况就大大减少了,甚至于消失了。那么真的是只要在主线程做耗时操作就会产生ANR吗?为什么在有时候明明觉得自己没在主线程做耗时操作也出现了ANR呢?一旦出现莫名其妙的ANR,怎么定位导致ANR的产生的位置和解决问题呢?那么接下来就来一个
Android进阶解密①——activity的启动过程
leap_
Activity的启动分为根activity启动和普通activity启动,根activity的启动过程包括了普通activity的启动过程,本文只介绍根activity的启动;根Activity启动的整理流程:Launcher进程请求SystemServer进程的AMSAMS请求用户进程的ApplicationThreadApplicationThread请求ActivityThread,Act
android 多行文本换行,Android进阶——或许是处理“More&click”型多行的TextView换行的最优雅的一种方式...
勃尼兄弟
android 多行文本换行
引言相信很多AndroidAPP开发者在处理TextView换行的时候都曾头痛不已过,尤其是在做复杂布局的时候,适配的时候都踩过不少坑。笔者也踩过,直到在一次查看源码的时候发现了ViewTreeObserver,总算是实现了优雅的格式化多行文本,在使用一个控件的时候抽点时间了解下提供的公共方法,有时候可以避免很多不必要的坑。一、ViewTreeObserver概述ViewTreeObserver顾
Android 进阶之旅 - 终章
HongChengDarren
开发杂谈 android
我们的《Android进阶之旅》到这里就算是完结了,五六年的时间,我的青春,我职业生涯的黄金时期,基本都耗在了这上面。很多东西我都是现学现卖,平时上班也很少有时间能备课,很多地方难免讲得不如意,希望大家能理解,也希望没有浪费掉大家的时间。这期间运气很好,进了大公司,赚了一些钱,和自己心爱的人结了婚,买了房也买了车,没车贷房贷的压力,生活滋润了年纪也大了,懂得东西多了也少了一些纯粹。希望自己依旧能勿
使用单调队列解决 “滑动窗口最大值” 问题
彭旭锐
算法 数据结构
本文已收录到GitHub·AndroidFamily,有Android进阶知识体系,欢迎Star。技术和职场问题,请关注公众号[彭旭锐]私信我提问。前言大家好,我是小彭。在上一篇文章中,我们介绍了单调栈这种特殊的栈结构,单调栈是一种非常适合处理“下一个更大元素问题”的数据结构。今天,分享到单调栈的孪生兄弟——单调队列(MonotonicQueue)。类似地,单调队列也是在队列的基础上增加了单调的性
android应用开发基础学习心得体会
x18275767219
android 学习
目录导语1.学习Java编程语言2.熟悉AndroidStudio3.学习Android开发框架4.实验室和样例代码5.Android数据存储和管理6.Android性能优化7.Android进阶开发导语Android开发是移动应用开发的一个重要领域,也是目前市场需求量较大的技能之一。作为一名Android开发者,你可以通过自己的技能和创造力,为用户提供高质量的应用程序。学习Android开发需要
Android进阶(十)资源和Service的插件化
Android高级工程师
一、系统资源加载1、资源类别res目录下存放的资源文件。编译时会在R文件中生成资源文件的十六进制值。res目录下资源通过Context.getResource方法获取到Resource对象,然后通过getXXX获取资源。assets目录下存放的原始文件,编译时不会被编译。通过AssetManager的open方法获取目录下文件资源,AssetManager来源于Resources类的getAsse
Android进阶之自定义View原理(三)View的绘制流程
kakaxicm
引言前面我们讲到自定义View的测量和布局原理,并举例说明了这两个知识点的具体应用,本篇我们继续从源码入手看看View的绘制流程,与测量和布局流程,View的绘制过程要简单一些,主要流程如下:View绘制流程图.png(一)View的draw流程源码分析:/***作用:根据给定的Canvas自动渲染View(包括其所有子View)。*绘制过程:*1.绘制view背景*2.绘制view内容*3.绘制
从图灵机到量子计算机,计算机可以解决所有问题吗?
彭旭锐
算法
本文已收录到GitHub·AndroidFamily,有Android进阶知识体系,欢迎Star。技术和职场问题,请关注公众号[彭旭锐]进Android面试交流群。前言大家好,我是小彭。今天,我们正式开启一个新专栏——计算机组成原理。计算机组成原理是计算机科学中最基础的理论知识,你越早掌握这些知识,你就能越早享受知识带来的“复利效应”。在构思到写作的过程中,我一直在思考应该以什么内容作为这个专栏的
【Android进阶】flutter-alertdialog
小康
classMyAlertDialogextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'AlertDialog组件示例',home:Scaffold(appBar:AppBar(title:Text('AlertDialog组件示例'),),body:Center(chi
Android进阶之光——设计模式(设计模式的分类、创建型设计模式)
So_ProbuING
设计模式六大原则单一职责原则:就一个类而言,应该仅有一个引起它变化的原因开放封闭原则:类、模块、函数等应该是可以拓展的,在拓展时尽量少修改里氏替换原则:所有引用基类的地方必须能透明地使用其子类对象依赖倒置原则:高层模块不应该依赖底层模块,两者都应该依赖于抽象,抽象不应该依赖于细节,细节应该依赖于抽象迪米特原则:一个软件实体应当尽可能少地与其他实体发生相互左右接口隔离原则:一个类对另一个类的依赖应该
【Android进阶篇】Android中ListPreference的作用和使用方法的详细介绍
孤舟簔笠翁
Android应用进阶篇 android
1,ListPreference的作用ListPreference是Android中的一个Preference子类,用于显示一个可选择的列表,并且可以保存用户所选择的值。它继承自DialogPreference,可以在用户点击时弹出一个对话框,显示可选择的选项,并将用户选择的值保存到SharedPreferences中。使用ListPreference的步骤如下:1,在preferences.xm
【Android进阶篇】Android中PreferenceScreen的作用和详细用法介绍
孤舟簔笠翁
Android应用进阶篇 android
1,PreferenceScreen的作用在Android开发中,PreferenceScreen是一个非常重要的布局控件,主要用于创建设置界面(settingspage)。它可以包含多个Preference子项,如CheckBoxPreference,ListPreference等,用于设置应用程序的各种选项。以下是一些关于PreferenceScreen的详细使用说明:1,创建设置页面:首先,
NDK 系列(5):JNI 从入门到实践,爆肝万字详解!
彭旭锐
请点赞关注,你的支持对我意义重大Hi,我是小彭。本文已收录到GitHub·Android-NoteBook中。这里有Android进阶成长知识体系,有志同道合的朋友,带你建立核心竞争力。前言在Android生态中主要有C/C++、Java、Kotlin三种语言,它们的关系不是替换而是互补。其中,C/C++的语境是算法和高性能,Java的语境是平台无关和内存管理,而Kotlin则融合了多种语言中的优
Android进阶解密③—Hook
leap_
源码的执行是按照一定流程思路进行的,hook就是在源码的执行流程之间插入一步操作,起到拦截,替换的作用;被改变的对象称为hook点,一般将不易发生变化的类作为hook点;常见的hook点有:静态变量单例代理模式:学习hook必须了解代理模式,可以参考我这篇文章:反射和动态代理HookstartActivity首先需要知道startactivity的流程:Android进阶解密①——activity
Android 进阶解密阅读笔记5
jkwen
接上篇Android进阶解密阅读笔记4内容,以下代码是基于API28版本进行的分析,分析思路还是参阅的「Android进阶解密」,不过书上好像有个小错误,所以我就参照着书本做的分析。//ActiveServicesintbindServiceLocked(IApplicationThreadcaller,IBindertoken,Intentservice,StringresolvedType,f
Android进阶(9)| 四大组件的工作过程
yzbkaka
本节目录一.Activity的工作过程1.Activity的创建流程1)startActivity()有好几种重载方式,但是他们最终都会去调用startActivityForResule()方法。2)接着在startActivityForResule()内部中会去调用execStartActivity()方法。3)在execStartActivity()方法中会去调用ActivityManager
Android面试Android进阶(十八)-Retrofit相关
肖义熙
Retrofit是基于OkHttp封装的一个网络请求框架,底层网络请求通信由OkHttp实现。上篇文章讲了一些OkHttp相关的一些东西,没看过的可以去看看OkHttp相关本篇基于v2.9.0版本分析源码及问答,先看看Retrofit的简单使用,基于使用来分析。//1、创建OkHttpClient实例(非必须,内部帮你实例化了一个)valmOkHttpClient=OkHttpClient.Bui
Android 进阶解密阅读笔记10
jkwen
上一篇Android进阶解密阅读笔记9从WindowManager开始梳理了相关类还有关联,这篇来梳理下WindowManagerGlobal的作用。WindowManagerGlobalWindowManagerImpl对象里包含了一个WindowManagerGlobal对象,它是单例实现的,其作用是帮助WindowManagerImpl实现一些功能,例如addView,removeView,
2018-05-07—RecyclerView使用
季白zy
大家好,已经有一段时间没有写博客了,51放了个好假哈哈哈。我在CSDN现在也在写博客,是一些关于Android进阶的一些东西,希望大家还是多去光顾一下,帮我找找哪里有不足。链接:季白的CSDN博客大家还记得我们学过的ListView吧,今天在群里跟朋友们交流了一下,有的人说按照谷歌的尿性,再过几个版本就要彻底舍弃ListView了(是不是真的我不知道,不过就算舍弃,我们也还是得会用是吧哈)。而与之
Spring4.1新特性——综述
jinnianshilongnian
spring 4.1
目录
Spring4.1新特性——综述
Spring4.1新特性——Spring核心部分及其他
Spring4.1新特性——Spring缓存框架增强
Spring4.1新特性——异步调用和事件机制的异常处理
Spring4.1新特性——数据库集成测试脚本初始化
Spring4.1新特性——Spring MVC增强
Spring4.1新特性——页面自动化测试框架Spring MVC T
Schema与数据类型优化
annan211
数据结构 mysql
目前商城的数据库设计真是一塌糊涂,表堆叠让人不忍直视,无脑的架构师,说了也不听。
在数据库设计之初,就应该仔细揣摩可能会有哪些查询,有没有更复杂的查询,而不是仅仅突出
很表面的业务需求,这样做会让你的数据库性能成倍提高,当然,丑陋的架构师是不会这样去考虑问题的。
选择优化的数据类型
1 更小的通常更好
更小的数据类型通常更快,因为他们占用更少的磁盘、内存和cpu缓存,
第一节 HTML概要学习
chenke
html Web css
第一节 HTML概要学习
1. 什么是HTML
HTML是英文Hyper Text Mark-up Language(超文本标记语言)的缩写,它规定了自己的语法规则,用来表示比“文本”更丰富的意义,比如图片,表格,链接等。浏览器(IE,FireFox等)软件知道HTML语言的语法,可以用来查看HTML文档。目前互联网上的绝大部分网页都是使用HTML编写的。
打开记事本 输入一下内
MyEclipse里部分习惯的更改
Array_06
eclipse
继续补充中----------------------
1.更改自己合适快捷键windows-->prefences-->java-->editor-->Content Assist-->
Activation triggers for java的右侧“.”就可以改变常用的快捷键
选中 Text
近一个月的面试总结
cugfy
面试
本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/46753275
前言
打算换个工作,近一个月面试了不少的公司,下面将一些面试经验和思考分享给大家。另外校招也快要开始了,为在校的学生提供一些经验供参考,希望都能找到满意的工作。
HTML5一个小迷宫游戏
357029540
html5
通过《HTML5游戏开发》摘抄了一个小迷宫游戏,感觉还不错,可以画画,写字,把摘抄的代码放上来分享下,喜欢的同学可以拿来玩玩!
<html>
<head>
<title>创建运行迷宫</title>
<script type="text/javascript"
10步教你上传githib数据
张亚雄
git
官方的教学还有其他博客里教的都是给懂的人说得,对已我们这样对我大菜鸟只能这么来锻炼,下面先不玩什么深奥的,先暂时用着10步干净利索。等玩顺溜了再用其他的方法。
操作过程(查看本目录下有哪些文件NO.1)ls
(跳转到子目录NO.2)cd+空格+目录
(继续NO.3)ls
(匹配到子目录NO.4)cd+ 目录首写字母+tab键+(首写字母“直到你所用文件根就不再按TAB键了”)
(查看文件
MongoDB常用操作命令大全
adminjun
mongodb 操作命令
成功启动MongoDB后,再打开一个命令行窗口输入mongo,就可以进行数据库的一些操作。输入help可以看到基本操作命令,只是MongoDB没有创建数据库的命令,但有类似的命令 如:如果你想创建一个“myTest”的数据库,先运行use myTest命令,之后就做一些操作(如:db.createCollection('user')),这样就可以创建一个名叫“myTest”的数据库。
一
bat调用jar包并传入多个参数
aijuans
下面的主程序是通过eclipse写的:
1.在Main函数接收bat文件传递的参数(String[] args)
如: String ip =args[0]; String user=args[1]; &nbs
Java中对类的主动引用和被动引用
ayaoxinchao
java 主动引用 对类的引用 被动引用 类初始化
在Java代码中,有些类看上去初始化了,但其实没有。例如定义一定长度某一类型的数组,看上去数组中所有的元素已经被初始化,实际上一个都没有。对于类的初始化,虚拟机规范严格规定了只有对该类进行主动引用时,才会触发。而除此之外的所有引用方式称之为对类的被动引用,不会触发类的初始化。虚拟机规范严格地规定了有且仅有四种情况是对类的主动引用,即必须立即对类进行初始化。四种情况如下:1.遇到ne
导出数据库 提示 outfile disabled
BigBird2012
mysql
在windows控制台下,登陆mysql,备份数据库:
mysql>mysqldump -u root -p test test > D:\test.sql
使用命令 mysqldump 格式如下: mysqldump -u root -p *** DBNAME > E:\\test.sql。
注意:执行该命令的时候不要进入mysql的控制台再使用,这样会报
Javascript 中的 && 和 ||
bijian1013
JavaScript && ||
准备两个对象用于下面的讨论
var alice = {
name: "alice",
toString: function () {
return this.name;
}
}
var smith = {
name: "smith",
[Zookeeper学习笔记之四]Zookeeper Client Library会话重建
bit1129
zookeeper
为了说明问题,先来看个简单的示例代码:
package com.tom.zookeeper.book;
import com.tom.Host;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.Wat
【Scala十一】Scala核心五:case模式匹配
bit1129
scala
package spark.examples.scala.grammars.caseclasses
object CaseClass_Test00 {
def simpleMatch(arg: Any) = arg match {
case v: Int => "This is an Int"
case v: (Int, String)
运维的一些面试题
yuxianhua
linux
1、Linux挂载Winodws共享文件夹
mount -t cifs //1.1.1.254/ok /var/tmp/share/ -o username=administrator,password=yourpass
或
mount -t cifs -o username=xxx,password=xxxx //1.1.1.1/a /win
Java lang包-Boolean
BrokenDreams
boolean
Boolean类是Java中基本类型boolean的包装类。这个类比较简单,直接看源代码吧。
public final class Boolean implements java.io.Serializable,
读《研磨设计模式》-代码笔记-命令模式-Command
bylijinnan
java 设计模式
声明: 本文只为方便我个人查阅和理解,详细的分析以及源代码请移步 原作者的博客http://chjavach.iteye.com/
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* GOF 在《设计模式》一书中阐述命令模式的意图:“将一个请求封装
matlab下GPU编程笔记
cherishLC
matlab
不多说,直接上代码
gpuDevice % 查看系统中的gpu,,其中的DeviceSupported会给出matlab支持的GPU个数。
g=gpuDevice(1); %会清空 GPU 1中的所有数据,,将GPU1 设为当前GPU
reset(g) %也可以清空GPU中数据。
a=1;
a=gpuArray(a); %将a从CPU移到GPU中
onGP
SVN安装过程
crabdave
SVN
SVN安装过程
subversion-1.6.12
./configure --prefix=/usr/local/subversion --with-apxs=/usr/local/apache2/bin/apxs --with-apr=/usr/local/apr --with-apr-util=/usr/local/apr --with-openssl=/
sql 行列转换
daizj
sql 行列转换 行转列 列转行
行转列的思想是通过case when 来实现
列转行的思想是通过union all 来实现
下面具体例子:
假设有张学生成绩表(tb)如下:
Name Subject Result
张三 语文 74
张三 数学 83
张三 物理 93
李四 语文 74
李四 数学 84
李四 物理 94
*/
/*
想变成
姓名 &
MySQL--主从配置
dcj3sjt126com
mysql
linux下的mysql主从配置: 说明:由于MySQL不同版本之间的(二进制日志)binlog格式可能会不一样,因此最好的搭配组合是Master的MySQL版本和Slave的版本相同或者更低, Master的版本肯定不能高于Slave版本。(版本向下兼容)
mysql1 : 192.168.100.1 //master mysq
关于yii 数据库添加新字段之后model类的修改
dcj3sjt126com
Model
rules:
array('新字段','safe','on'=>'search')
1、array('新字段', 'safe')//这个如果是要用户输入的话,要加一下,
2、array('新字段', 'numerical'),//如果是数字的话
3、array('新字段', 'length', 'max'=>100),//如果是文本
1、2、3适当的最少要加一条,新字段才会被
sublime text3 中文乱码解决
dyy_gusi
Sublime Text
sublime text3中文乱码解决
原因:缺少转换为UTF-8的插件
目的:安装ConvertToUTF8插件包
第一步:安装能自动安装插件的插件,百度“Codecs33”,然后按照步骤可以得到以下一段代码:
import urllib.request,os,hashlib; h = 'eb2297e1a458f27d836c04bb0cbaf282' + 'd0e7a30980927
概念了解:CGI,FastCGI,PHP-CGI与PHP-FPM
geeksun
PHP
CGI
CGI全称是“公共网关接口”(Common Gateway Interface),HTTP服务器与你的或其它机器上的程序进行“交谈”的一种工具,其程序须运行在网络服务器上。
CGI可以用任何一种语言编写,只要这种语言具有标准输入、输出和环境变量。如php,perl,tcl等。 FastCGI
FastCGI像是一个常驻(long-live)型的CGI,它可以一直执行着,只要激活后,不
Git push 报错 "error: failed to push some refs to " 解决
hongtoushizi
git
Git push 报错 "error: failed to push some refs to " .
此问题出现的原因是:由于远程仓库中代码版本与本地不一致冲突导致的。
由于我在第一次git pull --rebase 代码后,准备push的时候,有别人往线上又提交了代码。所以出现此问题。
解决方案:
1: git pull
2:
第四章 Lua模块开发
jinnianshilongnian
nginx lua
在实际开发中,不可能把所有代码写到一个大而全的lua文件中,需要进行分模块开发;而且模块化是高性能Lua应用的关键。使用require第一次导入模块后,所有Nginx 进程全局共享模块的数据和代码,每个Worker进程需要时会得到此模块的一个副本(Copy-On-Write),即模块可以认为是每Worker进程共享而不是每Nginx Server共享;另外注意之前我们使用init_by_lua中初
java.lang.reflect.Proxy
liyonghui160com
1.简介
Proxy 提供用于创建动态代理类和实例的静态方法
(1)动态代理类的属性
代理类是公共的、最终的,而不是抽象的
未指定代理类的非限定名称。但是,以字符串 "$Proxy" 开头的类名空间应该为代理类保留
代理类扩展 java.lang.reflect.Proxy
代理类会按同一顺序准确地实现其创建时指定的接口
Java中getResourceAsStream的用法
pda158
java
1.Java中的getResourceAsStream有以下几种: 1. Class.getResourceAsStream(String path) : path 不以’/'开头时默认是从此类所在的包下取资源,以’/'开头则是从ClassPath根下获取。其只是通过path构造一个绝对路径,最终还是由ClassLoader获取资源。 2. Class.getClassLoader.get
spring 包官方下载地址(非maven)
sinnk
spring
SPRING官方网站改版后,建议都是通过 Maven和Gradle下载,对不使用Maven和Gradle开发项目的,下载就非常麻烦,下给出Spring Framework jar官方直接下载路径:
http://repo.springsource.org/libs-release-local/org/springframework/spring/
s
Oracle学习笔记(7) 开发PLSQL子程序和包
vipbooks
oracle sql 编程
哈哈,清明节放假回去了一下,真是太好了,回家的感觉真好啊!现在又开始出差之旅了,又好久没有来了,今天继续Oracle的学习!
这是第七章的学习笔记,学习完第六章的动态SQL之后,开始要学习子程序和包的使用了……,希望大家能多给俺一些支持啊!
编程时使用的工具是PLSQL