最近找工作,所以参考了网上的文章,做了一下总结,希望给到最近也要面试的朋友
1. android四大组件
Activity,Service,ContentProvider,BroadcastReceiver
2. Activity的life cycle
onCreate onResume onPaused onStop onDestroy
3. 进程和线程的区别
通常一个任务就是一个程序,而一个程序就是一个进程。进程是有操作系统来来协调的。
线程是进程的组成部分,一个进程可以拥有多个线程。
4. 谈一谈”==“与”equals()"的区别
== 如果是基本类型比较的是真实值,如果是引用类型比较的是内存中的地址。Object类的equals方法内部也是调用==。所以说如果子类不覆盖equals方法,== 和 equals是一样。String类中重写了equals方法。此时equals方法不再等同于==,而是根据String内的值来比较。
5. 说一说android中的设计模式
单例模式
单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局的访问的一种模式方法。比较有名的就是饿汉式和懒汉式
// 饿汉式单例
public class Singleton1 {
// 指向自己实例的私有静态引用,主动创建
private static Singleton1 singleton1 = new Singleton1();
// 私有的构造方法
private Singleton1(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton1 getSingleton1(){
return singleton1;
}
}
// 懒汉式单例
public class Singleton2 {
// 指向自己实例的私有静态引用
private static Singleton2 singleton2;
// 私有的构造方法
private Singleton2(){}
// 使用 synchronized 修饰,临界资源的同步互斥访问
public static synchronized Singleton2 getSingleton2(){
if (singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
这里的懒汉式单例也是线程安全的,但是网上会诟病synchronized的使用导致效率较低。
除了以上两种,还有双重检验锁、静态内部类、枚举等
//枚举单例
public enum EnumSingleton {
INSTANCE;
private EnumSingleton() {
}
}
具体参考这里
建造者模式
Android中典型的用到Builder的地方是AlertDialog
// 显示一个alert对话框
public void showDialog(Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Title");
builder.setMessage("Set Your Message");
builder.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
setTitle("Clicked OK");
}
});
builder.setNegativeButton("Cancel",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
setTitle("Clicked Cancel");
}
});
builder.create().show(); // 构建AlertDialog, 并且显示
}
观察者模式
观察者模式在对象间建立了一对多的关系,当一个对象状态发生改变时,通知和它关联的所有对象它的改变,让这些对象作出相应的调整。
其实这个跟当前一些android的设计模式也有点像比如MVP/MVVM,数据的改变引起UI的变化
像比较有名的RxJava就是观察者模式的
6 Java NIO and IO
NIO和传统IO(一下简称IO)之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区。
NIO non-blocking IO
ref: www.jianshu.com/p/88ec3430a…
7 Hashmap vs HashTable vs ConcurrentHashMap
Hashmap 线程不安全, 在put数据和resize的时候会有线程不安全的问题。
Hashtable 线程安全(加了锁,但是全局一把锁)
ConcurrentHashMap 线程安全(分段锁,不同地方访问不会锁住)
REF:www.jianshu.com/p/e2f75c8cc…
8 hashcode()
因为不同的对象可能会生成相同的hashcode值。
如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
如果两个对象的hashcode值相等,则equals方法得到的结果未知。
REF: www.cnblogs.com/dolphin0520…
9 tcp and udp
TCP有三次握手和四次断开。 TCP使用面向连接的通信方式,大大提高了数据通信的可靠性。
UDP是一个非连接的协议,传输数据之前源端和终端不建立连接。 UDP使用尽最大努力交付,即不保证可靠交付, 因此主机不需要维持复杂的链接状态表。
TCP与UDP的区别:
1、基于连接与无连接;
2、对系统资源的要求(TCP较多,UDP少);
3、UDP程序结构较简单;
4、流模式与数据报模式 ;
5、TCP保证数据正确性,UDP可能丢包;
6、TCP保证数据顺序,UDP不保证。
REF:zhuanlan.zhihu.com/p/24860273
10 android stack
Linux Kernel
android 底层还是linux的
HAL
硬件抽象层,提供一些硬件:camera/bluetooth的访问
Native C/C++ Library
一些C/C++的库 比如webkit,opengl ES,Media Framework
Android Runtime
ART:android 5.0 之后开始使用之前用的是Dalvik
Java API Framework
View System:视图系统
Activity Manager:管理activity的生命周期
11 排序和时间复杂度
冒泡排序:时间复杂度 O(n^2)
public class BubbleSort {
public static int[] bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return arr;
}
int n = arr.length;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n -i - 1; j++) {
if (arr[j + 1] < arr[j]) {
int t = arr[j];
arr[j] = arr[j+1];
arr[j+1] = t;
}
}
}
return arr;
}
)
选择排序:时间复杂度 O(n^2)
public class SelectSort {
public static int[] selectSort(int[] a) {
int n = a.length;
for (int i = 0; i < n - 1; i++) {
int min = i;
for (int j = i + 1; j < n; j++) {
if(a[min] > a[j]) min = j;
}
//交换
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
return a;
}
}
REF:zhuanlan.zhihu.com/p/57088609
12 Annotation
一个 annotation 包含 多个(1-n)个Elementtype 和 一个 retentionPolicy
RetentionPolicy包含:
RUNTIME,CLASS, SOURCE
REF:www.cnblogs.com/skywang1234…
13 webview js和java相调
java -> js
- 通过WebView.loadUrl()
webView.loadUrl("javascript:jsMethod1(result=1)")
- 通过WebView.evaluateJavascript()
webView.evaluateJavascript("jsMethod2()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//do something
}
});
js -> java
- 通过WebView.addJavascriptInterface()
public class AppJavaScriptProxy {
public AppJavaScriptProxy() {
}
@JavascriptInterface
public void showMessage(String message) {
Log.d("MSG", message);
}
}
webView.addJavascriptInterface(new AppJavaScriptProxy(),“androidAppProxy”);
- 通过WebViewClient.shouldOverrideUrlLoading()
这个主要是通过url的schema来判断
- 通过WebChromeClient.onJsAlert()、onJsConfirm()、onJsPrompt()
14 Https 四次握手
但是这样会有中间人攻击的问题,想要安全还需要双向的认证。
15 String、StringBuffer、StringBuilder区别
String类是不可变类,任何对String的改变都 会引发新的String对象的生成;
StringBuffer和StringBuilder区别则是可变类,任何对它所指代的字符串的改变都不会产生新的对象。
不必考虑到线程同步问题,我们应该优先使用StringBuilder类;如果要保证线程安全,则需要使用StringBuffer。
16 ANR
如果 Android 应用的界面线程处于阻塞状态的时间过长,会触发“应用无响应”(ANR) 错误。如果应用位于前台,系统会向用户显示一个对话框,如图 1 所示。ANR 对话框会为用户提供强行退出应用的选项。
诊断 ANR 时需要考虑以下几种常见模式:
- 应用在主线程上非常缓慢地执行涉及 I/O 的操作。
- 应用在主线程上进行长时间的计算。
- 主线程在对另一个进程进行同步 binder 调用,而后者需要很长时间才能返回。
- 主线程处于阻塞状态,为发生在另一个线程上的长操作等待同步的块。
- 主线程在进程中或通过 binder调用与另一个线程之间发生死锁。主线程不只是在等待长操作执行完毕,而且处于死锁状态。
关于死锁
例如,一个进程 p1占用了显示器,同时又必须使用打印机,而打印机被进程p2占用,p2又必须使用显示器,这样就形成了死锁。 因为p1必须等待p2发布打印机才能够完成工作并发布屏幕,同时p2也必须等待p1发布显示器才能完成工作并发布打印机,形成循环等待的死锁。
找出ANR的原因
TraceView
您可以使用 TraceView 在查看用例时获取正在运行的应用的跟踪信息,并找出主线程繁忙的位置
拉取跟踪信息文件
Android 会在遇到 ANR 时存储跟踪信息。在较低的操作系统版本中,设备上只有一个 /data/anr/traces.txt 文件。在较新的操作系统版本中,有多个 /data/anr/anr_* 文件。您可以使用 Android 调试桥 (ADB) 作为根,从设备或模拟器中获取 ANR 跟踪信息:
adb root
adb shell ls /data/anr
adb pull /data/anr/<filename>
17 AIDL
在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。编写执行该编组操作的代码较为繁琐,因此 Android 会使用 AIDL 为您处理此问题。
只有在需要不同应用的客户端通过 IPC 方式访问服务,并且希望在服务中进行多线程处理时,您才有必要使用 AIDL。如果您无需跨不同应用执行并发 IPC,则应通过实现 Binder 来创建接口;或者,如果您想执行 IPC,但不需要处理多线程,请使用 Messenger 来实现接口。无论如何,在实现 AIDL 之前,请您务必理Binder机制。
18 Binder机制
为什么Android 要采用Binder作为IPC机制?
- 性能:Binder数据拷贝只需要一次,而管道、消息队列和socket都需要2次,共享内存不需要一次内存拷贝,从性能角度来看,binder性能仅次于共享内存。
- 稳定性:Binder基于C/S 架构 ,server端与client端相对独立,共享内存需要考虑访问临界资源的并发同步问题,binder结构的稳定性较好
- 安全性:Android为每个人应用程序分配了自己的UID,进程的UID是鉴别进程身份的重要标志,Android系统中对外只暴露Client端,Client端将任务发送给Server端,Server端会根据权限控制策略
REF:www.jianshu.com/p/6b1994a1f…
传统的Linux下的2次copy的IPC
一次完整的 Binder IPC 通信过程通常是这样:
- 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
- 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
- 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
Binder 是基于 C/S 架构的。由一系列的组件组成,包括 Client、Server、ServiceManager、Binder 驱动。其中 Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。其中 Service Manager 和 Binder 驱动由系统提供,而 Client、Server 由应用程序来实现。Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 来访问设备文件 /dev/binder,从而实现与 Binder 驱动的交互来间接的实现跨进程通信。
REF:zhuanlan.zhihu.com/p/35519585
19 Service
Service 是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可通过绑定到服务与之进行交互,甚至是执行进程间通信 (IPC)。例如,服务可在后台处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互。
如果其他组件通过调用 startService() 启动服务(这会引起对 onStartCommand() 的调用),则服务会一直运行,直到其使用 stopSelf() 自行停止运行,或由其他组件通过调用 stopService() 将其停止为止。
如果其他组件通过调用 bindService() 来创建服务,且未调用 onStartCommand(),则服务只会在该组件与其绑定时运行。当该服务与其所有组件取消绑定后,系统便会将其销毁。
20 Service 和 IntentService
Service
这是适用于所有服务的基类。扩展此类时,您必须创建用于执行所有服务工作的新线程,因为服务默认使用应用的主线程,这会降低应用正在运行的任何 Activity 的性能。
IntentService
这是 Service 的子类,其使用工作线程逐一处理所有启动请求。如果您不要求服务同时处理多个请求,此类为最佳选择。实现 onHandleIntent(),该方法会接收每个启动请求的 Intent,以便您执行后台工作。
Service不是独立的进程,也不是独立的线程,它是依赖于应用程序的主线程的,不建议在Service中编写耗时的逻辑和操作,否则会引起ANR。
IntentService 它创建了一个独立的工作线程来处理所有的通过onStartCommand()传递给服务的intents(把intent插入到工作队列中)。通过工作队列把intent逐个发送给onHandleIntent()。
不需要主动调用stopSelft()来结束服务。因为,在所有的intent被处理完后,系统会自动关闭服务。
一段代码:
public class HelloIntentService extends IntentService {
/**
* A constructor is required, and must call the super <code><a href="/reference/android/app/IntentService.html#IntentService(java.lang.String)">IntentService(String)</a></code>
* constructor with a name for the worker thread.
*/
public HelloIntentService() {
super("HelloIntentService");
}
/**
* The IntentService calls this method from the default worker thread with
* the intent that started the service. When this method returns, IntentService
* stops the service, as appropriate.
*/
@Override
protected void onHandleIntent(Intent intent) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// Restore interrupt status.
Thread.currentThread().interrupt();
}
}
}
REF:www.jianshu.com/p/c0fa9923d…
21 Fragment lifecycle
启动时候的lifecycle
onAttach(Activity) called once the fragment is associated with its activity. onCreate(Bundle) called to do initial creation of the fragment. onCreateView(LayoutInflater, ViewGroup, Bundle) creates and returns the view hierarchy associated with the fragment. onActivityCreated(Bundle) tells the fragment that its activity has completed its own Activity#onCreate. onViewStateRestored(Bundle) tells the fragment that all of the saved state of its view hierarchy has been restored. onStart() makes the fragment visible to the user (based on its containing activity being started). onResume() makes the fragment begin interacting with the user (based on its containing activity being resumed).
不再被使用的时候的lifecycle
onPause() fragment is no longer interacting with the user either because its activity is being paused or a fragment operation is modifying it in the activity. onStop() fragment is no longer visible to the user either because its activity is being stopped or a fragment operation is modifying it in the activity. onDestroyView() allows the fragment to clean up resources associated with its View. onDestroy() called to do final cleanup of the fragment's state. onDetach() called immediately prior to the fragment no longer being associated with its activity.
21 final, finally, finalize的区别
-
final 用于声明属性,方法和类, 分别表示属性不可变, 方法不可覆盖, 类不可继承.
-
finally 是异常处理语句结构的一部分,表示总是执行.
-
finalize 是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等. JVM不保证此方法总被调用.
22 Parcelable
Parcel 介绍:Parcel 内部包装了可序列化的数据,可以在 Binder 中自由传输
Parcelable 是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过 Intent 和 Binder 传递。
Parcelable 和 Serializable 区别
- 两者都可以实现序列化并且都可以用于 Intent 间数据传递
- Serializable 是 Java 中的序列化接口,使用简单但是开销很大,序列化和反序列化过程需要很多 I/O 操作
- Parcelable 是 Android 中的序列化方式,更适合用在 Android 中,确定是使用麻烦,但是效率很高。Android 中首选 Parcelable
- Parcelable 主要用于 Android 跨进程通信时在对内存数据的序列话,跨进程传输的数据是必须序列化的,Parcelable 更方便
- 将对象序列化到设备的本地文件或者网络传输,建议使用 Serializable
REF:www.jianshu.com/p/d0d582952…
23 LRUCache
Last Recent Used cache,最近使用的会被保留,常用在image缓存上。一般会借助LinkedHashMap来实现整个LRU的算法。LRU的cache会以强引用的方式去指向一个value(对象)
24 Bitmap
Bitmap内存模型
- Android 2.3.3(API10)之前,Bitmap的像素数据存放在Native内存,而Bitmap对象本身则存放在Dalvik Heap中。Native内存中的像素数据并不会以可预测的方式进行同步回收,有可能会导致Native内存升高。
- Android3.0之后,Bitmap的像素数据也被放在了Dalvik Heap中。Bitmap 内存也会随着对象一起被回收。带来的问题是java Heap会使用的比较多,容易导致OOM。
- Android 8.0 之后,Bitmap的像素数据又被放到 Native 内存中。当然此时Google做了改进,在Native层的Bitmap的像素数据可以做到和Java层的对象一起快速释放。
Glide
官方推荐用来做bitmap的加载
Glide
.with(myFragment)
.load(url)
.centerCrop()
.placeholder(R.drawable.loading_spinner)
.into(myImageView);
25 ButterKnife 原理
ButterKnife 整个过程是在项目编译阶段完成的,主要用到了 annotationProcessor 和 JavaPoet 技术,使用时通过生成的辅助类(类名+ViewBinder)完成操作,并不是在项目运行时通过注解加反射实现的,所以并不会影响项目运行时的性能,可能仅在项目编译时有略微的影响。
26 ViewBinding
通过视图绑定(ViewBinding)功能,您可以更轻松地编写可与视图交互的代码。在模块中启用视图绑定之后,系统会为该模块中的每个 XML 布局文件生成一个绑定类。绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。
视图绑定功能可按模块启用。要在某个模块中启用视图绑定,请将 viewBinding 元素添加到其 build.gradle 文件中
android {
...
viewBinding {
enabled = true
}
}
假设某个布局文件名为 result_profile.xml:
<LinearLayout ... >
<TextView android:id="@+id/name" />
<ImageView android:cropToPadding="true" />
<Button android:id="@+id/button"
android:background="@drawable/rounded_button" />
</LinearLayout>
生成的绑定类将名为 ResultProfileBinding。此类具有两个字段:一个是名为 name 的 TextView,另一个是名为 button 的 Button。该布局中的 ImageView 没有 ID,因此绑定类中不存在对它的引用。
每个绑定类还包含一个 getRoot() 方法,用于为相应布局文件的根视图提供直接引用。在此示例中,ResultProfileBinding 类中的 getRoot() 方法会返回 LinearLayout 根视图。
然后你的代码就可以这样写:
private lateinit var binding: ResultProfileBinding
@Override
fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }
}
27 什么是POJO,JavaBean?
POJO: 一个简单的Java类,这个类有一些private的参数作为对象的属性,没有实现/继承任何特殊的java接口或者类,不遵循任何主要java模型,约定或者框架的java对象。在理想情况下,POJO不应该有注解。然后针对每一个参数定义get和set方法访问的接口。
public class BasicInfoVo {
private String orderId;
private Integer uid;
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
}
JavaBean:
- JavaBean是可序列化的,实现了serializable接口
- 具有一个无参构造器
- 有按照命名规范的set和gett,is(可以用于访问布尔类型的属性)方法
- JavaBean 所有属性为private
public class UserInfo implements java.io.Serializable{
//实现serializable接口。
private static final long serialVersionUID = 1L;
private String name;
private int age;
//无参构造器
public UserInfo() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//javabean当中可以有其它的方法
public void userInfoPrint(){
System.out.println("");
}
}
REF: www.jianshu.com/p/6f3e2bd50…
REF: www.jianshu.com/p/224489dfd…
28 Gson
Gson常用来解析和生成Json
属性重命名 @SerializedName
保留了前端、后台、Android/java各自的命名习惯。
@SerializedName("email_address")
public String emailAddress;
是否暴露 @Expose注解
简单说来就是需要导出的字段上加上@Expose 注解,不导出的字段不加。注意是不导出的不加。
public class Category {
@Expose public int id;
@Expose public String name;
@Expose public List<Category> children;
//不需要序列化,所以不加 @Expose 注解,
//等价于 @Expose(deserialize = false,serialize = false)
public Category parent;
}
REF:www.jianshu.com/p/0e40a52c0…
29 斐波那契数列的实现
先上代码
public long fib(int n) {
if (n == 0 || n == 1) {
return n;
} else {
return fib(n-1) + fib(n-2)
}
}
基本上这个代码一出,面试官接下来的问题就来了,复杂度是多少?会有什么问题?
因为这个算法确实不是很好,一直在频繁的调用,n一大,可能会导致堆栈溢出。
然后再来看另外一种
public long fib(int n) {
long [] fib = new long[n+1]
fib[0] = 0;
if (n > 0) {
fib[1] = 1;
}
for (int i = 2; i <= n; i++) {
fib[i] = fib[i-1] + fib[i-2];
}
return fib[n];
}
这种写法的复杂度就是O(n), 也不会有堆栈溢出的问题。
还有一种把数组也省略了的方法:
public long fib(int n) {
long pre = 0;
long current = 1;
if (n == 0 || n == 1) {
return n;
}
int oldValue;
for (int i = 2; i <= n; i++) {
oldValue = current;
current = current + pre;
pre = oldValue;
}
return current;
}
同样复杂度为O(n), 不需要一个数组。
30 Dagger的使用
首先我认为dagger2 相比于Rxjava,MVP架构更难入手,稍微不慎就会导致整个项目编译不过(需要自动生成相关代码),引入Dagger2 势必会增加项目的学习成本,是否值得就需要根据情况考虑了。 当你开发一个小的项目,很多代码或者整个项目都是你一个人写的,需要构造的对象不多,且你比较熟悉对象的构造和生命周期,你就可以快速定位完成修改,修改和直接创建对象并没有那么难以接受。即使这样,当因为一些业务你需要修改代码时,比如本来一个UserRepository,开始功能很简单,只需提供一个UserApi就可以new了,后来要保存一些用户设置,你在构造函数加入SharedPreferences,再后来需要本地保存用户的数据,你又加入了DiskLruCache、Sqlite,再然后。。。同时你要明白,构造DiskLruCache,SqliteOpenHelp这些对象也是需要一堆参数的, 如果是多人合作就更糟糕了,别人使用你写的对象,一旦你修改了构造函数,他们就需要同步跟进,再如果你加入的一些参数,又是另外一个人写的对象。 使用Dagger2 的好处比较明显的就是,可以方便的修改,构建对象。 比如很多对象的构建都需要Context,你只要有一个可以提供Context的moudule就可以了,还可以比较方便的控制对象的生命周期,简单来讲就是可以很方便的构建和修改对象的构造函数(只需要维护好Component,module,当然这些对于新手并不简单)。 还有使用Dagger2 可以非常方便的进行单元测试,mock测试数据,关键就在于解除了数据和业务逻辑代码的耦合(dagger2 是用来方便的解除耦合,并不是只有使用了dagger2 就可以,还需要良好的设计和架构),dagger2 之于测试的便利,我还没有很深的体会,测试写的太少了,正在不断学习测试技术中。 最后,一定要明白我们为什么要把Dagger2、Rxjava、Mvp引入项目中?要明白他们能给我们带来的好处,作为学习的话,当然是多多益善了,这些主流的技术受到追捧必然有它的道理。
REF:www.zhihu.com/question/39…
32 Dalvik vs ART
ART与Dalvik VM的区别
DVM中的应用每次运行时,字节码都需要通过即时编译器(JIT,just in time)转换为机器码,这会使得应用的运行效率降低。而在ART中,系统在安装应用时会进行一次预编译(AOT,ahead of time),将字节码预先编译成机器码并存储在本地,这样应用每次运行时就不需要执行编译了,运行效率也大大提升。 ART占用空间比Dalvik大(字节码变为机器码之后,可能会增加10%-20%),这就是“时间换空间大法”。 预编译也可以明显改善电池续航,因为应用程序每次运行时不用重复编译了,从而减少了 CPU 的使用频率,降低了能耗。
33 MVP vs MVVM
同
Model不依赖于View的实现,只要外部程序调用Model的接口就能够实现对数据的增删改查。 View:UI层,提供对最终用户的交互操作功能,包括UI展现代码及一些相关的界面逻辑代码。 Model 和 View相互隔离
异
Presenter接收View的命令,对Model进行操作;Presenter会反作用于View,Model的变更通知首先被Presenter获得,然后Presenter再去更新View。一个Presenter只对应于一个View。
ViewModel就是包含View的一些数据属性和操作的类,这种模式的关键技术就是数据绑定(data binding),View的变化会直接影响ViewModel,ViewModel的变化或者内容也会直接体现在View上。这种模式实际上是框架替应用开发者做了一些工作,开发者只需要较少的代码就能实现比较复杂的交互。
REF:www.jianshu.com/p/301076003…
34 volatile vs synchronized
- volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
- synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
- volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
- volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
- volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
- volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
35 Http/2 的优点
-
多路复用的单一长连接
-
头部压缩和二进制格式
-
服务端推动Sever Push
REF:www.jianshu.com/p/8ac6baf47…
36 Memory Leak
一般非静态内部类会持有外部类的引用,这个是一般造成mem leak的主要原因。
然后怎么查找mem leak可以参考下面
37 thread pool
REF: www.jianshu.com/p/2dc69025c…
38 RecycleView 的缓存机制
REF:www.jianshu.com/p/3e9aa4bda…
39 线程安全几个要素
线程安全需要保证几个基本特性
-
1、原子性,简单说就是相关操作不会中途被其他线程干扰,一般通过同步机制实现。
-
2、可见性,是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上,volatile 就是负责保证可见性的。
-
3、有序性,是保证线程内串行语义,避免指令重排等。
REF:blog.csdn.net/bird_tp/art…
40 面向对象的3个要素
-
封装
-
继承
继承概念的实现方式有三类:实现继承、接口继承和可视继承。
实现继承,是指使用基类的属性和方法而无需额外编码的能力;
接口继承,是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
可视继承,是指子窗体(类)使用基窗体(类)的外观和实现代码的能力
- 多态 实现多态,有二种方式,覆盖,重载。
覆盖,是指子类重新定义父类的虚函数的做法。
重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
41 死锁
产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 占有且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不可强行占有:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之 一不满足,就不会发生死锁。
REF:blog.csdn.net/rabbit_in_a…
42 动态代理和静态代理
静态代理:由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
REF:blog.csdn.net/mine_song/a…
深入理解可以看:
REF:www.zhihu.com/question/20…
Retrofit 就有用到动态代理
43 JUnit4 的顺序
@Before:初始化方法 对于每一个测试方法都要执行一次(注意与BeforeClass区别,后者是对于所有方法执行一次)
@After:释放资源 对于每一个测试方法都要执行一次(注意与AfterClass区别,后者是对于所有方法执行一次)
@Test:测试方法,在这里可以测试期望异常和超时时间
@Test(expected=ArithmeticException.class)检查被测方法是否抛出ArithmeticException异常
@Ignore:忽略的测试方法
@BeforeClass:针对所有测试,只执行一次,且必须为static void
@AfterClass:针对所有测试,只执行一次,且必须为static void
一个JUnit4的单元测试用例执行顺序为:
@BeforeClass -> @Before -> @Test -> @After -> @AfterClass;
每一个测试方法的调用顺序为:
@Before -> @Test -> @After;
44 sleep 和 wait的区别
1 sleep 让线程等待设定好的时间,而wait等待别人notify
2 sleep的时候不会释放资源/锁,而wait则会释放
3 wait主要用来做线程间的通讯,而sleep只是让线程停止执行一段时间
45 Thread & Runnable
都可以创建线程
但是一般还是Runnable,原因是因为Java是单继承的,用了Runnable还可以继续继承。
并且Runnable不会改变Thread的内部逻辑,继承Thread的方式会去改变内部逻辑
46 Flutter的实现原理
底层基于Skia这个2D图形库,实现了跨平台。
关于Skia:skia.org/index_zh
47 加密
对称加密:指的就是加、解密使用的同是一串密钥,比如,DES,AES
非对称加密:指的是加、解密使用不同的密钥,有公钥和私钥之分。公钥加密的信息,只有私钥才能解密。反之,私钥加密的信息,只有公钥才能解密。比如:RSA
48 一些锁
乐观锁 VS 悲观锁
悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。
乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)
悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
公平锁 VS 非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。
非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。
ReentrantLock 可通过设置来确定是否使用公平锁。
独享锁 VS 共享锁
独享锁也叫排他锁,是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。JDK中的synchronized和JUC中Lock的实现类就是互斥锁。
共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。
独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。
比如:ReentrantReadWriteLock里面有两把锁:read lock 和 write lock
read lock就是共享锁,而write lock是独享锁。
可重入锁 VS 非可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
REF: tech.meituan.com/2018/11/15/…
49 Synchronized vs ReentrantLock
相同点
这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待。
不同点
Synchronized是java语言的关键字,ReentrantLock需要lock()和unlock()方法配合try/finally语句块来完成。
Synchronized既可以修饰方法,也可以修饰代码块。
ReentrantLock有一点自己的特点:
- 等待可中断。
- Synchronized的锁是非公平锁,ReentrantLock默认情况下也是非公平锁,但可以通过带布尔值的构造函数要求使用公平锁。
- 锁绑定多个条件即同时绑定多个Condition对象。
50 ThreadLocal是什么
ThreadLocal 是一个线程内部的数据存储类,通过它可以在 指定的线程中 存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
51 SoftReference vs WeakReference vs PhantomReference
WeakReference, 弱引用, 当所引用的对象在 JVM 内不再有强引用时, GC 后 weak reference 将会被自动回收。
SoftReference 于 WeakReference 的特性基本一致, 最大的区别在于 SoftReference 会尽可能长的保留引用直到 JVM 内存不足时才会被回收(虚拟机保证), 这一特性使得 SoftReference 非常适合缓存应用
PhantomReference 一个对象是都有虚引用的存在都不会对生存时间都构成影响,也无法通过虚引用来获取对一个对象的真实引用。唯一的用处:能在对象被GC时收到系统通知,JAVA中用PhantomReference来实现虚引用
REF:www.zhihu.com/question/37…
52 Activity 启动过程
启动流程:
- 点击桌面App图标,Launcher进程采用Binder IPC向system_server进程发起startActivity请求;
- system_server进程接收到请求后,向zygote进程发送创建进程的请求;
- Zygote进程fork出新的子进程,即App进程;
- App进程,通过Binder IPC向sytem_server进程发起attachApplication请求;
- system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进程发送scheduleLaunchActivity请求;
- App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息;
- 主线程在收到Message后,通过发射机制创建目标Activity,并回调Activity.onCreate()等方法。
到此,App便正式启动,开始进入Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染结束后便可以看到App的主界面。
REF: cloud.tencent.com/developer/a…