前言:面试的误区,很多人不会面试,甚至很多有经验的人。面试最重要的是交流,半个小时的时间,怎么能突出你的技能。首先一点,基础知识要牢靠,可能有人会说,你这不是废话吗。这还真不是废话,有些人会做不一定会说,甚至有的人代码能写出来都讲不出来。面试即考察基础知识,又考察语言组织能力,有的人可能心里知道,但是说出来就不一样了。面试一定要有逻辑性,和代码是一样的。这就要求我们在梳理知识点的时候,一定要有知识体系图,比如思维导图,类图等。有些人虽然知道原理,但是不知道怎么去说,不知道从哪里开始说。这里讲述一下最简单的方式,从简到繁,从外到内,先总述,再细讲。
面试技术核心:把知识点提炼提炼再提炼。把简历上的知识点全都弄明白,不求多只求精炼
本篇文章亮点:每一个知识点,最后都会用一张图去解决。每个知识点都可以串起来说个5分钟,告别面试语言障碍。
一,内存区域,内存分配,Full GC,Minor GC,Major GC让这些问题不再困扰我们。
一想到Java虚拟机,大家可能都慌了,那是一本书呀,怎么三言两语就能说清楚呢。在面试中只要问到这一类问题,都可以用以下方式统一回答。
1,先介绍内存区域
为什么先介绍内存区域呢,如果上来就问你Full GC你会怎么回答,可能一脸萌萌哒,或者根本不知道Full Gc是什么。如果知道了,把概念一说:“Full GC是清除堆空间的,包括新生代和老年代”,这就完了吗,如果对知识结构不清楚,接下来会有很多问题。比如:什么情况下产生Full GC等问题。面试不要让面试官牵着鼻子走,要主动出击,如果是体系的回答,这样会少很多问题。好了,不扯别的了,开始脑补。
先上图:这张图就是体系,一定要记住。记住这张图才能继续往下说,每个区都是干嘛的。
简单介绍各个区域:注意堆和栈一定要搞清楚,不然面试会很难看。可能会问你堆和栈是干嘛的,最起码的理论得知道。
①,方法区:存储已被虚拟机加载的类,常量,静态变量等。
②,堆区:存储对象的实例,是GC管理的主要区域。
③,虚拟机栈:用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
④,本地方法栈:与虚拟机栈一样,只不过为native层服务。
⑤,程序计数器:当前线程所执行的字节码的行号指示器,此区域不会出现内存溢出。
再次脑补,内存区域图,然后想想每个区域的作用。
2,接下来就该介绍内存分配了
内存区域知道了,就该说说内存分配了,这样很有条理和逻辑。
先上图:这是堆内存图,分为新生代,老年代,永久代(jdk1.8无永久代,使用metaspace实现)
新生代:新生代又分为Eden和Survivor区,对象优先分配在Eden区。
老年代:分配大对象,长期存活的对象。
3,开始讲GC吧
有了上边的介绍,这时候就可以说说GC了,这样说的时候就很清晰了。
Minor GC:清理新生代,当Eden区没有足够的空间进行分配时,虚拟机触发Minor GC。
Major GC:清理老年代,经常会伴随至少一次的Minor GC。
Full GC:清理新生代和老年代。
Full GC产生的条件:
- 老年代空间不足 。
- 调用System.gc()时,系统建议执行Full GC,但是不必然执行。
- 通过Minor GC后进入老年代的平均大小 > 老年代的可用内存 。
虚拟机是如何区分新生代和老年代的,这里还可以讲一下,对象年龄计数器,也就是分代收集算法。如果Eden区满了执行Minor GC后,对象依然存活,将会被移到Survivor区,同时对象年龄设置为1。以此类推如果Minor GC后再Survivor区依然存活,对象再加1,直到达到年龄阈值,被移到老年区。
讲到这里其实已经不错了,如果再去说一下垃圾收集器算法就更厉害了。
4,再说说垃圾收集器算法
在垃圾回收前怎么知道对象要被回收呢,如下:
①,引用计数:很好理解,就是记录对象被引用的次数。
②,可达性分析算法:当一个对象到GC Roots没有任何引用链时,证明对象不可用。
再谈引用
①,强引用:只要引用存在,垃圾回收器不会回收。
②,软引用:在出现内存溢出之前,将会把这些对象回收。
③,弱引用:垃圾回收器工作时,无论当前内存是否足够,都会回收。
④,虚引用:为对象设置虚引用是能在对象回收时收到通知。
垃圾收集算法
①,标记清除法
②,标记整理法
③,分代收集法
④,复制收集法
到这里就可以了,然后生成思维导图,把知识点串起来。看着这个体系去一条条的说,还可以每个点再去延伸,我大概试了一下,把这些说完需要六分钟左右。这样以来延长你说话时间,等于缩小面试官问问题的时间,概率大大提升。
二,Android的消息机制
在面试中Handler经常被问到吧,但是你的回答是:“MessageQueue,Looper,MessageQueue保存数据,Looper从MessageQueue中取数据,Handler发送和接收数据”。如果是这样的回答,那就太简单了,可能自己都不知道说些啥。好了,那就来个知识体系吧,告别Handler带来的恐惧。
提问知识点
系统为什么不允许在子线程中访问UI:因为Android的UI控件不是线程安全的。
为什么不对UI控件加锁访问呢:加锁会让UI访问变得复杂,其次锁机制会降低UI访问效率,锁机制会阻塞线程。
Handler是怎么实现延时任务的?
Handler中的post方法?
在面试中如果被问到Handler了,就把涉及到的类先简单介绍一下,然后再逐个进行深入详解。
1,MessageQueue:存取消息
MessageQueue主要包含两个操作:插入和读取。插入和读取对应方法enqueueMessage和next。
内部实现是单链表的数据结构,单链表插入和删除有优势。(单链表:每个节点都有数据元素区和指针区,指针指向后续节点)
2,Looper:查询消息
Looper何时产生:在UI线程(ActivityThread)创建时就会初始化Looper。
查看Looper源码,发现源码中有用到ThreadLocal这个类,那就说说ThreadLocal类。
①,ThreadLocal:是一个线程内部的数据存储类,怎么去理解呢,举个例子:
使用ThreadLocal存Boolean类型的数据,代码如下:
private ThreadLocal<Boolean> mThreadLocal=new ThreadLocal<Boolean>();
分别在主线程,子线程1和子线程2中设置和访问,代码如下:
mThreadLocal.set(true);
Log.e("ThreadLocal","main value="+mThreadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
mThreadLocal.set(false);
Log.e("ThreadLocal","thread1 value="+mThreadLocal.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Log.e("ThreadLocal","thread2 value="+mThreadLocal.get());
}
}).start();
打印结果:看到打印结果自然明白了,虽然在不同线程访问同一个ThreadLocal对象,但是他们取到的值不一样,互不影响。对于Looper的作用域就是线程并且不同的线程具有不同的Looper,所以通过ThreadLocal就可以轻松实现Looper在线程中的存取。
接下来说一下ThreadLocal内部实现。
从ThreadLoacl 的 set 和 get 方法来看,它们操作的对象都是当前线程对象中的 ThreadLocalMap 对象的 Entry[] 数组。ThreadLocalMap用于存储当前线程的副本。
Entry数组保存局部变量。通过key(ThreadLocal类型)的hashcode来计算数组存储的索引位置i。如果i位置已经存储了对象,那么就往后挪一个位置依次类推,直到找到空的位置,再将对象存放。
②,Looper工作原理
Looper就是负责不停的从MessageQueue中取消息,在主线程中已经存在Looper了。如果在子线程中用Handler就需要主动创建Looper,代码如下:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Handler handler=new Handler();
Looper.loop();
}
}).start();
Looper.prepare()方法是为当前线程创建Looper,Looper.loop()方法是开启消息循环。
3,Handler:负责发送接收消息
Handler发送消息是向消息队列中插入了一条消息,MessageQueue的next方法返回消息给Looper,Looper将消息交给Handler处理,即Handler的dispatchMessage方法被调用。
又到了上图的时候,看着这张图,让Handler问题不再恐慌。建议梳理的过程中,把源码大致看一下,最起码要看看关键方法的实现,这样更有助于理解Handler。
三,Activity启动流程
activity的启动流程很复杂,不过这也是面试常问的问题。在这里怎么去提炼信息呢,原则上把涉及到的主要类,核心方法梳理一下就可以。这里不可能完全掌握,但是必要的还是需要去构建知识体系。
在网上找了很多关于activity启动的文章,最后感觉还是这张图能简单易懂的说明问题,比起长篇大论,不如这张图。
启动一个activity的方式有两种,一种是在launcher中点击一个app,这时候会启动app。另一种是在app中跳转activity页面。不管是哪一种,总体流程类似。
1,在启动activity之前,先要了解一下几个进程
①,init进程
Android是基于linux系统的,手机开机之后,linux内核进行加载。加载完成之后会启动init进程。init进程会启动ServiceManager,孵化一些守护进程,并解析init.rc孵化Zygote进程。
②,Zygote进程
所有的App进程都是由Zygote进程fork生成的,包括SystemServer进程。Zygote初始化后,会注册一个等待接受消息的socket,OS层会采用socket进行IPC通信。
③,SystemServer进程
System Server是Zygote孵化的第一个进程。SystemServer负责启动和管理整个Java framework,包含AMS,PMS等服务。
2,开始启动了,动次打次
把启动流程简化一下,提炼提炼再提炼,如果分步骤去说,更有条理。
第一步:Launcher通知AMS要启动新的Activity
- Launcher.startActivitySafely //首先Launcher发起启动Activity的请求
- Activity.startActivity
- Activity.startActivityForResult
- Instrumentation.execStartActivity //交由Instrumentation代为发起请求
- ActivityManager.getService().startActivity //通过IActivityManagerSingleton.get()得到一个AMP代理对象
- ActivityManagerProxy.startActivity //通过AMP代理通知AMS启动activity
第二步:AMS先校验一下Activity的正确性,如果正确的话,会暂存一下Activity的信息。然后,AMS会通知Launcher程序pause Activity。
- ActivityManagerService.startActivity
- ActivityManagerService.startActivityAsUser
- ActivityStackSupervisor.startActivityMayWait
- ActivityStackSupervisor.startActivityLocked :检查有没有在AndroidManifest中注册
- ActivityStackSupervisor.startActivityUncheckedLocked
- ActivityStack.startActivityLocked :判断是否需要创建一个新的任务来启动Activity。
- ActivityStack.resumeTopActivityLocked:获取栈顶的activity,并通知Launcher应该pause掉这个Activity以便启动新的activity。
- ActivityStack.startPausingLocked
- ApplicationThreadProxy.schedulePauseActivity
第三步:检查activity所在进程是否存在,如果存在,就直接通知这个进程,在该进程中启动Activity;不存在的话,会调用Process.start创建一个新进程。
- ActivityManagerService.activityPaused
- ActivityStack.activityPaused
- ActivityStack.completePauseLocked
- ActivityStack.resumeTopActivityLocked
- ActivityStack.startSpecificActivityLocked
- ActivityManagerService.startProcessLocked
- Process.start //在这里创建了新进程,新的进程会导入ActivityThread类,并执行它的main函数
第四步:创建ActivityThread实例,执行一些初始化操作,并绑定Application。如果Application不存在,会调用LoadedApk.makeApplication创建一个新的Application对象。
- ActivityThread.main
- ActivityThread.attach(false) //声明不是系统进程
- ActivityManagerProxy.attachApplication
第五步:启动目标activity
- ActivityManagerService.attachApplication //AMS绑定本地ApplicationThread对象,后续通过ApplicationThreadProxy来通信。
- ActivityManagerService.attachApplicationLocked
- ActivityStack.realStartActivityLocked //真正要启动Activity了!
- ApplicationThreadProxy.scheduleLaunchActivity //AMS通过ATP通知app进程启动Activity
涉及到的相关类
- ActivityStack:Activity在AMS的栈管理,用来记录已经启动的Activity的先后关系,状态信息等。通过ActivityStack决定是否需要启动新的进程。
- ActivitySupervisor:管理 activity 任务栈。
- ActivityThread:ActivityThread 运行在UI线程(主线程),App的真正入口。
- ApplicationThread:用来实现AMS和ActivityThread之间的交互。
- ApplicationThreadProxy:ApplicationThread在服务端的代理。AMS就是通过该代理与ActivityThread进行通信的。
- IActivityManager:继承与IInterface接口,抽象出跨进程通信需要实现的功能。
- AMN:运行在server端(SystemServer进程)。实现了Binder类,具体功能由子类AMS实现。
- AMS:AMN的子类,负责管理四大组件和进程,包括生命周期和状态切换。AMS因为要和ui交互,所以极其复杂,涉及window。
- AMP:AMS的client端代理(app进程)。了解Binder知识可以比较容易理解server端的stub和client端的proxy。AMP和AMS通过Binder通信。
- Instrumentation:仪表盘,负责调用Activity和Application生命周期。测试用到这个类比较多。
- ActivityStackSupervisor:负责所有Activity栈的管理。内部管理了mHomeStack、mFocusedStack和mLastFocusedStack三个Activity栈。其中,mHomeStack管理的是Launcher相关的Activity栈;mFocusedStack管理的是当前显示在前台Activity的Activity栈;mLastFocusedStack管理的是上一次显示在前台Activity的Activity栈。
3,继续提炼呀
在这里,activity整个流程已经很清楚了。但是要知道,面试不可能记住这么多过程和类。我们只需要说出一些关键点,然后再去延伸。
最终版本来了,我们可以把activity的启动看成四个进程,Launcher进程,System_Server进程,Zygote进程,App进程,这四个进程很好记住。用最简单的方式,讲述最复杂的流程。
四,事件分发
看到这四个字,能想到啥,能说出个啥。这又是一个面试常问,而又回答不全的一个问题。我们能想到的无非就是事件发送,拦截,处理等。因此还是需要系统的去整理,才能系统的去讲好。
1,View的事件体系
①,MontionEvent
手指触摸屏幕会产生以下事件类型,同时通过MotionEvent还可以拿到点击事件发生的坐标。
单指触摸
- ACTION_DOWN:手指刚接触屏幕
- ACTION_MOVE:手指在屏幕上移动
- ACTION_UP:手指从屏幕上松开
- ACTION_CANCEL:父View如果拦截事件了,子View会收到这个时间,并且不会再有move和up事件。
多指触摸
- ACTION_POINTER_DOWN:有非主要的手指按下(即按下之前已经有手指在屏幕上)
- ACTION_POINTER_UP:有非主要的手指抬起(即抬起之后仍然有手指在屏幕上)
②,TouchSlop
是系统所能识别出来的被认为滑动的最小的距离,是一个常量。
③,VelocityTracker
用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。
④,GestureDetector
手势检测,用于辅助检测用户的单击,滑动,长按,双击等。
④,Scroller
用于实现View的弹性滑动
2,View的事件分发机制
事件的分发可以理解为三个过程,分发,拦截,消费。
①,分发三阶段
- dispatchTouchEvent():用来事件的分发,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响。
- onInterceptTouchEvent():用来事件的拦截,在dispatchTouchEvent内部调用。
- onTouchEvent():用来处理点击事件,在dispatchTouchEvent内部调用。
②,传递顺序
一个点击事件产生后,传递顺序 Activity->Window->View,具体过程如下:
由Activity的dispatchTouchEvent来派发,由Activity内部的Window来完成。Window会将事件传递给DecorView,DecorView是页面的底层容器。Window的实现类是PhoneWindow。DecorView负责向子View中去分发,这样一级一级的下发。
3,View事件的滑动
了解了View的事件传递后,可以合理的利用事件传递解决滑动问题
1,View的滑动
①,普通移动
scrollTo和scrollBy:只能移动View的内容,不能移动View本身。
还可以通过LayoutParams移动位置。
②,渐进式移动
可以使用Scroller,实现弹性动画。还可以使用动画,延时策略。
2,滑动冲突
由于各种View的互相嵌套,会出现滑动冲突问题。
解决办法有两种:
①,外部拦截法
在父容器中的onInterceptTouchEvent中拦截。
②,内部拦截法
在子元素中处理,重写dispatTouchEvent方法,配合requestDisallowInterceptTouchEvent方法使用。
最后还是上图,面试只要掌握一条,由易到难,先总述,再挑一个熟悉的细讲。
五,多线程
概念:线程是操作系统能够进行运算调度的最小单位
1,死锁的四个条件
①,互斥条件
②,请求与保持条件
③,不可剥夺条件
④,循环等待条件
2,线程锁
①,synchronized:同步代码块或方法,是可重入锁。
②,volatile:是一个特殊的修饰符,只有成员变量才能使用它。
3,Java线程池
- newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
- newFixedThreadPool :创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool :创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。
- newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
4,Android中线程
①,AsyncTask:是一种轻量的异步任务类,可以在线程池中执行后台任务,执行结果可以交给主线程。
其中封装了Thread和Handler。
AsyncTask四个核心方法:
- onPreExecute():在主线程中执行,在异步任务执行之前。
- doInBackground():在线程池中执行,用于执行异步任务。
- onProgressUpdate():在主线程中执行,当后台任务的执行进度发生改变时此方法会调用。
- onPostExecute():在主线程中执行,在异步任务执行之后。
工作原理:
从它的execute方法开始,其中里边有Executor,这是一个串行线程池,其中SerialExecutor是Executor的一个实现。
②,HandlerThread:继承了Thread,它是一种可以使用Handler的Thread,内部通过Looper.prepare()来创建消息队列,Looper.loop()来开启消息循环。
③,IntentService:内部封装了HandlerThread和Handler。
六,进程通信
由于进程之间是不能互相访问的,因此就有了多进程通信的技术,android中特有的多进程通信是Binder。正常情况下,一个应用就是一个进程。但是android中一个应用也可以有多个进程,那就是给四大组件指定进程,通过android:process属性。其中以“:”开头的为私有进程,不以“:”开头的为共有进程。
多进程带来的问题:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharedPreferences的可靠性下降
- Application会多次创建
1,序列化
①,Serializable
Serializable是Java提供的一个接口,使用时在类中指定serialVersionUID。
对象的序列化和反序列化用到了ObjectOutputStream和ObjectInputStream。
Serializable序列化是保存在磁盘的。
②,Parcelable
Parcelable也是一个接口,内部是由Parcel完成的。Parcelable序列化是保存在内存的。
2,Binder
Binder可大致分为:Binder驱动,Service Manager,Service,Client这几部分.。
Binder是作为一个特殊的字符型设备而存在,设备节点/dev/binder,遵循Linux设备驱动模型。主要通过binder_ioctl函数与用户空间进程交换数据。
Binder的实质就是要把对象从一个进程映射到另一个进程。Binder内部使用内存映射。
3,AIDL
AIDL的实现主要分为两步:
服务端:创建Service监听客户端请求,创建AIDL将暴露给客户端的接口在AIDL中声明,最后在Service中实现这个AIDL接口
客户端:绑定服务端Service,将服务端返回的Binder对象转成AIDL接口所属类型。
①,服务端
单独创建个包名,为啥要单独创建包名呢。第一复制到别的应用方便,客户端和服务器端包名要一致。第二客户端需要反序列化服务端中的类。
创建三个文件:Book.java,Book.aidl,IBookManager.aidl
Book.java类实现了Parcelable接口
public class Book implements Parcelable {
private String bookName;
protected Book(Parcel in) {
bookName = in.readString();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(bookName);
}
}
看下Book.aidl中的内容,就是这么简单。
parcelable Book ;
在看IBookManager.aidl中的内容。其中看到了in关键字,这是aidl的数据类型。in表示输入型参数,out表示输出型参数,inout表示输入输出型参数
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
开始创建服务,这里的CopyOnWriteArrayList支持并发读写。注册服务,指定action。
public class BookManagerService extends Service {
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book("android"));
mBookList.add(new Book("java"));
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
}
②,客户端
需要注意的是,客户端的onServiceConnected和onServiceDisconnected,都是在UI线程中。
Intent intent = new Intent();
intent.setPackage("包名");
intent.setAction("指定action");
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
private ServiceConnection mConnection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager= IBookManager.Stub.asInterface(service);
try {
List<Book> list=bookManager.getBookList();
Log.e("MainActivity", "onServiceConnected: size="+list.size() );
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
③,RemoteCallbackList
删除跨进程接口类,实现进程间的接口传递删除。
④,服务以外挂掉重连
第一种:给Binder设置DeathRecipient监听,当Binder死亡,会受到binderDied回调,进行重连。
第二种:onServiceDisconnected重连远程服务。
⑤,安全验证
第一种:使用permission,在onBind中进行验证。
第二种:在onTransact方法中进行权限验证。
4,Messenger
Messenger底层实现是通过AIDL,一次处理一个请求。
服务端:
首先在服务端创建Service来处理客户端连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回一个Messenger对象底层的Binder.
客户端:
绑定服务端Service,通过服务端IBinder对象创建一个Messenger。
5,ContentProvider
跨进程通信数据共享,一般用于数据库访问,使用很简单,只需要继承ContentProvider就可以,然后在清单文件中注册。
<provider
android:authorities="唯一标识"
android:name="类名"
android:permission="权限"/>
android:authorities是唯一标识,通过这个属性外部应用可以访问,比如访问如下:
Uri uri= Uri.parse("content://唯一标识");
getContentResolver().query(uri,null,null,null,null);
如果数据改变,通知外界,可以使用ContentObserver的registerContentObserver方法注册,通过unRegisterContentObserver方法来解除。
如果是按照类型查询不同表数据,可以使用UriMatcher
启动过程
ContentProvider所在进程启动时,ContentProvider会同时启动并被发布到AMS中。ContentProvider的onCreate要先于Application的onCreate。
①,通过AMS的startProcessLocked先启动应用进程,进入ActivityThread的main方法。
②,创建ContextImpl和Instrumentation,创建Application。
③,ActivityThread类的acquireProvider()。
6,Socket
Socket分为流式套接字和用户套接字两种,分别对应网络传输层的TCP和UDP协议。TCP解析是面向连接的协议,提供稳定的双向通信功能,连接时需要三次握手。UDP是无连接的,提供不稳定的单向通信。
1,服务端设计,当Service启动时,建立通信。
关键代码如下:
ServerSocket serverSocket=new ServerSocket(8688);
Socket client=serverSocket.accept();
//用于接收客户端消息
BufferedReader in=new BufferedReader(new InputStreamReader(client.getInputStream()));
//用户向客户端发送消息
PrintWriter out=new PrintWriter(new BufferedWriter(
new OutputStreamWriter(client.getOutputStream())),true)
2,客户端
Socket socket=new Socket("localhost",8688);
//用于接收服务端消息
BufferedReader in=new BufferedReader(
new InputStreamReader(socket.getInputStream()));