关于这十来天(7月17日-7月27日)的一些Android面试知识点

相比较去年中旬,这阶段安卓的岗位需求量确实比较大,基本上都是招聘者找上门来,所以做好一些基本的准备,巩固好知识点,是很有机会的。下面是一些经常被问到的问题:

1.在子线程中能不能直接new Handler()

答:不能。如果在子线程中直接new Handler(),会抛出异常java.lang.RuntimeException:Can't create handler inside thread that has not called Looper.prepare();因为在Hanlder的构造方法中,会先检查有没有创建消息循环,如若没有,则会报错,所以需要先进行Looper.prepare()启动消息循环。

 

2.讲讲Handler机制

答:Looper是用来封装消息循环和消息队列的一个类,而handler可以看成一个用来向消息队列插入消息的工具类,一个handler线程拥有一个looper对象,对应一个MessageQueue消息队列。在主线程中,会自动为其创建Looper对象,并开启消息循环,handler通过绑定的Looper对象向消息队列插入消息,looper.looper()方法是一个死循环,不断从消息队列中取消息,如果有消息就读取,否则就阻塞。

 

3.谈谈自定义view的过程

①通过继承原生view控件。比如可以去继承一个Linearlayout,然后用LayoutInflater去加载xml文件,再去调用具体的方法;

②继承View:先建立一个属性文件res/values/styles.xml,声明一个自定义属性的集合,然后在构造方法通过TypeArray加载自定义属性集合,并跟属性名去设置数值。

关键的方法:onMeasure(int widthMeasureSpec,int heightMeasureSpec),对当前View进行尺寸的测量,其中widthMeasureSpec和heightMeasureSpec两个参数包含了宽和高的信息,以及测量模式,

widthMeasureSpec、heightMeasureSpec,int整型数据占用32bit字节,而google将前面的2个bit用于区分不同的布局模式,后面的30个bit存放的是尺寸的数据。

测量模式:UNSPECIFIED 父容器没有对当前view做任何限制,当前view可以取任何尺寸

EXACITY 当前的尺寸就是view的尺寸

AT_MOST 当前的尺寸是view的最大尺寸

代码:

int widthSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);

int heightSpec = MeasureSpec.makeMeasureSpec(200,MeasureSpec.AT_MOST);

view.measure(widthSpec,height);

例:自定义流式布局flowlayout:

先写一个view自定义ViewGroup;

在onMeasure拿到ViewGroup的尺寸和测量模式:

需要遍历子控件,根据相同方法拿到每个view的尺寸和测量模式,然后进行测量:

①计算每一行已使用的宽度,如果当前行的子view的宽度加上下一个view的宽度小于父容器的总宽度,则将下一个view加入行中。否则换行添加view操作。

②换行添加view到行中,有两种情况,一种是当前行中没有其他元素,单个view尺寸超过父控件,则该view单独一行加入,而后进行换行操作;另一种是当前行已经有元素,则需要新建一行,将view加进去。

③宽度设置好后,对高度进行测量计算,高度以当前行的子控件的最大高度为准

④onLayout() 进行布局,因为先前已经得到测量尺寸,只需在此设置。

 

4.android事件传递机制

事件传递的方法:父控件→子控件

事件响应的方法:子控件→父控件

在ViewGroup中事件有dispatchTouchEvent() onInterceptTouchEvent() onTouchEvent()

在View中事件有 dispatchTouchEvent onTouchEvent()

①dispatchTouchEvent()最先调用,只负责分发,dispatchTouchEvent()首先接收到ACTION_DOWN,执行super.dispatchTouchEvent(ev),事件向下分发。dispatchTouchEvent()返回true,后续事件(ACTION_MOVE、ACTION_UP)会再传递,如果返回false,dispatchTouchEvent()就接收不到ACTION_UP、ACTION_MOVE。

分发事件一直都存在。

②onInterceptTouchEvent()拦截事件,如果返回true,在onTouchListener处理,否则向下传递。

③onTouchListener() 消费,返回true,在本View处理事件,返回false,向上传递。

 

5.谈谈mvc和mvp

mvc是软件架构中比较常见的一种框架,通过controller的控制去操作model层的数据,并且返回给view层显示,反过来说,当用户发出事件时,view层会发送指令给controller层,controller又会去通知model层进行数据更新,然后直接显示在view层上。比如说你动态的去展示一个button按钮的隐藏和显示,这些方法没有直接写在xml中,必须在activity中进行编码,从而造成activity既是view层也是controller层。

mvp的话,就是对mvc上诉的缺点进行改进,所有有关用户事件的转发都放在presenter层进行处理,相当于一个中间桥梁,view层的事件传到presenter层,presenter层去操作model层数据,并返回给view层展示,整个过程中view层和model层没有直接交互,就是一个解耦的过程。需要根据所需的逻辑进行接口化的定义,维护接口的成本会增加。

 

6.内存泄露


①单例设计模式造成的内存泄漏

public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}

1、如果我们传入的Context是Application的Context的话,就没有任何问题,因为Application的Context生命周期和应用程序生命周期一样长。

2、如果我们传入的Context是Activity的Context的话,这时如果我们因为需求销毁了该Activity的话,Context也会随着Activity被销毁,但是单例还在持有对该类对象的引用,这时就会造成内存泄漏。

public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
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 TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new TestResource();
}
//...
}
class TestResource {
//...
}

在Activity中创建了非静态内部类,非静态内部类默认持有Activity类的引用,但是他的生命周期还是和应用程序一样长,所以当Activity销毁时,静态内部类的对象引用不会被GC回收,就会造成了内存溢出,

解决办法:

1、将内部类改为静态内部类。

2、将这个内部类封装成一个单例,Context使用Application的Context

 

③Handler造成的内存泄漏

public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadData();
}
private void loadData(){
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}

handler也是一个非静态匿名内部类,他跟上面的一样,也会持有Activity的引用,我们知道handler是运行在一个Looper线程中的,而Looper线程是轮询来处理消息队列中的消息的,假设我们处理的消息有十条,而当他执行到第6条的时候,用户点击了back返回键,销毁了当前的Activity,这个时候消息还没有处理完,handler还在持有Activity的引用,这个时候就会导致无法被GC回收,造成了内存泄漏。正确的做法是:在onDestory调用

mHandler.removeCallbacksAndMessages()

 

④线程造成的内存泄漏

//——————test1
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
}.execute();
//——————test2
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();

上面是两个内部类,当我们的Activity销毁时,这两个任务没有执行完毕,就会使Activity的内存资源无法被回收,造成了内存泄漏。

正确的做法是使用静态内部类:如下

static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
//——————
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();

这样就避免了内存泄漏,当然在Activity销毁时也要记得在OnDestry中调用AsyncTask.cancal()方法来取消相应的任务。避免在后台运行浪费资源。

 

⑤资源未关闭造成的内存泄漏

在使用完BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源时,一定要在Activity中的OnDestry中及时的关闭、注销或者释放内存,

否则这些资源不会被GC回收,就会造成内存泄漏。

 

7.性能优化

①布局优化:如果布局能用LinearLayout或RelativeLayout使用,使用LinearLayout,因为RelativeLayout功能比较复杂,它的布局过程需要花费更多的CPU时间;采用include、merge标签进行布局的重用,ViewStub为布局的按需加载,需要时才会将ViewStub中的布局加载到内存,提高了程序初始化效率。

②内存泄漏优化:AndroidStudio的Android Monitor查看内存使用情况,或者下载MAT工具对内存泄漏情况进行分析;不存在的对象进行释放和回收;单例、静态变量、匿名内部类、非静态内部类、资源未关闭等造成的内存泄漏

③采取AsyncTask、Handler进行UI界面的更新

④ListView/RecycleView及Bitmap优化:使用ViewHolder模式来提高效率;异步加载,将耗时任务放在异步线程中;ListView/RecycleView滑动时停止加载数据;bitmap图片的压缩

⑤线程优化:采用线程池

⑥其它性能优化:

避免过度的创建对象;

不要过度使用枚举,

枚举占用的内存空间要比整型大;

常量请使用static final来修饰;

使用一些Android特有的数据结构,比如SparseArray和Pair等;

适当采用软引用和弱引用;

采用内存缓存和磁盘缓存;

尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄漏。

 

8.线程

Thread:子线程,可以去执行一些耗时任务。简单的在子线程执行下载操作,由于子线程是与UI线程保持简单的同步关系,所以在子线程执行下载耗时任务是不安全。

AsyncTask:线程池的一个封装,采用异步任务进行操作,

线程池:线程池的优点有

(1)复用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。

(2)能够有效的控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。

(3)能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。

 

单例模式·懒汉式

public class Singleton {
private static Singleton instance=null;
private Singleton() {
}
public static Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

在单线程程序中,上面两种形式基本可以满足要求了,但是在多线程环境下,单例类就有可能会失效,这个时候就要对其加锁了,来确保线程安全。

synchronized同步块进行加锁
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance(){
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

9.Android分渠道打包

举了友盟多渠道打包的例子:

1.添加友盟依赖库

2.在Manifest.xml中加入设置友盟的key值和渠道名

3.在build.gradle根目录下加入配置,及需要的渠道名

4.然后通过AndroidStudio的release打包时,选中渠道名,完成多渠道打包

 

10.Android数据加解密

Android加密算法有多种多样,常见的有MD5、RSA、AES、3DES四种

 

11.WebView与JavaScript的交互

Android去调用JS的代码:

①通过webview的loadUrl()方法 Android4.4之前用

html中写有一个方法call(),call()里面是一个弹窗:alert("Android调用了JS的call方法");

android代码中

WebSettings webSettings = mWebView.getSettings();

// 设置与Js交互的权限

webSettings.setJavaScriptEnabled(true);

// 设置允许JS弹窗

webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

然后通过mWebView.loadUrl("javascript:call()");

最后设置setWebChromeClient()去响应alert()弹窗事件

②通过webview的evaluateJavascript() Android4.4后用

该方法比第一种方法更高效、更简洁

因为该方法的执行不会刷新页面,而第一种方法loadUrl会

Android4.4后才用

// 只需要将第一种方法的loadUrl()换成下面该方法即可

mWebView.evaluateJavascript("javascript:call()", new ValueCallback<String>() {

@Override

public void onReceiveValue(String value) {

//此处为 js 返回的结果

}

});

}

 

JS去调用Android的代码:

①通过WebView的addJavascriptInterface()进行对象映射

步骤1:定义一个与JS对象映射关系的Android类:AndroidtoJs

// 继承自Object类

public class AndroidtoJs extends Object {

// 定义JS需要调用的方法

// 被JS调用的方法必须加入@JavascriptInterface注解

@JavascriptInterface

public void hello(String msg) {

System.out.println("JS调用了Android的hello方法");

}

}

步骤2:将需要调用的JS代码以.html格式放到src/main/assets文件夹里

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

<title>Carson</title>

<script>

function callAndroid(){

// 由于对象映射,所以调用test对象等于调用Android映射的对象

test.hello("js调用了android中的hello方法");

}

</script>

</head>

<body>

//点击按钮则调用callAndroid函数

<button type="button" id="button1" οnclick="callAndroid()"></button>

</body>

</html>

步骤3:在Android里通过WebView设置Android类与JS代码的映射

// 设置与Js交互的权限

webSettings.setJavaScriptEnabled(true);

// 通过addJavascriptInterface()将Java对象映射到JS对象

//参数1:Javascript对象名

//参数2:Java对象名

mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS类对象映射到js的test对象

// 加载JS代码

// 格式规定为:file:///android_asset/文件名.html

mWebView.loadUrl("file:///android_asset/javascript.html");

 

②通过WebViewClient的shouldOverrideUrlLoading()方法回调拦截url

 

③通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息

 

12.Android适配

①dp

②AutoLayout库

③LinearLayout、RelativeLayout

④适配手机的单面板(默认)布局:res/layout/main.xml

适配尺寸>7寸平板的双面板布局(Android 3.2前):res/layout-large/main.xml

适配尺寸>7寸平板的双面板布局(Android 3.2后)res/layout-sw600dp/main.xml

④使用"wrap_content"、"match_parent"和"weight“来控制视图组件的宽度和高度

⑤资源图片可以使用.9.png后缀名的图片

⑥ImageView设置ScaleType为CenterCrop

 

13.ScrollView嵌套RecycleView出现的解决方案

①利用RecyclerView内部方法

recyclerView.setHasFixedSize(true);

recyclerView.setNestedScrollingEnabled(false);

setHasFixedSize(true)方法使得RecyclerView能够固定自身size不受adapter变化的影响

setNestedScrollingeEnabled(false)方法则是进一步调用了RecyclerView内部NestedScrollingChildHelper对象的setNestedScrollingeEnabled(false)方法,如下

public void setNestedScrollingEnabled(boolean enabled) {

getScrollingChildHelper().setNestedScrollingEnabled(enabled);

}

进而,NestedScrollingChildHelper对象通过该方法关闭RecyclerView的嵌套滑动特性,如下

public void setNestedScrollingEnabled(boolean enabled) {

if (mIsNestedScrollingEnabled) {

ViewCompat.stopNestedScroll(mView);

}

mIsNestedScrollingEnabled = enabled;

}

如此一来,限制了RecyclerView自身的滑动,整个页面滑动仅依靠ScrollView实现,即可解决滑动卡顿的问题

②通过重写ScrollView的onInterceptTouchEvent(MotionEvent ev)方法,拦截滑动事件,使得滑动事件能够直接传递给RecyclerView

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 保存当前touch的纵坐标值
touch = (int) ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
// 滑动距离大于slop值时,返回true
if (Math.abs((int) ev.getRawY() - touch) > slop) return true;
break;
}
return super.onInterceptTouchEvent(ev);
}
 
/**
* 获取相应context的touch slop值(即在用户滑动之前,能够滑动的以像素为单位的距离)
* @param context ScrollView对应的context
*/
private void setSlop(Context context) {
slop = ViewConfiguration.get(context).getScaledTouchSlop();
}

14.EventBus

EventBus能够简化各组件间的通信,让我们的代码书写变得简单,能有效的分离事件发送方和接收方(也就是解耦的意思),能避免复杂和容易出错的依赖性和生命周期问题。

https://www.jianshu.com/p/f9ae5691e1bb

 

15.数据库sqlite

MySQL对查询结果排序,从表中查询出来的数据,可能是无序的,或者其排列顺序表示用户期望的。

使用ORDER BY对查询结果进行排序

SELECT 字段名1,字段名2,……

FROM 表名

ORDER BY 字段名1 [ASC|DESC],字段名2[ASC|DESC]

学生表按分数降序排列:

select * from Student order by score desc

 

16.Android最新技术

有些面试官也会问一些Android最新的技术,比如谷歌大会发布了哪些内容等等