Android经典面试题 持续更新


[TOC]

以后更新的面试题会放在顶部

静态内部类、内部类、匿名内部类,为什么内部类会持有外部类的引用?持有的引用是this?还是其它?

静态内部类:使用static修饰的内部类
匿名内部类:使用new生成的内部类
因为内部类的产生依赖于外部类,持有的引用是类名.this。

事件分发机制

对于一个根ViewGroup来说,发生点击事件首先调用dispatchTouchEvent
如果这个ViewGroup的onIterceptTouchEvent返回true就表示它要拦截当前事件,接着这个ViewGroup的onTouchEvent就会被调用.如果onIterceptTouchEvent返回false,那么就会继续向下调用子View的dispatchTouchEvent方法
当一个View需要处理事件的时候,如果它没有设置onTouchListener,那么直接调用onTouchEvent.如果设置了Listenter 那么就要看Listener的onTouch方法返回值.为true就不调,为false就调onTouchEvent
View的默认实现会在onTouchEvent里面把touch事件解析成Click之类的事件
点击事件传递顺序 Activity -> Window -> View
一旦一个元素拦截了某事件,那么一个事件序列里面后续的Move,Down事件都会交给它处理.并且它的onInterceptTouchEvent不会再调用
View的onTouchEvent默认都会消耗事件,除非它的clickable和longClickable都是false(不可点击),但是enable属性不会影响

View本身是没有onInterceptTouchEvent()方法的, 这个方法在ViewGroup中

计算机网络的ISO模型分层

Android经典面试题 持续更新_第1张图片
ISO分层

什么是Socket ?

我们知道两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。

能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。

Android经典面试题 持续更新_第2张图片

TCP协议的三次握手

Android经典面试题 持续更新_第3张图片
三次握手1

第一次握手:客户端尝试连接服务器,向服务器发送syn包(同步序列编号Synchronize Sequence Numbers),syn=j,客户端进入SYN_SEND状态等待服务器确认

第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态

第三次握手:第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手

定睛一看,服务器socket与客户端socket建立连接的部分其实就是大名鼎鼎的三次握手

Android经典面试题 持续更新_第4张图片
三次握手2

Android的四大组件是哪些 ?

Activity:Activity是Android程序与用户交互的窗口,是Android构造块中最基本的一种,它需要为保持各界面的状态,做很多持久化的事情,妥善管理生命周期以及一些跳转逻辑。

service:后台服务于Activity,封装有一个完整的功能逻辑实现. Service可以在很多场合的应用中使用,比如播放多媒体的时候用户启动了其他Activity这个时候程序要在后台继续播放,比如检测SD卡上文件的变化,再或者在后台记录你地理信息位置的改变等等,总之服务总是藏在后台的。

Content Provider:是Android提供的第三方应用数据的访问方案,可以派生Content Provider类,对外提供数据,可以像数据库一样进行选择排序,屏蔽内部数据的存储细节,向外提供统一的接口模型,大大简化上层应用,对数据的整合提供了更方便的途径

BroadCast Receiver:接受一种或者多种Intent作触发事件,接受相关消息,做一些简单处理,转换成一条Notification,统一了Android的事件广播模型

请介绍下Android中常用的四种布局

  1. FrameLayout 所有东西依次都放在左上角,会重叠,这个布局比较简单,也只能放一点比较简单的东西。
  2. LinerLayout 线性布局,每一个LinearLayout里面又可分为垂直布局(android:orientation="vertical")和水平布局(android:orientation="horizontal" )。当垂直布局时,每一行就只有一个元素,多个元素依次垂直往下;水平布局时,只有一行,每一个元素依次向右排列。
  3. RelativeLayout 相对布局可以理解为某一个元素为参照物,来定位的布局方式。
  4. TableLayout 表格布局.

android中的动画有哪几类,它们的特点和区别是什么

三种, Tween(补间动画), Frame(帧动画)和属性动画.

Tween动画,这种实现方式可以使视图组件移动、放大、缩小以及产生透明度的变化;

Frame动画,传统的动画方法,通过顺序的播放排列好的图片来实现,类似电影。

属性动画, 通过改变某个控件的属性值来实现动画效果

android中有哪几种解析xml的类?官方推荐哪种?以及它们的原理和区别

XML解析主要有三种方式,SAX、DOM、PULL。

常规在PC上开发我们使用Dom相对轻松些,但一些性能敏感的数据库或手机上还是主要采用SAX方式,SAX读取是单向的,优点:不占内存空间、解析属性方便,但缺点就是对于套嵌多个分支来说处理不是很方便。
而DOM方式会把整个XML文件加载到内存中去,但是提醒大家该方法在查找方面可以和XPath很好的结合如果数据量不是很大推荐使用。
而PULL常常用在J2ME对于节点处理比较好,类似SAX方式,同样很节省内存,在J2ME中我们经常使用的KXML库来解析。

ListView的优化方案

  1. 复用convertView

  2. 给contentView设置tag(setTag()),传入一个viewHolder对象,用于缓存要显示的数据,可以达到图像数据异步加载的效果。

  3. 设置ListView的高度为 确定值或match_parent

  4. 如果listview需要显示的item很多,就要考虑分页加载。

请介绍下Android的数据存储方式。

使用SharedPreferences存储数据;文件存储数据;SQLite数据库存储数据;使用ContentProvider存储数据;网络存储数据;

activity的启动模式有哪些?是什么含义?

在android里,有4种activity的启动模式,分别为:

“standard” (默认): Activity无限压栈, 不做任何处理

“singleTop” : 如果启动activity时发现它当前已经在栈顶则不会重新创建一个实例, 如果你启动的那个activity不再栈顶的话就会重新创建一个activity实例。

“singleTask” : 当你的activity设置为SingleTask时,每次启动你的activity的时候 就会去栈里面是否存在这个activity的实例,如果存在则直接使用这个实例,并把这个activity之上的所有其他的activity统统出栈,如果栈里面没有这个activity的实例的话就创建一个该activity的实例。

“singleInstance” : 是其所在栈的唯一activity,它会每次都被重用。

activity在屏幕旋转时的生命周期

不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次;设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次;设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法

如何启用Service,如何停用Service

服务的开发比较简单,如下:

第一步:继承Service类

public classSMSService extends Service {}

第二步:在AndroidManifest.xml文件中的节点里对服务进行配置:


服务不能自己运行,需要通过调用Context.startService()或Context.bindService()方法启动服务。这两个方法都可以启动Service,但是它们的使用场合有所不同。使用startService()方法启用服务,调用者与服务之间没有关连,即使调用者退出了,服务仍然运行。使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。

如果打算采用Context.startService()方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onStart()方法。如果调用startService()方法前服务已经被创建,多次调用startService()方法并不会导致多次创建服务,但会导致多次调用onStart()方法。采用startService()方法启动的服务,只能调用Context.stopService()方法结束服务,服务结束时会调用onDestroy()方法。

如果打算采用Context.bindService()方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onBind()方法。这个时候调用者和服务绑定在一起,调用者退出了,系统就会先调用服务的onUnbind()方法,接着调用onDestroy()方法。如果调用bindService()方法前服务已经被绑定,多次调用bindService()方法并不会导致多次创建服务及绑定(也就是说onCreate()和onBind()方法并不会被多次调用)。如果调用者希望与正在绑定的服务解除绑定,可以调用unbindService()方法,调用该方法也会导致系统调用服务的onUnbind()-->onDestroy()方法。

服务常用生命周期回调方法如下:

onCreate() 该方法在服务被创建时调用,该方法只会被调用一次,无论调用多少次startService()或bindService()方法,服务也只被创建一次。

onDestroy()该方法在服务被终止时调用。

与采用Context.startService()方法启动服务有关的生命周期方法

onStart() 只有采用Context.startService()方法启动服务时才会回调该方法。该方法在服务开始运行时被调用。多次调用startService()方法尽管不会多次创建服务,但onStart() 方法会被多次调用。

与采用Context.bindService()方法启动服务有关的生命周期方法

onBind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务绑定时被调用,当调用者与服务已经绑定,多次调用Context.bindService()方法并不会导致该方法被多次调用。

onUnbind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务解除绑定时被调用

注册广播有几种方式,这些方式有何优缺点?请谈谈Android引入广播机制的用意。

答:首先写一个类要继承BroadcastReceiver

第一种:在清单文件中声明,添加

 
    
        
            

        
    

第二种使用代码进行注册如:


//完成广播注册
@Override
protected void onResume() {
    super.onResume();
    IntentFilter filter = new IntentFilter();
    filter.addAction("com.lulu.action.MY_BROADCAST");
    registerReceiver(receiver2, filter);
}

//完成解除注册
@Override
protected void onPause() {
    super.onPause();
    unregisterReceiver(receiver2);
}

两种注册类型的区别是:

1)第一种是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。

2)第二种不是常驻型广播,也就是说广播跟随程序的生命周期。

Looper MessageQueue Message Handler的关系

Looper内部有loop方法, 里面取的是Looper内部的MessageQueue
MessageQueue内部有Message
Handler内部获取当前线程的Looper对象, 找到MessageQueue, Handler向其发送消息

loop一直在轮询遍历MessageQueue, 找到发来的handler对象并执行dispatchMessage, 处理消息

mvc模式的原理

mvc是model,view,controller的缩写,mvc包含三个部分:

模型(model)对象:是应用程序的主体部分,所有的业务逻辑都应该写在该层。

视图(view)对象:是应用程序中负责生成用户界面的部分。也是在整个mvc架构中用户唯一可以看到的一层,接收用户的输入,显示处理结果。

控制器(control)对象:是根据用户的输入,控制用户界面数据显示及更新model对象状态的部分,控制器更重要的一种导航功能,响应用户出发的相关事件,交给m层处理。

android鼓励弱耦合和组件的重用,在android中mvc的具体体现如下:

1)视图层(view):一般采用xml文件进行界面的描述,使用的时候可以非常方便的引入,当然,如果你对android了解的比较的多了话,就一定可以想到在android中也可以使用JavaScript+html等的方式作为view层,当然这里需要进行java和javascript之间的通信,幸运的是,android提供了它们之间非常方便的通信实现。

2)控制层(controller):android的控制层的重任通常落在了众多的acitvity的肩上,这句话也就暗含了不要在acitivity中写代码,要通过activity交割model业务逻辑层处理,这样做的另外一个原因是android中的acitivity的响应时间是5s,如果耗时的操作放在这里,程序就很容易被回收掉。

3)模型层(model):对数据库的操作、对网络等的操作都应该在model里面处理,当然对业务计算等操作也是必须放在的该层的。

什么是ANR 如何避免它?

ANR:Application Not Responding。在 Android 上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应
用程序无响应(ANR:Application Not Responding)对话框.

避免方法:Activity应该在它的关键生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作和潜在的耗时操作,例如网络或数据库操作,或者高耗时的计算如改变位图尺寸,应该在子线程里(或者异步方式)来完成。主线程应该为子线程提供一个Handler,以便完成时能够提交给主线程。

什么情况会导致Force Close ?如何避免?能否捕获导致其的异常?

抛出运行时异常时就会导致Force Close,比如空指针、数组越界、类型转换异常等等。

捕获:可以通过logcat查看抛出异常的代码出现的位置,然后到程序对应代码中进行修改。

避免:编写程序时,要思维缜密,在可能出现异常的地方都作相应的处理,增强程序的健壮性。

描述一下android的系统架构

Android 架构:Linux Kernel(Linux内核)、Hardware Abstraction Layer(硬件抽象层)、Libraries(系统运行库或者是c/c++ 核心库)、Application Framework(开发框架包 )、Applications(核心应用程序)

原来所说是四层: app、Framework、lib、kernel

Android经典面试题 持续更新_第5张图片
来自牛客网

请介绍下ContentProvider是如何实现数据共享的。

一个程序可以通过实现一个Content provider的抽象接口将自己的数据完全暴露出去,而且Contentproviders是以类似数据库中表达方式将数据暴露。Content providers存储和检索数据,通过它可以让所有的应用程序访问到,这也是应用程序之间唯一共享数据的方法。

要想使应用程序的数据公开化,可通过2种方法:创建一个属于你自己的Content provider或者将你的数据添加到一个已经存在的Content provider中,前提是有相同数据类型并且有写入Contentprovider的权限。

如何通过一套标准及统一的接口获取其他应用程序暴露的数据?

Android提供了ContentResolver,外界的程序可以通过ContentResolver接口访问ContentProvider提供的数据。

Service和Thread的区别

答:servie是系统的组件,它由系统进程托管(servicemanager);它们之间的通信类似于client和server,是一种轻量级的进程间通讯(IPC (Inter-process communication))通信,这种通信的载体是binder,它是在linux层交换信息的一种ipc。而thread是由本应用程序托管。

1). Thread:Thread是程序执行的最小单元,它是分配CPU的基本单位。可以用Thread 来执行一些异步的操作。

2). Service:Service是android的一种机制,当它运行的时候如果是LocalService,那么对应的Service 是运行在主进程的main 线程上的。如:onCreate,onStart这些函数在被系统调用的时候都是在主进程的 main 线程上运行的。如果是Remote Service,那么对应的Service 则是运行在独立进程的main 线程上。

既然这样,那么我们为什么要用Service 呢?其实这跟android 的系统机制有关,我们先拿Thread 来说。Thread的运行是独立于 Activity 的,也就是说当一个Activity 被 finish 之后,如果你没有主动停止Thread 或者Thread 里的 run方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:当Activity 被 finish 之后,你不再持有该Thread 的引用。另一方面,你没有办法在不同的Activity 中对同一Thread 进行控制。

举个例子:如果你的Thread 需要不停地隔一段时间就要连接服务器做某种同步的话,该Thread 需要在Activity 没有start的时候也在运行。这个时候当你start 一个Activity 就没有办法在该Activity 里面控制之前创建的Thread。因此你便需要创建并启动一个Service ,在Service 里面创建、运行并控制该Thread,这样便解决了该问题(因为任何Activity 都可以控制同一Service,而系统也只会创建一个对应Service 的实例)。

因此你可以把Service 想象成一种消息服务,而你可以在任何有Context 的地方调用Context.startService、Context.stopService、Context.bindService,Context.unbindService,来控制它,你也可以在Service 里注册BroadcastReceiver,在其他地方通过发送broadcast 来控制它,当然这些都是Thread 做不到的。

Android本身的api并未声明会抛出异常,则其在运行时有无可能抛出runtime异常,你遇到过吗?诺有的话会导致什么问题?如何解决?

答:会,比如nullpointerException。我遇到过,比如textview.setText()时,textview没有初始化。会导致程序无法正常运行出现forceclose。打开控制台查看logcat信息找出异常信息并修改程序。

IntentService有何优点?

Acitivity的进程当处理Intent的时候,会产生一个对应的Service; Android的进程处理器现在会尽可能的不kill掉你;非常容易使用

如果后台的Activity由于某原因被系统回收了,如何在被系统回收之前保存当前状态?

答:重写onSaveInstanceState()方法,在此方法中保存需要保存的数据,该方法将会在activity被回收之前调用。通过重写onRestoreInstanceState()方法可以从中提取保存好的数据

如何将一个Activity设置成窗口的样式。

中配置:android :theme="@android:style/Theme.Dialog" 

AIDL的全称是什么?如何工作?能处理哪些类型的数据?

全称是:Android Interface Define Language

AIDL(AndRoid接口描述语言)是一种接口描述语言; 编译器可以通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程的目的. 如果需要在一个Activity中, 访问另一个Service中的某个对象, 需要先将对象转化成AIDL可识别的参数(可能是多个参数),然后使用AIDL来传递这些参数, 在消息的接收端, 使用这些参数组装成自己需要的对象.

AIDL的IPC的机制是基于接口的,但它是轻量级的。它使用代理类在客户端和实现层间传递值. 如果要使用AIDL, 需要完成2件事情:

1. 引入AIDL的相关类.; 
2.调用aidl产生的class.

AIDL的创建方法:

AIDL语法很简单,可以用来声明一个带一个或多个方法的接口,也可以传递参数和返回值。 由于远程调用的需要, 这些参数和返回值并不是任何类型.下面是些AIDL支持的数据类型:

  1. 不需要import声明的简单Java编程语言类型(int,boolean等)

  2. String, CharSequence不需要特殊声明

  3. List, Map和Parcelables类型, 这些类型内所包含的数据成员也只能是简单数据类型, String等其他比支持的类型.

解释下Android程序运行时权限与文件系统权限的区别。

运行时权限Dalvik(android授权)

文件系统 linux 内核授权

系统上安装了多种浏览器,能否指定某浏览器访问指定页面?请说明原由。

通过直接发送Uri把参数带过去,或者通过manifest里的intentfilter里的data属性

android系统的优势和不足

5大优势

  1. 开放性
  2. 挣脱运营商的束缚
  3. 丰富的硬件选择
  4. 不受限制的开发商
  5. 无缝结合的Google应用

5大不足

  1. 安全和隐私
  2. 首先开卖Android的不是最大的运营商
  3. 运营商仍然能够影响到Android手机
  4. 同类机型用户减少
  5. 过分依赖开发商 缺少标准配置

Android dvm的进程和Linux的进程, 应用程序的进程是否为同一个概念

DVM指Dalvik的虚拟机。每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的Dalvik虚拟机实例。而每一个DVM都是在Linux 中的一个进程,所以说可以认为是同一个概念。

一条最长的短信息约占多少byte?

文70(包括标点),英文160,160个字节。

有一个一维整型数组int[]data保存的是一张宽为width,高为height的图片像素值信息。请写一个算法,将该图片所有的白色不透明(0xffffffff)像素点的透明度调整为50%


final int size = data.length;

for(int i = 0; i< size; i++){

     if(data[i] == 0xffffffff)

            data[i] = 0x80ffffff;

}

如何将SQLite数据库(dictionary.db文件)与apk文件一起发布

解答:可以将dictionary.db文件复制到 Android工程中的res/raw目录中。所有在res/raw目录中的文件不会被压缩,这样可以直接提取该目录中的文件。

复制的基本方法是使用getResources().openRawResource方法获得res/raw目录中资源的 InputStream对象,然后将该InputStream对象中的数据写入其他的目录中相应文件中。在Android SDK中可以使用SQLiteDatabase.openOrCreateDatabase方法来打开任意目录中的SQLite数据库文件。

谈谈Android的IPC(进程间通信)机制

IPC是内部进程通信的简称. Android中的IPC机制是为了让Activity和Service之间可以随时的进行交互,故在Android中该机制,只适用于Activity和Service之间的通信,类似于远程方法调用,类似于C/S模式的访问。通过定义AIDL接口文件来定义IPC接口。Servier端实现IPC接口,Client端调用IPC接口本地代理。

DDMS和TraceView的区别?

DDMS是一个程序执行查看器,在里面可以看见线程和堆栈等信息,TraceView是程序性能分析器.

Picasso和Glide的区别

  1. Picasso加载了全尺寸的图片到内存,然后让GPU来实时重绘大小。而Glide加载的大小和ImageView的大小是一致的,因此更小。当然,Picasso也可以指定加载的图片大小的:
  2. Picasso和Glide在磁盘缓存策略上有很大的不同。Picasso缓存的是全尺寸的,而Glide缓存的是跟ImageView尺寸相同的。

IMEI的获取

权限:

 

核心代码:

Imei = ((TelephonyManager) getSystemService(TELEPHONY_SERVICE))
.getDeviceId();

Android的性能优化

  1. 内存
    1). 减少占用
    2). 复用: 视图的复用, 对象的复用

  2. 速度
    1). 网络
    2). 多线程

  3. 响应时间
    1). 缓存: 增加内存, 提升访问速度;
    2). 显示加载进度
    3). 启动页预加载一部分内容

Android项目工程的assets目录和raw目录的作用

共同点:
它们会被原封不动的拷贝到APK中,而不会像其它资源文件那样被编译成二进制的形式。

由 于raw是Resources (res)的子目录,Android会自动的为这目录中的所有资源文件生成一个ID,这个ID会被存储在R类当中,作为一个文件的引用。这意味着这个资源 文件可以很容易的被Android的类和方法访问到,甚至在Android XML文件中你也可以@raw/的形式引用到它。在Android中,使用ID是访问一个文件最快捷的方式。MP3和Ogg文件放在这个目录下是比较合适 的。
assets目录更像一个附录类型的目录,Android不会为这个目录中的文件生成ID并保存在R类当中,因此它与 Android中的一些类和方法兼容度更低。同时,由于你需要一个字符串路径来获取这个目录下的文件描述符,访问的速度会更慢。但是把一些文件放在这个目 录下会使一些操作更加方便,比方说拷贝一个数据库文件到系统内存中。要注意的是,你无法在Android XML文件中引用到assets目录下的文件,只能通过AssetManager来访问这些文件。数据库文件和游戏数据等放在这个目录下是比较合适的。

接口和抽象类的区别

一个类只能继承一个类(抽象类)(正如人不可能同时是生物和非生物),但是可以实现多个接口(吃饭接口、走路接口)。
第一点. 接口是抽象类的变体,接口中所有的方法都是抽象的。而抽象类是声明方法的存在而不去实现它的类。
第二点. 接口可以多实现,抽象类不行
第三点. 接口定义方法,不能实现,而抽象类可以实现部分方法。
第四点. 接口中基本数据类型为static 而抽类象不是的。

一个线程安全的Singleton, 双重加锁

public class Singleton{  
   private static Singleton instance = null;//是否是final的不重要,因为最多只可能实例化一次。  
   private Singleton(){}  
   public static Singleton getInstance(){  
       if(instance == null){  
           //双重检查加锁,只有在第一次实例化时,才启用同步机制,提高了性能。  
           synchronized(Singleton.Class){  
               if(instance == null){  
                   instance = new Singleton();  
               }  
           }  
       }  
       return instance;  
   }  
}  

怎样写一个可以更新UI的Handler


private class MyThread extends Thread{
    public void run() {
       // 可以更新UI的handler
       Handler handler = new Hanlder(Looper.getMainLooper());

    } 
}


RecyclerView和ListView的区别

内容稍多, 请大家移步到: http://www.tuicool.com/articles/aeeaQ3J

参考文章:

http://www.cnblogs.com/colife/p/5480068.html
http://www.cnblogs.com/sunzn/archive/2013/05/10/3064129.html
http://www.cnblogs.com/dolphinX/p/3460545.html

你可能感兴趣的:(Android经典面试题 持续更新)