面试总结

Posted by 阿呆 on 2019-01-10

面试

感觉自己的面试一直很垃圾,没有表现出自己应有的水平,这里好好总结一下,能正常发挥就万幸

自我介绍

大家好,我是余丹,今年27岁,毕业于西南交大。毕业之后从事过三份工作,分别从事了两年的老师,还有两年的Android开发工作,个人认为目前具备中高级Android开发水平,能独立承担一个项目从0到1的开发过程,并且保证代码的质量。

我的优点是,具有很强的学习精神,举个例子,虽然之前待过的两家公司都是一个人承担Android开发,再学习的过程中存在一些障碍,为了解决这个问题,我加入了一些学习小组,譬如开发群、知识星球等比较高效的社区,并且积极提问以及尽可能帮助别人解决问题,在这个过程中也认识了一些Android界的大牛,对我帮助很大。技术提升最快的阶段也是这个阶段,别人戏称我是问题少年,在遇到一些比较难解决的问题,Google也找不到较准确的答案的时候,请教别人往往能很好地解决。

我不把写代码仅仅当成一份赚钱的工作,有一定的热爱,并且我的人生态度是,做一件事就要做好,不然就不要做,因为人最宝贵的就是时间,我们要把它花在有价值的东西上,。这句话是王小波地原话,我对待代码也是如此。从编码的规范到代码的执行效率,都会考虑。

持续学习,扩充自己的技术栈,持续优化自己的代码,让它更稳定高效地运行,是我对待技术的态度。

在学习的过程中,我会把获得的知识和技能总结成blog的形式,发布在自己的个人网站上(http://danyublog.com),最近在写关于Handler和自定义View的文章,给了几个朋友看,觉得写的不错,让我给他们公众号投稿,这几天由于工作忙,也没有来得及校队问斩。也会尝试着自己写开源库,目前在写的一个开源库是一个通用的DownLoader,通过IntentService来实现,并且通过广播来解耦下载结果的监听过程,里面主要涉及到数据库的以及Service的使用,还有,参与了几个朋友的开源库的使用以及反馈。

当然,我也有一些缺点,主要体现在有

平常除了技术外,有空闲的时间也会写写诗、摄影、以及骑行。关于骑行,我想也能突出我做事情比较执着地优点,在川藏线上,我看到很多骑行的,只有很少几个人能全程骑下来不推车的,我是其中一个。

关于薪资:其实刚开始学习技术的时候,我没有找到好的学习方法,不过目前我觉得自己走上了正轨吧,工作也是我目前唯一的重心,所以,我觉得我的潜力是很大的,我也希望找到一个能长待的公司,

技术补充

Android与H5的交互

WebView基本配置

1
2
3
4
5
ebSettings settings = wv.getSettings();
//让webView支持JS
settings.setJavaScriptEnabled(true);
//加载百度网页
wv.loadUrl("http://www.baidu.com/");

WebView与H5交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
1. Android调用H5方法(直接调用)
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn1:
//参数 “javascript:” + js方法名
wv.loadUrl("javascript:message()");
break;
case R.id.btn2:
//在android调用js有参的函数的时候参数要加单引号
wv.loadUrl("javascript:message2('" + name + "')");
break;
case R.id.btn3:
// 调用H5中有返回值的方法
mWebView.evaluateJavascript("sum(1,2)",new ValueCallback() { @Override*
public void on ReceiveValue(String value) {
Log.e(TAG,"onReceiveValue value=" + value);
}
});
}
}
---------------------
2. H5调用Android方法
2.1 先在Java代码里定义一个类,并写出H5要调用的Java方法,注意添加注解
public class JsInteration {
@JavascriptInterface
public String back() {
return "hello world";
}
}
2.2 mWebView.addJavascriptInterface(newJsInteration(),"android"); //“android”是这个新建类的别名
2.3 H5调用:
window.别名.android中的方法名(parameter Values)
这里就是:
var result=window.android.back();

事件分发

事件分发

内部拦截法则和外部拦截法则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
1、外部拦截法 (子view代码无需修改)(符合view事件分发机制)

说明:需要在父ViewGroup,重写onInterceptTouchEvent方法,根据业务需要,判断哪些事件是父Viewgroup需要的,需要的话就对该事件进行拦截,然后交由onTouchEvent方法处理,若不需要,则不拦截,然后传递给子view或子viewGroup,
---------------------
public boolean onInterceptTouchEvent(MotionEvent ev) {
int y= (int) ev.getY();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
yDown=y;
isIntercept=false;
break;
case MotionEvent.ACTION_MOVE:
yMove=y;
if (yMove-yDown<0){ //根据业务需求更改判断条件,判断是时候需要拦截
isIntercept=false;
}else if(yMove-yDown>0&&getChildAt(0).getScrollY()==0){
isIntercept=true;
}else if(yMove-yDown>0&&getChildAt(0).getScrollY()>0){
isIntercept=false;
}
break;
case MotionEvent.ACTION_UP:
isIntercept=false;
break;
}
return isIntercept; //返回true表示拦截,返回false表示不拦截
}
---------------------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
内部拦截法:
2、内部拦截法(父viewgroup需要重写onInterceptTouchEvent)(不符合view事件分发机制)

说明:顾名思义就是在子view中拦截事件,父viewGroup默认是不拦截任何事件的,所以,当事件传递到子view时,
子view根据自己的实际情况来,如果该事件是需要子view来处理的,那么子view就自己消耗处理,如果该事件不需要由子view来处理,那么就调用getParent().requestDisallowInterceptTouchEvent()方法来通知父viewgroup来拦截
这个事件,也就是说,叫父容器来处理这个事件,这刚好和view的分发机制相反。
---------------------
//子view的代码·
public boolean dispatchTouchEvent(MotionEvent ev) {
int y= (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
yDown=y;
break;
case MotionEvent.ACTION_MOVE:
yMove=y;
Log.e("mes",yMove+"!!!");
int scrollY = getScrollY();
if (scrollY == 0&&yMove-yDown>0) { //根据业务需求判断是否需要通知父viewgroup来拦截处理该事件
//允许父View进行事件拦截
Log.e("mes",yMove-yDown+"拦截");
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(ev);
}
---------------------
//父viewgroup代码 (要确保down是不拦截,move和up时要拦截)
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(ev.getAction()==MotionEvent.ACTION_DOWN){
return false;
}else{
return true;
}
}
---------------------

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
package com.zhuoli.education.view;


import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import android.widget.ImageView;

public class ZoomImageView extends ImageView implements OnScaleGestureListener,
OnTouchListener, ViewTreeObserver.OnGlobalLayoutListener

{
private static final String TAG = ZoomImageView.class.getSimpleName();
private Context mContext=null;
public static final float SCALE_MAX = 4.0f;
/**
* 初始化时的缩放比例,如果图片宽或高大于屏幕,此值将小于0
*/
public static final float SCALE_MIN = 0.2f;
private float initScale = 1.0f;

/**
* 用于存放矩阵的9个值
*/
private final float[] matrixValues = new float[9];

private boolean once = true;

/**
* 缩放的手势检测
*/
private ScaleGestureDetector mScaleGestureDetector = null;

private final Matrix mScaleMatrix = new Matrix();

public ZoomImageView(Context context)
{
this(context, null);
}

public ZoomImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
super.setScaleType(ScaleType.MATRIX);
mContext = context;
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mScaleGestureDetector = new ScaleGestureDetector(context, this);
this.setOnTouchListener(this);
}

@Override
public boolean onScale(ScaleGestureDetector detector)
{
float scale = getScale();//之前的缩放值
float scaleFactor = detector.getScaleFactor(); //现在的缩放值

if (getDrawable() == null)
return true;

/**
* 缩放的范围控制
*/
if ((scale < SCALE_MAX && scaleFactor > 1.0f)
|| (scale > SCALE_MIN && scaleFactor < 1.0f))
{
/**
* 最大值最小值判断
*/
if (scaleFactor * scale < SCALE_MIN)
{
scaleFactor = SCALE_MIN/ scale;
}
if (scaleFactor * scale > SCALE_MAX)
{
scaleFactor = SCALE_MAX / scale;
}
/**
* 设置缩放比例
*/
//foucsX,focusY指的是以两个手指的中点为缩放中心,不是拖拽的点哈
mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(),
detector.getFocusY());
checkBorderAndCenterWhenScale();
setImageMatrix(mScaleMatrix);
}
return true;

}

@Override
public boolean onScaleBegin(ScaleGestureDetector detector)
{
return true;
}

@Override
public void onScaleEnd(ScaleGestureDetector detector)
{

}

private int lastPointerCount = 0;
private boolean isCanDrag = false;
//用来记录它位置,计算它的滑动方向
private float mLastX = 0;
private float mLastY = 0;
//检查是否可以上下 OR 左右滑动
private boolean isCheckLeftAndRight = false;
private boolean isCheckTopAndBottom = false;
private int mTouchSlop;
@Override
public boolean onTouch(View v, MotionEvent event)
{
mScaleGestureDetector.onTouchEvent(event);
float x = 0, y = 0;
// 拿到触摸点的个数
final int pointerCount = event.getPointerCount();
// 得到多个触摸点的x与y均值
for (int i = 0; i < pointerCount; i++)
{
x += event.getX(i);
y += event.getY(i);
}
x = x / pointerCount;
y = y / pointerCount;

/**
* 每当触摸点发生变化时,重置mLasX , mLastY
* 触摸点发生变化的时候,不允许拖动,图片会停留在那里
*/
if (pointerCount != lastPointerCount)
{
//这里直接判死刑,你就算是满足拖动(>TouchSlop)的动作,也不让你拖
isCanDrag = false;
mLastX = x;
mLastY = y;
}


lastPointerCount = pointerCount;
//之前的状态,之前的矩形
RectF rectF = getMatrixRectF();

switch (event.getAction())
{
//涉及到一个初始状态,边界条件
//这里一定要判断ACTION_DOWN的情况,因为我们阻止拦截肯定是在拖动的动作做出来之前,那么第一个动作虽然父 ViewGroup
// 肯定不会拦截,但是后续的第二个MOVE要不要拦截呢?如果在这里你不判断好(当满足条件时,直接封锁第二个MOVE事件被拦截的可能)
// ,那么后续的第二个事件可能就被直接拦截了
//我一开始就是只在ACTION_MOVE里面判断,而没有在ACTION_DOWN里面判断,所以一直出现错误:
//错误就是在放大之后,两个手指直接一起MOVE可行,但是重新单手指点击却直接被ViewPager拦截掉
case MotionEvent.ACTION_DOWN:
if(rectF.width()>getWidth() || rectF.height()>getHeight())
//这个函数其实是设置一种状态,true表示不能拦截,false表示可以拦截
//在这里将状态设置为不能拦截,当条件满足(ACTION_MOVE里面),再将状态设置为可拦截
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "ACTION_MOVE");
float dx = x - mLastX;
float dy = y - mLastY;

if (isCanDrag==false)
{
isCanDrag = isCanDrag(dx, dy);
}
//这个 isCanDrag 其实是指可以上下或者左右拖动,而且它指的是它符合拖动的动作,即 >= TuchSlop
//而不是说它确实可以拖动,有可能不符合拖动的环境
if (isCanDrag)
{
if (getDrawable() != null)
{
if (rectF.left == 0 && dx > 0)
{
//这个函数可以阻止父ViewGroup通过onInterceptTouchEvent来拦截事件
//考虑到父亲是ViewPager,当滑动满足侧滑的时候,它是会拦截事件的
//那么我们怎么来阻止它呢?就是即便满足条件,也不让他拦截后续事件
Log.d(TAG, "onTouch: can drag to right");
getParent().requestDisallowInterceptTouchEvent(false);
}

if (rectF.right == getWidth() && dx < 0)
{
Log.d(TAG, "onTouch: can drag to left");
getParent().requestDisallowInterceptTouchEvent(false);
}

isCheckLeftAndRight = isCheckTopAndBottom = true;
// 如果宽度小于屏幕宽度,则禁止左右移动
if (rectF.width() < getWidth())
{
dx = 0;
isCheckLeftAndRight = false;
}
// 如果高度小雨屏幕高度,则禁止上下移动
if (rectF.height() < getHeight())
{
dy = 0;
isCheckTopAndBottom = false;
}
mScaleMatrix.postTranslate(dx, dy);
checkMatrixBounds();
setImageMatrix(mScaleMatrix);
}
}
mLastX = x;
mLastY = y;
break;

case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "ViewGroup take the remained event");
lastPointerCount = 0;
break;
}

// return true 说明默认消费事件
//ViewPager只有在横向滑动才会拦截(ViePager的拦截函数拦截侧滑动作)
return true;

}

/**
* 在缩放时,进行图片显示范围的控制
*/
private void checkBorderAndCenterWhenScale()
{

RectF rect = getMatrixRectF();
float deltaX = 0;
float deltaY = 0;

int width = getWidth();
int height = getHeight();

// 如果宽或高大于屏幕,则控制范围
if (rect.width() >= width)
{
if (rect.left > 0)
{
deltaX = -rect.left;
}
if (rect.right < width)
{
deltaX = width - rect.right;
}
}
if (rect.height() >= height)
{
if (rect.top > 0)
{
deltaY = -rect.top;
}
if (rect.bottom < height)
{
deltaY = height - rect.bottom;
}
}
// 如果宽或高小于屏幕,则让其居中
if (rect.width() < width)
{
deltaX = width * 0.5f - rect.right + 0.5f * rect.width();
}
if (rect.height() < height)
{
deltaY = height * 0.5f - rect.bottom + 0.5f * rect.height();
}
Log.e(TAG, "deltaX = " + deltaX + " , deltaY = " + deltaY);

mScaleMatrix.postTranslate(deltaX, deltaY);

}

/**
* 根据当前图片的Matrix获得图片的范围
*
* @return
*/
private RectF getMatrixRectF()
{
Matrix matrix = mScaleMatrix;
RectF rect = new RectF();
Drawable d = getDrawable();
if (null != d)
{
rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
matrix.mapRect(rect);
}
return rect;
}



/**
* 获得当前的缩放比例
*
* @return
*/
public final float getScale()
{
mScaleMatrix.getValues(matrixValues);
return matrixValues[Matrix.MSCALE_X];
}

@Override
protected void onAttachedToWindow()
{
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}

@SuppressWarnings("deprecation")
@Override
protected void onDetachedFromWindow()
{
super.onDetachedFromWindow();
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}

@Override
public void onGlobalLayout()
{
if (once)
{
Drawable d = getDrawable();
if (d == null)
return;
Log.e(TAG, d.getIntrinsicWidth() + " , " + d.getIntrinsicHeight());
int width = getWidth();
int height = getHeight();
// 拿到图片的宽和高
int dw = d.getIntrinsicWidth();
int dh = d.getIntrinsicHeight();
float scale = 1.0f;
// 如果图片的宽或者高大于屏幕,则缩放至屏幕的宽或者高
if (dw > width && dh <= height)
{
scale = width * 1.0f / dw;
}
if (dh > height && dw <= width)
{
scale = height * 1.0f / dh;
}
// 如果宽和高都大于屏幕,则让其按按比例适应屏幕大小
if (dw > width && dh > height)
{
scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
}
// 宽和高都小于屏幕
else{
scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
}
initScale = scale;
// 图片移动至屏幕中心
mScaleMatrix.postTranslate((width - dw) / 2, (height - dh) / 2);
mScaleMatrix.postScale(scale, scale, getWidth() / 2, getHeight() / 2);
setImageMatrix(mScaleMatrix);
once = false;
}

}

/**
* 移动时,进行边界判断,主要判断宽或高大于屏幕的
*/
private void checkMatrixBounds()
{
RectF rect = getMatrixRectF();

float deltaX = 0, deltaY = 0;
final float viewWidth = getWidth();
final float viewHeight = getHeight();
// 判断移动或缩放后,图片显示是否超出屏幕边界
if (rect.top > 0 && isCheckTopAndBottom)
{
deltaY = -rect.top;
}
if (rect.bottom < viewHeight && isCheckTopAndBottom)
{
deltaY = viewHeight - rect.bottom;
}
if (rect.left > 0 && isCheckLeftAndRight)
{
deltaX = -rect.left;
}
if (rect.right < viewWidth && isCheckLeftAndRight)
{
deltaX = viewWidth - rect.right;
}
mScaleMatrix.postTranslate(deltaX, deltaY);
}

/**
* 是否是推动行为
* @param dx
* @param dy
* @return
*/
private boolean isCanDrag(float dx, float dy)
{
return Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
}


}

内存优化

思维导图如下:

内存优化

内存优化的一个本质和三个知识点

  • 本质:对象的引用未被释放,导致对象本身无法被有效的回收。
  • 三个知识点:内存泄漏、内存溢出、内存优化工具。

性能优化

内存泄漏

APK瘦身

ANR

SOCKET编程

Android招聘要求

  1. 2年以上Android开发经验,优秀者可适当放宽
  2. 熟练使用 Android Studio 进行开发 ,熟悉常用的调试方法,能快速定位程序问题
  3. 具备一定的内存优化、性能优化经验
  4. 熟悉Android的消息机制、View加载流程以及事件分发处理,能处理常用的自定义View以及滑动冲突等问题
  5. 熟悉 HTTP 协议,熟悉 OKHTTP框架,对于Retrofit与RxJava的使用有经验者加分,能对常用的第三方库进行封装
  6. 熟悉常用的第三方SDK的集成,例如支付接口、登陆接口、推送接口等等
  7. 能独立承担Android开发的工作,有独立开发过的App或者参与过(标明自己参与的模块)的已上线的App请在简历上注明(包括名称和相应的应用市场)
  8. 熟悉Framework层的优先,并在简历注明自己熟悉的部分
  9. 有个人blog或者github上有开源作品者优先,并在简历上注明
  10. 请按照招聘要求重新设计好自己的简历

面试题

  1. 简述 https 原理

  2. 说说线程池的工作原理

  3. 静态内部类和内部类的区别

  4. Java中是如何传递参数的

  5. 简述Android的消息机制

  6. Activity的启动模式

  7. 简述一下如何优化listView

  8. 如何分析内存泄漏

  9. 如何解决滑动冲突(引入ZoomedImageView实例)

  10. 项目中用到哪些开源库,并说说原理,如何进行封装,谈谈OKHTTP和Retrofit的区别