2018面试题目总结1
1.HTTP和socket、websocket的区别
首先要了解网络七层:物 数 网 传 会 表 应, 如图:
HTTP 协议:超文本传输协议,对应于应用层,用于如何封装数据.
TCP/UDP 协议:传输控制协议,对应于传输层,主要解决数据在网络中的传输。
IP 协议:对应于网络层,同样解决数据在网络中的传输。
传输数据的时候只使用 TCP/IP 协议(传输层),如果没有应用层来识别数据内容,传输后的协议都是无用的。
应用层协议很多 FTP,HTTP,TELNET等,可以自己定义应用层协议。
web 使用 HTTP 作传输层协议,以封装 HTTP 文本信息,然后使用 TCP/IP 做传输层协议,将数据发送到网络上。
- HTTP协议
http 为短连接:客户端发送请求都需要服务器端回送响应.请求结束后,主动释放链接,因此为短连接。通常的做法是,不需要任何数据,也要保持每隔一段时间向服务器发送"保持连接"的请求。这样可以保证客户端在服务器端是"上线"状态。
HTTP连接使用的是"请求-响应"方式,不仅在请求时建立连接,而且客户端向服务器端请求后,服务器才返回数据。 - socket接口
Socket 是对 TCP/IP 协议的封装,Socket 只是个接口不是协议,通过 Socket 我们才能使用 TCP/IP 协议,除了 TCP,也可以使用 UDP 协议来传递数据。
Socket为长连接:通常情况下Socket 连接就是 TCP 连接,因此 Socket 连接一旦建立,通讯双方开始互发数据内容,直到双方断开连接。在实际应用中,由于网络节点过多,在传输过程中,会被节点断开连接,因此要通过轮询高速网络,该节点处于活跃状态(保活)。
// 创建Socket对象 & 指定服务端的IP及端口号
Socket socket = new Socket(“192.168.1.32”, 1989); - websocket 协议
WebSocket同HTTP一样都是基于TCP是应用层的协议,但是它是一种双向通信协议,是建立在TCP之上的可靠性传输协议。
(HTTPS = HTTP + SSL(安全套接字(Secure Socket Layer,SSL)协议是Web浏览器与Web服务器之间安全交换信息的协议,提供两个基本的安全服务:鉴别与保密。))
2.RecyclerView和ListView的异同
- ViewHolder是用来保存视图引用的类,listview可以非必须使用,但是recycleview必须继承,避免了listview不使用ViewHolder(一个ViewHolder先从缓存中(RecycledCiewPool)读取,如果都无法匹配到,则会新创建一个)每次都findViewById(int)。
- ListView只能在垂直方向上滚动,API并未提供方法,虽然可以实现水平滚动,但是Recycleview可以通过各种manager实现水平、竖直、瀑布流等样式,样式、分隔符、item动画自定义。
3.Android几种进程
- 前台进程:即与用户正在交互的Activity或者Activity用到的Service等,如果系统内存不足时前台进程是最后被杀死的
- 可见进程:可以是处于暂停状态(onPause)的Activity或者绑定在其上的Service,即被用户可见,但由于失去了焦点而不能与用户交互
- 服务进程:其中运行着使用startService方法启动的Service,虽然不被用户可见,但是却是用户关心的,例如用户正在非音乐界面听的音乐或者正在非下载页面自己下载的文件等;当系统要空间运行前两者进程时才会被终止
- 后台进程:其中运行着执行onStop方法而停止的程序,但是却不是用户当前关心的,例如后台挂着的QQ,这样的进程系统一旦没了有内存就首先被杀死
- 空进程:不包含任何应用程序的程序组件的进程,这样的进程系统是一般不会让他存在的
(如何避免后台进程被杀死:调用startForegound,让你的Service所在的线程成为前台进程;Service的onStartCommond返回START_STICKY或START_REDELIVER_INTENT;Service的onDestroy里面重新启动自己)
4.耗时操作
- Service的onStartCommand() 和 onBind() 方法中不能执行耗时操作
- BroadcastReceiver的onReceive方法不能执行耗时操作,因为这个方法是在主线程执行 的,耗时操作会导致UI不顺畅,超过10秒钟可能会被系统杀死
- UI线程执行耗时操作,可以采用View.post方法来执行,或者使用Handler
- onPause 中不适合做耗时较长的操作,Activity的跳转要先执行完前一个Activity的onPause方法,如果执行耗时操作会影响UI的显示
IntentService 与Service的不同之处在于IntentService是可以执行耗时任务的,而Service不能够执行耗时任务,否则会出现ANR(Application Not Responding)异常。
@Override
protected void onHandleIntent(@Nullable Intent intent) {
try {
Log.d("tag","start onHandleIntent");
// 可以执行耗时任务
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
---------------------
像HandlerThread(handleMessage(Message message)),AsyncTask(doInBackground(Integer… integers))都可以进行耗时操作。
5.java里的小问题
- 线程run()和start()的区别
public static void main(String args[]) {
Thread thread = new Thread() {
public void run() {
show();
}
};
thread .start();
// thread .run();
System.out.print("haha");
}
static void show() {
System.out.print("xixi");
}
---------------------
result: hahaxixi
-------------------
反之,放开thread .run(),释放thread .start()
------------------
result: xixihaha
.run(); 只是调用了一个普通方法,并没有启动另一个线程,程序还是会按照顺序执行相应的代码
.start(); 则表示,重新开启一个线程,不必等待其他线程运行完,只要得到cup就可以运行该线程。
-
sleep()和wait()区别
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
获取对象锁进入运行状态。 -
栈和堆
首先要知道,基本数据类型、局部变量都是存放在栈内存中的,用完就消失。
new创建的实例化对象及数组,是存放在堆内存中的,用完之后靠垃圾回收机制不定期自动消除。
main()
int[] x=new int[4];
x[0]=20
主函数main()中定义数组x,元素类型int,元素个数4
第一步:JVM执行main()函数,在栈内存中开辟一个空间,存放x变量(x变量是局部变量)。
同时,在堆内存中也开辟一个空间,存放new int[4]数组,堆内存会自动内存首地址值,如0x0045。
数组在栈内存中的地址值,会附给x,这样x也有地址值。所以,x就指向(引用)了这个数组。此时,所有元素均未附值,但都有默认初始化值0。
第二步:即在堆内存中将20附给[0]这个数组元素。这样,数组的三个元素值分别为20,0,0,0
栈:
函数中定义的基本类型变量,对象的引用变量都在函数的栈内存中分配。
栈内存特点,数据一执行完毕,变量会立即释放,节约内存空间。
栈内存中的数据,没有默认初始化值,需要手动设置。
堆:
堆内存用来存放new创建的对象和数组。
堆内存中所有的实体都有内存地址值。
堆内存中的实体是用来封装数据的,这些数据都有默认初始化值。
堆内存中的实体不再被指向时,JVM启动垃圾回收机制,自动清除,这也是JAVA优于C++的表现之一(C++中需要程序员手动清除)。
6.内存泄漏
- 单例造成内存泄漏
首先了解单例的静态特性使得其生命周期和应用的生命周期一样长
// 使用了单例模式
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
// this.context = context; //避免把当前activity传进来造成内存泄漏
this.context = context.getApplicationContext;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
- 非静态内部类创建静态实例造成的内存泄漏
public class MainActivity extends AppCompatActivity {
private static Test mT = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//非静态类的引用
if(mT == null){
mT = new Test();
}
//...
}
class Test{
//...
}
}
解决方法:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,就使用Application的Context。
- Handler造成的内存泄漏、线程造成的内存泄漏
解决方法:如上 - 资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。
解决方法:关闭,制空 - 使用listview造成的内存泄漏
解决办法:在构造Adapter时,使用缓存的convertView。 - 集合容器中的内存泄露
- WebView造成的泄露
解决方法总结:
- 在涉及使用Context时,对于生命周期比Activity长的对象应该使用Application的Context。凡是使用Context优先考虑Application的Context,当然它并不是万能的,对于有些地方则必须使用Activity的Context。
- 对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏。
- 对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null。
- 保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期
- 对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
1)将内部类改为静态内部类
2)静态内部类中使用弱引用来引用外部类的成员变量
7.自定义View
- 三个构造方法
第一个构造函数: 当不需要使用xml声明或者不需要使用inflate动态加载时候,实现此构造函数即可
第二个构造函数: 当需要在xml中声明此控件,则需要实现此构造函数。并且在构造函数中把自定义的属性与控件的数据成员连接起来。
第三个构造函数: 接受一个style资源 (暴露各种api) - 三个主要方法
1)onMeasure()
测量一个view要占的大小,方法参数是两个int型的值
(测量所得的宽高不一定是最后展示的宽高,最后宽高确定是在onLayout方法里,layou(left,top,right,bottom),不过一般都是一样的。)
2) onLayout
onLayout方法里主要是具体摆放子view的位置,水平摆放或者垂直摆放,所以在单纯的自定义view是不需要重写onLayout方法,不过需要注意的一点是,子view的margin属性是否生效就要看parent是否在自身的onLayout方法进行处理,而view得padding属性是在onDraw方法中生效的。
3)onDraw()
绘制,若是单纯继承,可以把构造方法删除,默认为空
4)requstLayout和invidious
时常用到刷新view的方法,这时候就会有三个方法供我们选择:requestLayout()、invalidate()、postInvalidate(),其实invalidate和postInvalidate这两个方法作用是一样的,唯一不同的是invalidate用在主线程,而postInvalidate用在异步线程,下面对比一下requestLayout和invalidate的内部实现:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
从代码可以看出,其实这两个方法内部都是调用的scheduleTraversals()方法,不同的是,requestLayout方法将mLayoutRequested标示置为true,scheduleTraversals这个方法以后找机会再细分析,现在只简单说下结论,
requestLayout会调用measure和layout 等一系列操作,然后根据布局是否发生改变,surface是否被销毁,来决定是否调用draw,也就是说requestlayout肯定会调用measure和layout,但不一定调用draw,读者可以试着改下我上面写的那个小程序,将postInvalidate改成requestlayout,动画效果就消失了,因为布局没有发生改变。
invalidate 只会调用draw,而且肯定会调,即使什么都没有发生改变,它也会重新绘制。
所以如果有布局需要发生改变,需要调用requestlayout方法,如果只是刷新动画,则只需要调用invalidate方法。
5)自定义view的状态保存
定义view的状态保存和Activity的状态保存是类似的,都是在onSaveInstanceState()保存,然后在onRestoreInstanceState将数据安全取出,如果一个view没有id,这个view的状态是不会保存的。
6)事件处理onTouchEvent
见下次分晓
8.Android中的动画
- View Animation: 视图动画在古老的Android版本系统中就已经提供了,只能被用来设置View的动画。
- Drawable Animation: 这种动画(也叫Frame动画、帧动画)其实可以划分到视图动画的类别,专门用来一个一个的显示Drawable的resources,就像放幻灯片一样。
- Property Animation: 属性动画只对Android 3.0(API 11)以上版本的Android系统才有效,这种动画可以设置给任何Object,包括那些还没有渲染到屏幕上的对象。这种动画是可扩展的,可以让你自定义任何类型和属性的动画。
1)视图动画,也叫Tween(补间)动画(eg: AlphaAnimation,RotateAnimation)可以在一个视图容器内执行一系列简单变换(位置、大小、旋转、透明度)。譬如,如果你有一个TextView对象,您可以移动、旋转、缩放、透明度设置其文本,当然,如果它有一个背景图像,背景图像会随着文本变化。
特别注意:补间动画执行之后并未改变View的真实布局属性值
2)Drawable动画其实就是Frame动画(帧动画),它允许你实现像播放幻灯片一样的效果,这种动画的实质其实是Drawable,所以这种动画的XML定义方式文件一般放在res/drawable/目录下
特别注意,AnimationDrawable的start()方法不能在Activity的onCreate方法中调运,因为AnimationDrawable还未完全附着到window上,所以最好的调运时机是onWindowFocusChanged()方法中。
3)Android 3.0以后引入了属性动画(eg: ValueAnimator,AnimatorSet),属性动画可以轻而易举的实现许多View动画做不到的事,上面也看见了,View动画无非也就做那几种事情,别的也搞不定,而 属性动画就可以的,譬如3D旋转一张图片。其实说白了,属性动画实现原理就是修改控件的属性值实现的动画(放在res/animator/目录下)。
特别注意:属性动画执行之后改变View的真实布局属性值
以上动画都可以通过XML或Android代码定义,建议使用XML文件定义(放在res/anim/目录下),因为它更具可读性、可重用性;