一、指标:理解synchronized的含义、明确synchronized关键字修饰普通方法、静态方法和代码块时锁对象的差异。
Java synchronized关键字
问题:
有如下一个类A
class A {
public synchronized void a() {}
public synchronized void b() {}
}
然后创建两个对象
A a1 = new A();
A a2 = new A();
然后在两个线程中并发访问如下代码:
Thread1 Thread2
a1.a(); a2.a();
请问二者能否构成线程同步?
答:不能构成线程同步。
如果A的定义是下面这种呢?
class A {
public static synchronized void a() {}
public static synchronized void b() {}
}
答:会构成线程同步。
解析:
Java的多线程中同步机制会对资源进行加锁,保证在同一时间只有一个线程可以操作对应资源,避免多线程同时访问相同资源而引发冲突。而Java中的Synchronized关键字就是用于实现同步机制的。
Synchronized定义:Java关键字,用于给对象和方法或代码块进行加锁,加锁的对象和方法或代码块只能有一个线程进行访问(需要持有同一把锁才可以),其他线程如果想访问它需要等待这个线程释放锁后才可以访问。
同步的原理:就是将部分操作功能数据的代码进行加锁。
同步的前提:
1.必须要有两个或两个以上的线程。
2.必须是多个线程使用同一个锁。
同步的好处与弊端:
好处:解决了线程的安全问题。
弊端:较为消耗资源,同步嵌套后容易死锁。
第一种情况是属于方法锁(也叫对象锁),类的实例对象可以有多个,每个实例对象都是一个锁,所以第一种的情况是互不干扰的,持有的锁不同也就不会构成线程的同步。就像是两个不同的锁,一个人在开一个锁,另一个人也可以去看另一把锁,他们是没有关联的。如果想要第一种情况构成线程的同步,就需要把这个定义为单例的。
第二种情况是属于类锁,每一个类只有一个class对象,所以它们在被静态修饰后持有的是同一把锁,也就构成了线程的同步,一个线程进去了,其他就需要进行等待,什么时候进去的线程释放了这个锁其他的线程才可以进去。就像现在只有一把锁,一个人在开锁,另一个人只能等着,不可能同时去开。
同步的方式有两种:
1.同步块:需要自己指定锁(如:synchronized(this)或synchronized(xxx.class))
2.同步方法:同步方法分为静态和非静态,静态的是同时使用static和synchronized进行修饰;非静态的是只有用synchronized修饰
为什么要有同步块的存在呢?
答:因为同步可能会在高并发时候造成系统的崩溃,而同步块可以降低崩溃的几率。同步块比直接在方法上同步要对效率影响更小,因为它的覆盖率比同步方法小,并且同步块我们可以指定一个对象当作锁,比方法的同步更灵活。
Static修饰符:用来修饰成员(成员变量和成员函数),静态是在对象之前存在的。
特点:
1.可以实现对象中数据的共享
2.可以使用 类名. 直接调用
3.随着类的加载而加载,消失而消失。优于对象的存在
注意:
1.被静态修饰的方法只能访问静态成员
2.静态方法中不能使用this,super关键字
3.对象特有的数据是不能被静态修饰的(如果被静态修饰了特有数据就变成共享数据了)
二、问题:清晰理解Service
Service是Android四大组件之一,它主要用于在后台处理一些耗时的逻辑计算,或去执行某些需要长时间运行的任务。
Service有两种方式启动,分别是startService和bindService。
(注:如果要执行耗时操作需要在服务中在单开一个线程来执行,Service是不提供用户界面的,也就是说我们是用眼睛看不到的)
第1问.Service的start和bind状态有什么区别?
答:
start(启动状态):
1.使用场景:主要用于执行后台计算或长期在后台运行,如:音乐播放。
2.生命周期:onCreate()->onStartCommand()->onDestroy()。(多次调用start,会重复执行onStartCommand()方法)
3.使用这个方式启动服务,调用者与服务直接没有关系,就是说启动它的那个Activity不能与这个服务进行交互,这个Activity就算销毁了服务仍然可以运行。
4.启动方式:startService()。
5.停止方式:外部调用Context.stopService(service)或自身调用stopSelf()(IntentService会自动调用stopSelf方法)。
bind(绑定状态):
1.使用场景:主要用于其他组件和Service进行交互,包括IPC(进程间通讯)。
2.生命周期:onCreate()->onBind()->onUnbind()->onDestroy()(多次调用bind,只会执行一次一次onBind()方法)。
3.使用这个方式启动服务,调用者与服务是相关联的,如果调用的Activity退出了,服务即被销毁,但这种绑定状态的启动可以和Activity进行交互。
4.启动方式:bindService(service, conn, flags)。
5.停止方式:Context.unbindService(conn)或启动它的Activity销毁。
(注:多个组件是可以绑定在一个Service上的,这种情况当所有组件解绑后,Service才会销毁。bindService(service, conn, flags)中第一个参数明确指定绑定的service的Intent;第二个参数是ServiceConnection对象;第三个参数是一个标志,它表明绑定中的操作,不想指定设为0即可。)
第2问.同一个Service,先startService在bindService,如何把它停止?
答:需要这两种启动方式的停止方式都进行调用,如果只是调用了stopService(),Service还在绑定状态。如果只是调用了unbindService(),会有一个与调用者没有关联的Service存在。如果想要彻底销毁只有既调用stopService(),也调用unbindService()。
(不管startService调用几次只需要调用一次stopService(stopSelf);如果不同组件调用n次bindService,必须调用n次unbindService方法)
第3问.你有注意到Service的onStartCommand方法的返回值吗?不同返回值有什么区别?
答:Service的onStartCommand方法的返回值主要有一下几种:
1.START_STICKY = 1;
系统在调用完onStartCommand()方法后,如果当前服务被kill。系统会重新创建这个服务并调用onStartCommand()方法,但它不会保留之前传递的Intent对象。如果此期间没有任何启动命令被传递到service,参数的Intent将为null。这个适用于不执行命令的媒体播放器(或类似的)。
2.START_NOT_STICKY = 2;
如果在执行完onStartCommand()后,服务被异常kill掉,系统不会自动重启该服务。只有在接受到新的Intent对象,这个服务才会被重新创建。这个可以用来避免在不需要的时候运行服务。
3.START_REDELIVER_INTENT = 3;
在执行onStartCommand()后,如果服务被kill,系统会自动重启该服务,并保留之前传递的Intent对象,可以在该服务被销毁前一直保留Intent对象数据。这个适用于需要立即恢复正在执行的工作服务,如:文件下载。
4.START_STICKY_COMPATIBILITY = 0;
这个是START_STICKY的兼容版本,在服务被kill掉后,服务虽然被重建,但没有重启。所以,这个不保证服务被终止后一定能重启。
(注:START_STICKY和START_REDELIVER_INTENT可以解决因内存资源不足而被杀死的问题,直接在onStartCommand()方法中返回)
第4问.Service的生命周期方法onCreate、onStart、onBind等运行在哪个线程?
答:
它们都是运行在主线程的。所以,如果在服务中直接做耗时操作也会出现ANR异常,要新开一个线程来做耗时操作。
当Service运行的是本地服务,那么对应的Service运行在主进程的main线程上。当Service运行的是远程服务,那么对应的Service运行在独立进程的main线程上。
三、话题:理解Activity的启动模式。
1、Activity的启动模式有哪几种,分别用于什么场景?
Activity的启动模式有四种:
1.standard(标准模式):它是系统默认的启动模式,使用这个模式每次启动都会创建一个新的实例,不管这个
实例是否存在,是否在栈顶。被创建的实例符合典型情况下Activity的生命周期。并且这种模式一个任务栈可以
有多个实例,每个实例也可以属于不同的任务栈,而且是谁启动了这个Activity,这个Activity就运行在启动
它的这个Activity所在的任务栈中。
使用场景:通常情况的Activity都是使用这个模式启动。
注1:一个任务栈中如果没有任何Activity时候,该任务栈就会被回收。使用ApplicationContext去启动standard模式下的Activity会报错。因为,这个模式下的Activity会默认进入启动它的Activity所属任务栈中。但非Activity类型的Context并没有任务栈,解决方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位(这个时候实际上是以singleTask模式启动)或直接使用singleTask模式。
注2:任务栈是一种“后进先出”的栈结构。
2.singleTop(栈顶复用模式或栈顶单例模式):
这个模式下,如果新的Activity已经位于栈顶,那么这个Activity就不会被创建,同时它的onNewIntent方法
会被调用,通过此方法的参数我们可以取出当前的请求信息(因为这里可以得到新的Intent)。如果新的
Activity实例已经存在但没有位于栈顶,那么仍会创建新的实例。如果栈顶不存在该Activity实例,则情况与
standard模式相同,创建新的实例。
使用场景:适用于接收通知启动的内容显示页面。
3.singleTask(栈内复用模式或栈内单例模式):
这种模式下,只要该Activity在一个栈中存在,启动时候就不会创建新的实例,并会调用onNewIntent函数。
如果该Activity不在栈顶就会导致它之上的所有Activity全部出栈(也就是都会移除栈)。如果不存在,就会创
建新的实例。
使用场景:程序的入口,也可以是程序的主页(如果返回主页的情况)。
4.singleInstance(单实例模式或全局唯一模式):
这个模式具有singleTask模式所有特性外,还有一点不同的就是这个模式下系统中只有一个该Activity实例,
也就是此模式的Activity只能单独位于一个任务栈中,所以就会单独占用一个任务栈,因为也是有栈内复用特性
所以不会重新创建实例。
使用场景:应用下载更新包,闹铃等。
2、清晰地描述下onNewIntent和onConfigurationChanged这两个生命周期方法的场景?
答:
onNewIntent生命周期方法的场景是Activity要提供多种方式调用启动,并且多个调用希望只有一个Activity
实例存在,就需要Activity的onNewIntent(Intent intent)函数。
只要Activity中加入自己的onNewIntent实现,在加上清单文件中对Activity设置
lanuchMode="singleTask"就可以使用。当调用onNewIntent时候需在里面使用setIntent(intent)赋值
给Activity的Intent。否则,后续的getIntent()就会获取以前的Intent。
onConfigurationChanged这个方法并不是只有屏幕方向改变时候才触发,在打开或隐藏键盘时候也会。
声明这个方法后在进行屏幕旋转或打开隐藏键盘时候就不会在走onCreate方法。否则,就会走onCreate方法。
四、话题:关于startActivityForResult
1、startActivityForResult的使用场景是什么?onActivityResult回调里面的resultCode和requestCode含义是什么?
答:
startActivityForResult(intent,requestCode)的使用场景一般是两个Activity需要进行交互的时
候使用,如一个Activity需要用到它跳转到的Activity中所返回的数据。
常用的场景就是做注册功能的时候,在注册界面用户需要选择他所在城市,用户在点击那个文本框后跳转到另
一个选择城市的界面,在选择好后把选择好的城市数据返回到之前的界面。然后,我得到返回的数据并把它显示到
TextView上。返回的数据我们可以通过在跳转的那个Activity里实现onActivityResult方法来得到。
onActivityResult的resultCode参数含义是用来判断返回的数据来自哪个Activity,如果返回的数据不
同,我们可以根据这个来进行区分。requestCode的含义是用来识别请求来源的,可以用来区分哪个模块回传的数据。
注:如果要想得到返回的数据一定是使用setResult返回的,如果使用startActivity跳转回这个Activity是在onActivityResult中得不到数据的。
2、Activity A启动B的时候,在B中何时该执行setResult ?setResult可以位于Activity的finish方法之后吗?
答:
在B中应在除onPause、onStop、onDestroy三个方法并在finish之前调用。
根据查看finish的源码可以知道setResult不可以位于finish之后,因为Activity的result是在
finish后才被返回。不在onPause、onStop、onDestroy三个调用是因为这三个方法不一定是在finish之前的。
测试打印的log:
12-07 15:38:23.397 4514-4514/ E/SecondActivity.this: onPause
12-07 15:38:23.411 4514-4514/ E/FirstActivity.this: onActivityResult
12-07 15:38:23.414 4514-4514/ E/FirstActivity.this: onRestart
12-07 15:38:23.415 4514-4514/ E/FirstActivity.this: onStart
12-07 15:38:23.415 4514-4514/ E/FirstActivity.this: onResume
12-07 15:38:23.737 4514-4514/ E/SecondActivity.this: onStop
12-07 15:38:23.737 4514-4514/ E/SecondActivity.this: onDestroy
根据这个log可以确定onActivityResult方法是在你返回的那个界面onStop与onDestroy方法之前调用的,所以
setResult这个方法放在这两个方法里是没有意义的。我把setResult放到onPause里也是不可以的,得不到数据。
注:在用户按Back返回键返回的时候会自动调用Activity的finish,并设置resultCode为RESULT_CANCELED(也就是取消的意思),所以你也得不到数据。解决方法是在Activity重写onBackPressed方法在里面super.onBackPressed之前使用setResult()。
五、话题:关于View的知识
1、View的getWidth()和getMeasuredWidth()有什么区别吗?
答:
2、如何在onCreate()中拿到View的宽度和高度?
答:
六、话题:关于Gradle的知识
1、如何理解Gradle?Grade在Android的构建过程中有什么作用?
答:
2、实践如下问题
问题:我们都知道,Android中时常需要发布渠道包,需要将渠道信息附加到apk中,然后在程序启动的时候读取渠道信息。仍然拿VirtualAPK来举例,
链接:GitHub - didi/VirtualAPK: A powerful and lightweig...。 动态指定一个渠道号(比如1001),那么构建的apk中,请在它的AndroidManifest.xml文件里面的application节点下面添加如下meta-data,请写一段Gradle脚本来自动完成: <application android:allowBackup="false" android:supportsRtl="true"> <meta-data android:name=“channel" android:value=“1001" /> </application> 要求:当通过如下命令来构建渠道包的时候,将渠道号自动添加到apk的manifest中。 ./gradlew clean assembleRelease -P channel=1001
PS:禁止使用manifestPlaceholders
答:
七、话题:关于序列化的知识
1、Parcelable和Serializable有什么用,它们有什么差别?
答:
2、自定义一个类让其实现Parcelable,大致流程是什么?
答:
八、话题:Java基础知识学习
1、Java中有哪几种引用?它们的含义和区别是什么?
答:
2、请用Java实现一个线程安全且高效的单例模式。
答:
九、话题:Android中的ClassLoader
1、Android中有哪几种ClassLoader?它们的作用和区别是什么?
答:
2、简述ClassLoader的双亲委托模型
答:
3、简述双亲委托模型在热修复领域的应用
答:
十、话题:Binder
1、什么是Binder?简单描述下它的工作过程和使用场景
答:
十一、话题:Okhttp和Retrofit
1、介绍这两个框架的作用和联系
答:
十二、话题:从源码的角度描述下Activity的启动过程
答:
十三、话题:简单描述下jvm的垃圾回收策略,比如引用计数、标记清除等策略
答:
十四、话题:用过RxJava和RxAndroid吗?RxAndroid切换线程是怎么实现的呢?
答:
十五、话题:进程保活
答:
十六、话题:四大组件
Android四大组件中每个组件的作用是什么?它们都可以开启多进程吗?
答:
十七、话题:AIDL
Android中AIDL的作用是什么?它所支持的参数类型是?默认情况下AIDL的调用过程是同步还是异步?如何指定AIDL为异步调用?
答:
十八、话题:线程池
Android中的线程池有哪些?它们的区别是什么?为什么要使用线程池?
答:
十九、话题:网络协议
网络的五层划分是什么?TCP和UDP的区别是什么?简述TCP的三次握手过程。
答:
二十、话题:MVC、MVP和MVVM
画出这三种开发模式的设计图,并给出它们的适用场景和优缺点。
答:
二十一、话题:AndroidStudio build过程
AndroidStudio点击build按钮后,AndroidStudio就会编译整个项目并将apk安装到手机上,请详细描述下这个过程的背后到底发生了什么?
答:
二十二、话题:大尺寸图片加载问题 这是一个经典的面试题: 给定一个1000 x 20000(宽1000px,高20000px)的大图,如何正常加载显示且不发生OOM ?
答:
二十三、话题:设计模式
如何理解设计模式的几大基本原则(单一职责、开闭原则等),你平时在工作中都用过哪几种模式,可以举一个工作中的例子吗?它解决了你的什么问题?
答: