关于这十来天(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最新的技术,比如谷歌大会发布了哪些内容等等