Android面试高频知识点

持续更新、、、

一 Activity的生命周期 ?

Activity的生命周期 ,包括典型的生命周期和异常情况下的生命周期,以及singletop模式和simgletask模式的生命周期。

典型生命周期包括c s r p s d ,注意特定场景下的生命周期,比如打开新的activity 时, 旧activity 调用onPause -> onStop,新activity 调用c s r ,如果回到旧activity 则调用onRestart -> onStart -> onResume。

异常情况下的生命周期分析,比如屏幕旋转时,Activity会被销毁,其onPause、onStop、onDestroy均会被调用,同时在onStop前调用onSaveInstanceState方法保存数据,而新activity 调用c s r。

singletop模式和simgletask模式的生命周期,如果要启动的Activity已经存在,则只调用onNewIntent方法,否则重新创建,调用 c s r。

 

二 Activity 的四种启动模式?

1-标准模式,onCreate、onStart、onResume都会被调用。比如Activity A启动了Activity B(B是标准模式),那么B就会进入到A所在的任务栈中。

2-singleTop:栈顶复用模式。在这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被回调,否则重新创建。

3-singleTask,在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例,系统也会回调其onNewIntent方法,否则重新创建。

4-singleInstance模式,单独开启一个任务栈。

 

 

三  activity四种启动模式的应用场景?

1.standard模式, mainfest中没有配置启动模式就默认为标准模式。

2.singleTop模式, 登录页面、WXPayEntryActivity、推送通知栏等。

3.singleTask模式,程序模块逻辑入口:主页面、WebView页面、扫一扫页面、电商中:购物界面,确认订单界面,付款界面等。

4.singleInstance模式, 系统Launcher、锁屏键、来电显示等系统应用。

 

四 什么是activity 的任务栈?

TaskAffinity属性的值就是任务栈的名字,默认为应用包名,如果为某个activity指定TaskAffinity属性,则表示开启新的任务栈。当TaskAffinity和singleTask启动模式配对使用的时候,待启动的Activity会运行在名字和TaskAffinity相同的任务栈。

当TaskAffinity和allowTaskReparenting结合的时候,会导致任务栈的转移。比如现在有2个应用A和B, 应用A启动了应用B的Activity C,然后按Home键回到桌面,然后再单击应用B的桌面图标,这个时候并不是启动了应用B的主Activity,而是重新显示了已经被应用A启动的Activity C,或者说,C从A的任务栈转移到了B的任务栈中。

 

五 Service的两种状态?

启动状态:当应用组件(如 Activity)通过调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响,除非手动调用才能停止服务, 服务不会将结果返回给调用方,无法交互。

绑定状态:当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务允许组件与服务进行交互、发送请求、获取结果,甚至是可以进程间通信 (IPC) 。多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会自动销毁。

注意,如果服务将执行任何耗时事件,则应在服务内创建新线程来完成这项工作,简而言之,Service里面的耗时操作应该另起线程执行。

 

六 Service的两种状态互相可以转换吗?

1. 先绑定服务后启动服务,那么绑定服务将会转为启动服务运行,这时如果之前绑定的宿主(Activity)被销毁了,也不会影响服务的运行,直到收到调用停止服务或者内存不足时才会销毁该服务。

2.先启动服务后绑定服务并不会转为绑定服务,但是还是会与宿主绑定,只是即使宿主解除绑定后,服务依然按启动服务的生命周期在后台运行,直到调用了stopService()或是服务本身调用了stopSelf()方法抑或内存不足时才会销毁服务。这种先启动,后绑定服务,用于音乐播放器控制歌曲。

 

七 Service生命周期

第一次调用startService方法时,onCreate方法、onStartCommand方法将依次被调用,而多次调用startService时,只有onStartCommand方法被调用,最后我们调用stopService方法停止服务时onDestory方法被回调,这就是启动状态下Service的执行周期。

è¿éåå¾çæè¿°

 

八 如何保证服务不被杀死?

1.因内存资源不足而杀死Service
这种情况比较容易处理,可将onStartCommand() 方法的返回值设为 START_STICKY或START_REDELIVER_INTENT ,该值表示服务在内存资源紧张时被杀死后,在内存资源足够时再恢复。

2.也可将Service设置为前台服务,这样就有比较高的优先级,在内存资源紧张时也不会被杀掉。

3.可以在 onDestory() 中发送广播重新启动,这样杀死服务后会立即启动。当然onDestory() 方法在某些情况下不会执行。

4.我们可开启两个服务,相互监听,相互启动。服务A监听B的广播来启动B,服务B监听A的广播来启动A。

 

九 IntentService作用及原理?

IntentService继承自Service,用于在后台执行耗时的异步任务,当任务完成后会自动停止。注意这里是自动停止。它拥有较高的优先级,不易被系统杀死(继承自Service的缘故),因此比较适合执行一些高优先级的异步任务。它的实现原理是HandlerThread和Handler,而HandlerThread是自带Looper的的Thread。自带Looper的的Thread有什么好处呢? 当开启子线程时,如果想在里面创建自己的Handler,是会报错的,因为该子线程无法获取到Looper。所以如果想在子线程里面创建自己的Handler,需要自己调用Looper.prepare()方法获取Looper,然后调用Looper.loop进行消息循环。而HandlerThread正好帮我们完成了这些工作,这就是HandlerThread的好处。

 

十 BroadcastReciever作用,使用场景,实现原理?

作用:

1.不同的程序之间的数据传输与共享,比如说拦截系统短信,拦截骚扰电话等。

2.起到了一个通知的作用,比如在service中要通知主程序,更新主程序的UI等

使用场景:

1.同一app内部的同一组件内的通信

2.同一app内部的不同组件之间的通信

3.同一app具有多个进程的不同组件之间的通信

4.不同app之间的组件之间通信

5.Android系统在特定情况下与App之间的通信

原理:

1.广播接收者通过Binder机制向AMS(Activity Manager Service)注册

2.广播发送者通过binder机制向AMS发送广播

3.AMS根据相应条件(IntentFilter/Permission等)在已注册列表中,查找合适的广播接收者,将广播发送到BroadcastReceiver(一般情况下是Activity)相应的消息循环队列中

4.消息循环执行拿到此广播,回调BroadcastReceiver中的onReceive()方法

静态注册与动态注册的区别:

1.注册的地方不同

2.动态注册时,当activity退出,就接收不到广播了,静态注册即使app退出,仍然能接收到广播。但是自Android3.1开始,对于系统广播,如果App进程已经退出,将不能接收到广播,对于自定义的广播,可以通过覆写flag为FLAG_INCLUDE_STOPPED_PACKAGES,使得静态注册的BroadcastReceiver,即使所在App进程已经退出,也能接收到广播。

BroadcastReceiver注意事项:

在onResume()注册、onPause()注销,因为onPause()在App死亡前一定会被执行,从而保证广播在App死亡前一定会被注销,从而防止内存泄露。不在onCreate() & onDestory() 或 onStart() & onStop()注册、注销是因为:
当系统因为内存不足要回收Activity占用的资源时,Activity在执行完onPause()方法后就会被销毁,有些生命周期方法onStop(),onDestory()就不会执行,从而导致内存泄露。

 

十一 Fragment 声明周期,其与Activity之间如何传值?

生命周期为:

1.Activity向Fragment传值:

在Activity中创建对象fragment,通过调用fragment.setArguments()传递到fragment中,然后在Fragment中通过调用getArguments()得到bundle对象,就能得到里面的值。

2.Fragment向Activity传值:

第一种,在Activity中调用getFragmentManager()得到fragmentManager,,调用findFragmentByTag(tag)或者通过findFragmentById(id)。

第二种,通过回调的方式,定义一个接口(可以在Fragment类中定义),接口中有一个空的方法,在fragment中需要的时候调用接口的方法,值可以作为参数放在这个方法中,然后让Activity实现这个接口,必然会重写这个方法,这样值就传到了Activity中。

3.Fragment与Fragment之间如何传值:

第一种:通过findFragmentByTag得到另一个的Fragment的对象,这样就可以调用另一个的方法了。
第二种:通过接口回调的方式。
第三种:通过setArgumentsgetArguments的方式。

FragmentPagerAdapter与FragmentStatePagerAdapter的区别?

FragmentPagerAdapter适用于Fragment页面少的情况,FragmentStatePagerAdapter适用于Fragment页面多的情况(页面多时回收内存)。

 

十二 Binder机制

Android为什么采用Binder做为IPC机制?

1.性能更强:Binder 只需要一次数据拷贝,性能上仅次于共享内存,而Socket/管道/消息队列需要2次拷贝。

2.更稳当:共享内存虽然无需拷贝,但是控制负责,难以使用。从稳定性的角度讲,Binder 机制是优于内存共享的(基于 C/S 架构)。

3.更安全传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份。Android 为每个安装好的 APP 分配了自己的 UID,故而进程的 UID 是鉴别进程身份的重要标志。传统的 IPC 只能由用户在数据包中填入 UID/PID,但这样不可靠,容易被恶意程序利用。

Linux 下的传统 IPC 通信原理?

通常的做法是消息发送方调用 copy_from_user() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中,然后内核程序调用 copy_to_user() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区,如下图:

这种传统的 IPC 通信方式有两个问题:

1.性能低下,一内存缓存区 --> 内核缓存区 --> 内存缓存区,需要 2 次数据拷贝。

2.接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。

动态内核可加载模块机制?

传统的 IPC 机制如管道、Socket 都是内核的一部分,因此通过内核支持来实现进程间通信自然是没问题的。但是 Binder 并不是 Linux 系统内核的一部分,那怎么办呢?这就得益于 Linux 的动态内核可加载模块(Loadable Kernel Module,LKM)的机制;模块可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。而在 Android 系统中,这个内核模块就叫 Binder 驱动(Binder Dirver)。

内存映射?

 知道了上面的LKM机制,Android 系统中用户进程之间是如何通过这个内核模块(Binder 驱动)来实现通信的呢?这就不得不提到 Linux 下的另一个概念:内存映射。内存映射通过 mmap() 来实现。内存映射简单的讲就是将用户空间的内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。

Binder IPC通信过程如下:

十三 View滑动的几种方式?

通过三种方式可以实现View的滑动:第一种是通过View本身提供的scrollTo/scrollBy方法来实现滑动;第二种是动画中的一种,view动画,也叫补间动画,另外属性动画也可以;第三种是通过改变View的LayoutParams使得View重新布局从而实现滑动。

scrollTo/scrollBy:操作简单,适合对View内容的滑动;

 动画:操作简单,主要适用于没有交互的View和实现复杂的动画效果;

改变布局参数:操作稍微复杂,适用于有交互的View。

弹性滑动的原理及实现?

它们都有一个共同思想:将一次大的滑动分成若干次小的滑动并在一个时间段内完成,弹性滑动的具体实现方式有很多,比如通过Scroller、Handler#postDelayed以及Thread#sleep等。

 

十四 View的事件分发机制?

点击事件达到顶级View(一般是一个ViewGroup)以后,会调用ViewGroup的dispatchTouchEvent方法,然后的逻辑是这样的:如果顶级ViewGroup拦截事件即onInterceptTouchEvent返回true,则事件由ViewGroup处理,这时如果ViewGroup的mOnTouchListener被设置,则onTouch会被调用,否则onTouchEvent会被调用。也就是说,如果都提供的话,onTouch会屏蔽掉onTouchEvent。在onTouchEvent中,如果设置了mOnClickListener,则onClick会被调用。如果顶级ViewGroup不拦截事件,则事件会传递给它所在的点击事件链上的子View,这时子View的dispatchTouchEvent会被调用。到此为止,事件已经从顶级View传递给了下一层View,接下来的传递过程和顶级View是一致的,如此循环,完成整个事件的分发。

处理滑动冲突的场景及解决方法?

如上面的滑动冲突场景,外部方向和内部方向不一致,先根据滑动距离判断是水平滑动还是竖直滑动,然后有两种拦截法,外部拦截法和内部拦截法。

外部拦截法:

所谓外部拦截法是指点击事情都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可。
 

public boolean onInterceptTouchEvent(MotionEvent event) {
            boolean intercepted = false;
            int x = (int) event.getX();
            int y = (int) event.getY();
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                intercepted = false;//这里不能拦截,否则后续点击事件都被拦截。
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                if (父容器需要当前点击事件) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                intercepted = false;//默认不拦截
                break;
            }
            default:
                break;
              }
              mLastXIntercept = x;
              mLastYIntercept = y;
              return intercepted;
  }

内部拦截法:

内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理,这种方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法稍显复杂。它的伪代码如下,我们需要重写子元素的dispatchTouchEvent方法:
 

public boolean dispatchTouchEvent(MotionEvent event) {
            int x = (int) event.getX();
            int y = (int) event.getY();

            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                parent.requestDisallowInterceptTouchEvent(true);//父容器不允许拦截
                    break;
                }
                case MotionEvent.ACTION_MOVE: {
                    int deltaX = x - mLastX;
                    int deltaY = y - mLastY;
                    if (父容器需要此类点击事件)) {
                        parent.requestDisallowInterceptTouchEvent(false);//父容器允许拦截
                    }
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    break;
                }
                default:
                    break;
                }

                mLastX = x;
                mLastY = y;
                return super.dispatchTouchEvent(event);
        }

除了子元素需要做处理以外,父元素也要默认拦截除了ACTION_DOWN以外的其他事件,这样当子元素调用parent.requestDisal-lowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。
 

public boolean onInterceptTouchEvent(MotionEvent event) {
            int action = event.getAction();
            if (action == MotionEvent.ACTION_DOWN) {
                return false;
              } else {
                  return true;
              }
    }

 

十五 View的工作流程,measure过程、layout过程、draw过程?

MeasureSpec有三种模式:EXACTLY对应布局文件中控件宽高的固定值或Match_Parent;AT_MOST对应布局文件中控件宽高的Wrap_Content;UNSPECIFIED一般用于系统内部。子元素的MeasureSpec与父容器的MeasureSpec和子元素本身的LayoutParams有关,此外还和View的margin及padding有关。

当View采用固定宽/高,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式。当View的宽/高是match_parent时,如果父容器是精准模式,那么View也是精准模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。当View的宽/高是wrap_content时,不管父容器的模式是精准还是最大化,View的模式总是最大化并且大小不能超过父容器的剩余空间。

为什么获取View的宽高会为0?怎么解决?

如果在Activity的回调里面获取View的宽高会为0,比如在onCreate,onStart,onResume里面是获取不到view宽高的。原因是因为View的绘制有ViewrootImp来完成的,而ViewrootImp是在onResume方法回调之后创建的,所以在onResume之前无法获取。解决办法是在onWindowFocusChanged回调里面获取,还可以在view.post(runnable)里面和ViewTreeObserver的回调里面获取。

 

十六 自定义View?

直接继承View重写onDraw方法

这种方法主要用于实现一些不规则的效果,一般需要重写onDraw方法。采用这种方式需要自己支持wrap_content,并且padding也需要自己处理。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
                  
	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                  
	int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
                  
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
             
     int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
                  
	int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
 
	//需要考虑wrap_content的情况                 
	if (widthSpecMode == MeasureSpec.AT_MOST
 && heightSpecMode ==
                  MeasureSpec.AT_MOST) {
                   
 setMeasuredDimension(200, 200);
                  
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
                    		 	
setMeasuredDimension(200, heightSpecSize);
                  
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
                    		
setMeasuredDimension(widthSpecSize, 200);
                  
}
              }

              
@Override
              
protected void onDraw(Canvas canvas) {
                  
	super.onDraw(canvas);
  
	//需要把padding考虑进来                
	final int paddingLeft = getPaddingLeft();
                  
	final int paddingRight = getPaddingLeft();
                  
	final int paddingTop = getPaddingLeft();
                  
	final int paddingBottom = getPaddingLeft();
                  
	int width = getWidth() - paddingLeft - paddingRight;
                  
	int height = getHeight() - paddingTop - paddingBottom;
                  
	int radius = Math.min(width, height) / 2;
                  
	canvas.drawCircle(paddingLeft + width / 2, 
paddingTop + height / 2,
   radius, mPaint);
              
}
          
}

如果直接继承特定的View,比如TextView,则不需要考虑wrap_content,padding。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章