Android 面试集

我面试过的问题汇总,不定期更新

坐标问题

问题一般是如果获取到view当前的坐标,包括到parent或者到屏幕的距离


Android 面试集_第1张图片
201601060910182.png

View的静态坐标方法 解释
getLeft() 返回View自身左边到父布局左边的距离
getTop() 返回View自身顶边到父布局顶边的距离
getRight() 返回View自身右边到父布局左边的距离
getBottom() 返回View自身底边到父布局顶边的距离
getX() 返回值为getLeft()+getTranslationX(),当setTranslationX()时getLeft()不变,getX()变。
getY() 返回值为getTop()+getTranslationY(),当setTranslationY()时getTop()不变,getY()变。
同时也可以看见上图中给出了手指触摸屏幕时MotionEvent提供的一些方法解释,如下:
MotionEvent坐标方法 解释
getX() 当前触摸事件距离当前View左边的距离
getY() 当前触摸事件距离当前View顶边的距离
getRawX() 当前触摸事件距离整个屏幕左边的距离
getRawY() 当前触摸事件距离整个屏幕顶边的距离
int[] location = new int[2] ;
view.getLocationInWindow(location); //获取在当前窗口内的绝对坐标
view.getLocationOnScreen(location);//获取在整个屏幕内的绝对坐标
location [0]--->x坐标,location [1]--->y坐标

service

1 Activity和Service之间的通信
1.1 利用Handler通信:http://blog.sina.com.cn/s/blog_3fe961ae0100mvc5.html

1.2 Activity调用startService (Intent service)方法,将消息添加到Intent对象中,这样Service对象可以在调用onStartCommand (Intent intent, int flags, int startId)的时候可以得到这些消息。这种方法很简单,但如果有大量的信息要传递的话,就很麻烦了。因为Service端还要判断一下消息是什么,才能作进一步的动作。

1.3 Activity调用bindService (Intent service, ServiceConnection conn, int flags)方法,得到Service对象的一个引用,这样Activity可以直接调用到Service中的方法。具体代码:
http://blog.csdn.net/liuhe688/article/details/6623924。

1.4 Service向Activity发送消息,除了可以利用Handler外,还可以使用广播,当然Activity要注册相应的接收器。比如Service要向多个Activity发送同样的消息的话,用这种方法就更好。具体方法可以看一下这篇文章:
http://blog.csdn.net/liuhe688/article/details/6641806。

binder

其实就是dbus的高级版,采用cs架构,共享内存,client把数据放在共享内存区域,server去取,然后发送给另一个client

作者:Gityuan
链接:https://www.zhihu.com/question/39440766/answer/89210950
来源:知乎
著作权归作者所有,转载请联系作者获得授权。

(1)从性能的角度
数据拷贝次数:Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,但共享内存方式一次内存拷贝都不需要;从性能角度看,Binder性能仅次于共享内存。
前面几篇也说过了,IPC 中最基本的问题在于进程间使用的虚拟地址空间是相互独立的,不能直接访问,所以要相互访问,就要借助 kernel ,就是要让数据用用户空间进入到内核空间,然后再去到另一个进程的用户空间。传统的 IPC 是这样的,其实 binder 也是这样的,不过它把内核空间的地址和用户空间的虚拟地址映射到了同一段物理地址上,所以就只需要把数据从原始用户空间复制到内核空间,把目标进程用户空间和内核空间映射到同一段物理地址,这样第一次复制到内核空间,其实目标的用户空间上也有这段数据了。这就是 binder 比传统 IPC 高效的一个原因。

(2)从稳定性的角度
Binder是基于C/S架构的,简单解释下C/S架构,是指客户端(Client)和服务端(Server)组成的架构,Client端有什么需求,直接发送给Server端去完成,架构清晰明朗,Server端与Client端相对独立,稳定性较好;而共享内存实现方式复杂,没有客户与服务端之别, 需要充分考虑到访问临界资源的并发同步问题,否则可能会出现死锁等问题;从这稳定性角度看,Binder架构优越于共享内存。

仅仅从以上两点,各有优劣,还不足以支撑google去采用binder的IPC机制,那么更重要的原因是:

(3)从安全的角度
传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Android作为一个开放的开源体系,拥有非常多的开发平台,App来源甚广,因此手机的安全显得额外重要;对于普通用户,绝不希望从App商店下载偷窥隐射数据、后台造成手机耗电等等问题,传统Linux IPC无任何保护措施,完全由上层协议来确保。

Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志,前面提到C/S架构,Android系统中对外只暴露Client端,Client端将任务发送给Server端,Server端会根据权限控制策略,判断UID/PID是否满足访问权限,目前权限控制很多时候是通过弹出权限询问对话框,

动画

在渲染前获取 View 的宽高
这是一个比较有意义的问题,或者说有难度的问题,问题的背景为:有时候我们需要在view渲染前去获取其宽高,典型的情形是,我们想在onCreate、onStart、onResume中去获取view的宽高。如果大家尝试过,会发现,这个时候view还没有measure好,宽高都为0,那到底该怎么做才能正确获取其宽高呢,下面给出三种方法
Activity/View#onWindowFocusChanged :这个方法表明,view已经初始化完毕了,宽高已经准备好了
view.post(runnable) :通过post可以将一个runnable投递到消息队列的尾部,然后等待looper调用此runnable的时候,view也已经初始化好了
view.measure(int widthMeasureSpec, int heightMeasureSpec) :通过手动去measure来视图得到view的宽高

前两种方法都比较好理解也比较简单,这里主要介绍下第三种方法的详细用法:
采用 view.measure 去提前获取 view 的宽高,根据 view 的 layoutParams 来分
match_parent
直接放弃,无法 measure 出具体的宽高
具体的数值( dp/px )
比如宽高都是 100px ,如下 measure :
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec, heightMeasureSpec);
wrap_content
如下 measure :
int widthMeasureSpec = MeasureSpec.makeMeasureSpec( (1 << 30) - 1, MeasureSpec.AT_MOST);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec( (1 << 30) - 1, MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec, heightMeasureSpec);
注意到(1 << 30) - 1,通过分析MeasureSpec的实现可以知道,view的尺寸使用30位二进制表示的,也就是说最大是30个1即 2^30 - 1,也就是(1 << 30) - 1,在最大化模式下,我们用view理论上能支持的最大值去构造MeasureSpec是合理的。
关于view的measure,网络上有两个错误的用法,如下,为什么说是错误的,首先违背了系统的内部实现规范(因为无法通过错误的MeasureSpec去得出合法的SpecMode从而导致measure出错),其次不能保证一定能 measure 出正确的结果。
第一种错误用法
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
view.measure(widthMeasureSpec, heightMeasureSpec);
第二种错误用法
view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)

设计模式

创建型模式

工厂方法模式、大家都懂的
抽象工厂模式、把工厂抽象出来
单例模式、懒得说了
建造者模式、我的git
原型模式:就是clone

结构型模式

适配器模式:将一个接口转换成另一个接口,统一很多接口也算适配器模式
装饰模式:扩展接口,例如重写view。实现ondraw等方法也算一种简单的装饰模式
正常是用Decorator来实现,这样可以添加或者撤销,比继承灵活
外观模式(Facade)

外观模式是为了解决类与类之家的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口,
桥接模式(Bridge)

桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。滴滴地图切换
享元模式(Flyweight)

享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。

行为型模式

策略模式
模板方法模式
观察者模式
责任链模式
备忘录模式
状态模式
后面几种没用过

生命周期问题

Activity

我们打开应用时先后执行了onCreate()->onStart()->onResume三个方法
当我们按BACK键时,我们这个应用程序将结束,这时候我们将先后调用onPause()->onStop()->onDestory()三个方法
而当我们按HOME的时候,Activity先后执行了onPause()->onStop()这两个方法,这时候应用程序并没有销毁。
而当我们再次启动ActivityDemo应用程序时,则先后分别执行了onRestart()->onStart()->onResume()三个方法

由于Android本身的特性,使得现在不少应用都没有直接退出应用程序的功能,按照一般的逻辑,当Activity栈中有且只有一个Activity时,当按下Back键此Activity会执行onDestroy,那么下次点击此应用程图标将从重新启动,因此,当前不少应用程序都是采取如Home键的效果,当点击了Back键,系统返回到桌面,然后点击应用程序图标,直接回到之前的Activity界面,这种效果是怎么实现的呢?

通过重写按下Back键的回调函数,转成Home键的效果即可。

@Override
public void onBackPressed() {
Intent home = new Intent(Intent.ACTION_MAIN);
home.addCategory(Intent.CATEGORY_HOME);
startActivity(home);
}
当然,此种方式通过Home键效果强行影响到Back键对Activity生命周期的影响。注意,此方法只是针对按Back键需要退回到桌面时的Activity且达到Home效果才重写。

或者,为达到此类效果,Activity实际上提供了直接的方法。

1 activity.moveTaskToBack(true);
moveTaskToBack()此方法直接将当前Activity所在的Task移到后台,同时保留activity顺序和状态。

1.standard 每次重建

2.singleTop 在顶部不重建

3.singleTask 自己不重建,会销毁其他的activity

4.singleInstance 单独栈

fragment
场景演示 : 切换到该Fragment
11-29 14:26:35.095: D/AppListFragment(7649): onAttach
11-29 14:26:35.095: D/AppListFragment(7649): onCreate
11-29 14:26:35.095: D/AppListFragment(7649): onCreateView
11-29 14:26:35.100: D/AppListFragment(7649): onActivityCreated
11-29 14:26:35.120: D/AppListFragment(7649): onStart
11-29 14:26:35.120: D/AppListFragment(7649): onResume
屏幕灭掉:
11-29 14:27:35.185: D/AppListFragment(7649): onPause
11-29 14:27:35.205: D/AppListFragment(7649): onSaveInstanceState
11-29 14:27:35.205: D/AppListFragment(7649): onStop

屏幕解锁
11-29 14:33:13.240: D/AppListFragment(7649): onStart
11-29 14:33:13.275: D/AppListFragment(7649): onResume

切换到其他Fragment:
11-29 14:33:33.655: D/AppListFragment(7649): onPause
11-29 14:33:33.655: D/AppListFragment(7649): onStop
11-29 14:33:33.660: D/AppListFragment(7649): onDestroyView

切换回本身的Fragment:
11-29 14:33:55.820: D/AppListFragment(7649): onCreateView
11-29 14:33:55.825: D/AppListFragment(7649): onActivityCreated
11-29 14:33:55.825: D/AppListFragment(7649): onStart
11-29 14:33:55.825: D/AppListFragment(7649): onResume
回到桌面
11-29 14:34:26.590: D/AppListFragment(7649): onPause
11-29 14:34:26.880: D/AppListFragment(7649): onSaveInstanceState
11-29 14:34:26.880: D/AppListFragment(7649): onStop
回到应用
11-29 14:36:51.940: D/AppListFragment(7649): onStart
11-29 14:36:51.940: D/AppListFragment(7649): onResume

退出应用
11-29 14:37:03.020: D/AppListFragment(7649): onPause
11-29 14:37:03.155: D/AppListFragment(7649): onStop
11-29 14:37:03.155: D/AppListFragment(7649): onDestroyView
11-29 14:37:03.165: D/AppListFragment(7649): onDestroy
11-29 14:37:03.165: D/AppListFragment(7649): onDetach

序列化

1.序列化的目的

(1).永久的保存对象数据(将对象数据保存在文件当中,或者是磁盘中

(2).通过序列化操作将对象数据在网络上进行传输(由于网络传输是以字节流的方式对数据进行传输的.因此序列化的目的是将对象数据转换成字节流的形式)

(3).将对象数据在进程之间进行传递(Activity之间传递对象数据时,需要在当前的Activity中对对象数据进行序列化操作.在另一个Activity中需要进行反序列化操作讲数据取出)

(4).Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长(即每个对象都在JVM中)但在现实应用中,就可能要停止JVM运行,但有要保存某些指定的对象,并在将来重新读取被保存的对象。这是Java对象序列化就能够实现该功能。(可选择入数据库、或文件的形式保存)

(5).序列化对象的时候只是针对变量进行序列化,不针对方法进行序列化.

(6).在Intent之间,基本的数据类型直接进行相关传递即可,但是一旦数据类型比较复杂的时候,就需要进行序列化操作了.

list如何序列化

引用模式

⑴强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 ps:强引用其实也就是我们平时A a = new A()这个意思。

⑵软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(下文给出示例)。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

⑶弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

⑷虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

weakreference可以解决context导致的内存泄漏问题

同步锁

1.synchronized的缺陷:当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,必须等待或者阻塞,这对高并发的系统是致命的,这很容易导致系统的崩溃。如果某个线程在同步方法里面发生了死循环,那么它就永远不会释放这个对象锁,那么其他线程就要永远的等待。这是一个致命的问题。

  1. static方法和非static的锁不是同一个

concurrenthashmap
分段锁

转屏相关

固定横屏或者竖屏 android:screenOrientation="landscape"横屏设置;
android:screenOrientation="portrait"竖屏设置;

  1. 代码动态设置
    如果你需要动态改变横竖屏设置,那么,只需要在代码中调用setRequestedOrientation()函数:

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

//横屏设置

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

//竖屏设置

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);

//默认设置

  1. 重写onConfigurationChanged
    如果你不希望旋转屏幕的时候Activity被不断的onCreate(这种情况往往会造成屏幕切换时的卡顿),那么,可以使用此方法:
    首先,在AndroidMainfest.xml中添加configChanges:
    android:configChanges="keyboardHidden|orientation|screenSize"
    4.OrientationEventListener

layout 布局效率

Framelayout最简单

(1)RelativeLayout慢于LinearLayout是因为它会让子View调用2次measure过程,而后者只需一次,但是有weight属性存在时,后者同样会进行两次measure。
(2)RelativeLayout的子View如果高度和RelativeLayout不同,会引发效率问题,可以使用padding代替margin以优化此问题。
(3)在不响应层级深度的情况下,使用Linearlayout而不是RelativeLayout。

http://www.cnblogs.com/deman/p/5860976.html

Anr 类型

5.ANR 是什么?怎样避免和解决 ANR(重要)

ANR->Application Not Responding

也就是在规定的时间内,没有响应。

三种类型:

1). KeyDispatchTimeout(5 seconds) --主要类型按键或触摸事件在特定时间内无响应

2). BroadcastTimeout(10 seconds) --BroadcastReceiver在特定时间内无法处理完成

3). ServiceTimeout(20 seconds) --小概率类型 Service在特定的时间内无法处理完成

为什么会超时:事件没有机会处理 & 事件处理超时

怎么避免ANR

https://www.nowcoder.com/discuss/3244

Async task

handler+线程池
线程池有最多128总任务数 。
执行任务数量不同版本不一样。。有版本是最多5个同时执行。有的版本是核数+1。。。
问题
生命周期

关于AsyncTask存在一个这样广泛的误解,很多人认为一个在Activity中的AsyncTask会随着Activity的销毁而销毁。然后事实并非如此。AsyncTask会一直执行doInBackground()方法直到方法执行结束。一旦上述方法结束,会依据情况进行不同的操作。

如果cancel(boolean)调用了,则执行onCancelled(Result)方法
如果cancel(boolean)没有调用,则执行onPostExecute(Result)方法

还有一种常见的情况就是,在Activity中使用非静态匿名内部AsyncTask类,由于Java内部类的特点,AsyncTask内部类会持有外部类的隐式引用。详细请参考细话Java:”失效”的private修饰符**,由于AsyncTask的生命周期可能比Activity的长,当Activity进行销毁AsyncTask还在执行时,由于AsyncTask持有Activity的引用,导致Activity对象无法回收,进而产生内存泄露。作者:技术小黑屋链接:https://zhuanlan.zhihu.com/p/25099159来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

简单来说,activity不能有非静态匿名内部类

结果丢失

另一个问题就是在屏幕旋转等造成Activity重新创建时AsyncTask数据丢失的问题。当Activity销毁并创新创建后,还在运行的AsyncTask会持有一个Activity的非法引用即之前的Activity实例。导致onPostExecute()没有任何作用。

可以自己实现或者使用loaders

action_cancel

滑出当前控件范围会调用,后续没有事件

timeoutexception

AssetManager$AssetInputStream.finalize()极有可能是在执行synchronized (AssetManager.this)时卡住。目前对AssetManager加锁有两种可能:

  1. 业务线拿到AssetManager后加了锁(可能性低),导致finalize()卡住。
  2. 在调用AssetManager的时候,为了保证线程安全内部也加了锁,如果在短时间内大量的请求AssetManager,再加上部分资源较大(不是必要条件,但是会加重问题),会导致finalize()不容易抢到锁,导致卡住。

touch事件的调用

执行顺序是:
Activity.dispatchTouchEvent()
ViewGroup.dispatchTouchEvent()
View.dispatchTouchEvent()
View.onTouchEvent()
ViewGroup.onTouchEvent()
Activity.onTouchEvent()

onInterceptTouchEvent 返回false,则后续再来的事件(比如ACTION_UP)会继续传递给子view的ontouchEvent ,
onInterceptTouchEvent 返回true,则后续再来的事件(比如ACTION_UP)就不会传递给子view.

view的onTouchEvent返回true,则表示事件已经消化干净,viewgroup的onTouchEvent将不会被调用,否则相反.

onTouchEvent() -----> ACTION_DOWN 时 返回false,则后续的手势动作都不会传递进这个方法. (ViewGroup和View一样)

ViewGroup 的 onTouchEvent() -----> 返回 true, 则后续手势都不会传递进 onInterceptTouchEvent(),而是直接在ViewGroup 的 onTouchEvent() 中逐一处理.

6.0新特性

app links

web app交互方式有哪些,promt

webview如何启动其他app

webview防止白屏

运用设计模式下拉刷新实现

热补丁技术细节 ,如何加载,能否hack app的方法

mvp和mvvm优缺点,mvvm原理,mvp的接口方法太多怎么优化。

service相关

onserviceconnection方法,并不是一定在主线程调用,如果是localservice的话,是在主线程,但是如果是remoteservbice,是binder线程调用的这个方法。
unbindservice方法不能多次调用

//该方法可以验证service是否运行
 public boolean isServiceWork(Context mContext, String serviceName) {
        boolean isWork = false;
        ActivityManager myAM = (ActivityManager) mContext
                .getSystemService(Context.ACTIVITY_SERVICE);
        List myList = myAM.getRunningServices(40);
        if (myList.size() <= 0) {
            return false;
        }
        for (int i = 0; i < myList.size(); i++) {
            String mName = myList.get(i).service.getClassName();
            if (mName.equals(serviceName)) {
                isWork = true;
                break;
            }
        }
        return isWork;
    }

service生命周期onCreate->onstartcommand(onStart) or onbind ->onUnbind->onDestrtoy()

如果是startservice启动的话,调用onstart方法,binder启动的话,调用onBinder方法,startservice可以多次调用onstart,但是binservice多次,只调用一次onbind

app 自定义权限

你可能感兴趣的:(Android 面试集)