Android中高级进阶超强面试题集锦

不积跬步,无以至千里。

1 组件类

1.1 Activity生命周期

Android的生命周期主要有七个,按其创建到销毁主要有以下几个阶段:onCreate() -> onStart() -> onResume() -> onPause() -> onStop() -> onDetroy()

笔试题有时候需要画出这个图
生命周期
key point:

  • 1.activity的生命周期是通过handler消息来控制的;
  • 2.activity的实例创建是通过反射来实现的;
  • 3.在activity onResume生命周期后,才将布局view绘制添加到系统布局中并显示给用户;
  • 4.在activityonDestory生命周期后,只是将window、window中的view,DecorView移除并置为null,并没有将activity实例置为null,activity实例仍然在内存中,如果在gc时还有持有该activity的引用就会造成内存泄露,也就是说activity onDestory后只是页面的销毁,并不代表当前activity实例的销毁

另外:如何对Activity的生命周期进行管理呢?

1. 继承BaseActivity
创建一个任务栈管理,对生命中后期进行控制。

import java.util.Stack;

import android.app.Activity;

public class ActivityStackManager {
	private static Stack<Activity> activityStack;
	private static ActivityStackManager instance;

	private ActivityStackManager() {
	}

	public static ActivityStackManager getScreenManager() {
		if (instance == null) {
			instance = new ActivityStackManager();
		}
		return instance;
	}

	// 退出栈顶Activity
	public void popActivity() {
		Activity activity = activityStack.pop();
		if (activity != null) {
			// 在从自定义集合中取出当前Activity时,也进行了Activity的关闭操作
			activity.finish();
			activity = null;
		}
	}
	
	// 退出栈顶Activity
	public void removeActivity(Activity activity) {
		if (activity != null) {
			// 在从自定义集合中取出当前Activity时,也进行了Activity的关闭操作
			activityStack.remove(activity);
		}
	}

	// 获得当前栈顶Activity
	public Activity currentActivity() {
		Activity activity = null;
		if (!activityStack.empty())
			activity = activityStack.lastElement();
		return activity;
	}

	// 将当前Activity推入栈中
	public void pushActivity(Activity activity) {
		if (activityStack == null) {
			activityStack = new Stack<Activity>();
		}
		activityStack.push(activity);
	}

	// 退出栈中所有Activity
	public void popAllActivityExceptOne(Class cls) {
		while (true) {
			Activity activity = currentActivity();
			if (activity == null) {
				break;
			}
			if (activity.getClass().equals(cls)) {
				break;
			}
			popActivity();
		}
	}

	// 退出栈中所有Activity
	public void popAllActivity() {
		if (activityStack != null) {
			while (activityStack.size() > 0) {
				popActivity();
			}
		}
	}
}

2. 使用ActivityLifeCycleCallback
在Application类中,提供了一个应用生命周期回调的注册方法,用来对应用的生命周期进行集中管理,这个接口叫registerActivityLifecycleCallbacks,可以通过它注册自己的ActivityLifeCycleCallback,每一个Activity的生命周期都会回调到这里的对应方法。

推荐使用此方法。

/**
 * Created by dawish on 2017/2/16.
 */
public class App extends Application {
    private static App mApp;
    public static Stack<ActivityDetail> store;

    private static final int MAX_ACTIVITY_DETAIL_NUM = 2;

    @Override
    public void onCreate() {
        super.onCreate();
        mApp = this;
        store = new Stack<>();
        //注册监听器
        registerActivityLifecycleCallbacks(new SwitchBackgroundCallbacks());
    }

    public static App getAppContext() {
        return mApp;
    }

    private class SwitchBackgroundCallbacks implements ActivityLifecycleCallbacks {

        @Override
        public void onActivityCreated(Activity activity, Bundle bundle) {
            if(activity instanceof ActivityDetail) {
                if(store.size() >= MAX_ACTIVITY_DETAIL_NUM){
                    store.peek().finish(); //移除栈底的详情页并finish,保证商品详情页个数最大不超过指定
                }
                store.add((ActivityDetail) activity);
            }
        }

        @Override
        public void onActivityStarted(Activity activity) {

        }

        @Override
        public void onActivityResumed(Activity activity) {

        }

        @Override
        public void onActivityPaused(Activity activity) {

        }

        @Override
        public void onActivityStopped(Activity activity) {

        }

        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {

        }

        @Override
        public void onActivityDestroyed(Activity activity) {
            store.remove(activity);
        }
    }

    /**
     * 获取当前的Activity
     *
     * @return
     */
    public Activity getCurActivity() {
        return store.lastElement(); //返回栈顶Activity
    }
}


1.2 Service生命周期

在这里插入图片描述
onStartCommand: 在执行了startService方法之后,有可能会调用Service的onCreate方法,在这之后一定会执行Service的onStartCommand回调方法。也就是说,如果多次执行了Context的startService方法,那么Service的onStartCommand方法也会相应的多次调用

onStartCommand 返回值:
START_NOT_STICKY :表示当Service运行的进程被Android系统强制杀掉之后,不会重新创建该Service。
START_STICKY :表示Service运行的进程被Android系统强制杀掉之后,Android系统会将该Service依然设置为started状态(即运行状态),但是不再保存onStartCommand方法传入的intent对象,然后Android系统会尝试再次重新创建该Service,并执行onStartCommand回调方法,但是onStartCommand回调方法的Intent参数为null,也就是onStartCommand方法虽然会执行但是获取不到intent信息
START_REDELIVER_INTENT :跟START_STICKY 相似,但是传入最后一次的intent

1.3 Fragment 生命周期?

Fragment每个生命周期方法的意义、作用(注意红色的不是生命周期方法):

  • setUserVisibleHint():设置Fragment可见或者不可见时会调用此方法。在该方法里面可以通过调用getUserVisibleHint()获得Fragment的状态是可见还是不可见的,如果可见则进行懒加载操作。
  • onAttach():执行该方法时,Fragment与Activity已经完成绑定,该方法有一个Activity类型的参数,代表绑定的Activity,这时候你可以执行诸如mActivity = activity的操作。
  • onCreate():初始化Fragment。可通过参数savedInstanceState获取之前保存的值。
  • onCreateView():初始化Fragment的布局。加载布局和findViewById的操作通常在此函数内完成,但是不建议执行耗时的操作,比如读取数据库数据列表。
  • onActivityCreated():执行该方法时,与Fragment绑定的Activity的onCreate方法已经执行完成并返回,在该方法内可以进行与Activity交互的UI操作,所以在该方法之前Activity的onCreate方法并未执行完成,如果提前进行交互操作,会引发空指针异常。
  • onStart():执行该方法时,Fragment由不可见变为可见状态。
  • onResume():执行该方法时,Fragment处于活动状态,用户可与之交互。
  • onPause():执行该方法时,Fragment处于暂停状态,但依然可见,用户不能与之交互。
    onSaveInstanceState():保存当前Fragment的状态。该方法会自动保存Fragment的状态,比如EditText键入的文本,即使Fragment被回收又重新创建,一样能恢复EditText之前键入的文本。
  • onStop():执行该方法时,Fragment完全不可见。
  • onDestroyView():销毁与Fragment有关的视图,但未与Activity解除绑定,依然可以通过onCreateView方法重新创建视图。通常在ViewPager+Fragment的方式下会调用此方法。
  • onDestroy():销毁Fragment。通常按Back键退出或者Fragment被回收时调用此方法。
  • onDetach():解除与Activity的绑定。在onDestroy方法之后调用。

在这里插入图片描述
在这里插入图片描述

1.4 fragment的创建方式

静态创建
首先我们需要创建一个xml文件,然后创建与之对应的java文件,通过onCreatView()的返回方法进行关联,最后我们需要在Activity中进行配置相关参数即在Activity的xml文件中放上fragment的位置。

动态创建
动态创建Fragment主要有以下几个步骤:

创建待添加的fragment实例。
获取FragmentManager,在Activity中可以直接通过调用 getSupportFragmentManager()方法得到。
开启一个事务,通过调用beginTransaction()方法开启。
向容器内添加或替换fragment,一般使用repalce()方法实现,需要传入容器的id和待添加的fragment实例。
提交事务,调用commit()方法来完成。

1.5 Adapter对比

FragmnetPageAdapter在每次切换页面时,只是将Fragment进行分离,适合页面较少的Fragment使用以保存一些内存,对系统内存不会多大影响。

FragmentPageStateAdapter在每次切换页面的时候,是将Fragment进行回收,适合页面较多的Fragment使用,这样就不会消耗更多的内存

1.6 如何保证屏幕旋转fragment重建不会丢值

不使用构造方法传值。
调用setArguments()和getArguments()得到bundle对象

1.7 fragment中add和replace的区别

add
一种是add方式来进行show和add,这种方式你切换fragment不会让fragment重新刷新,只会调用onHiddenChanged(boolean isHidden)。

replace
而用replace方式会使fragment重新刷新,因为add方式是将fragment隐藏了而不是销毁再创建,replace方式每次都是重新创建。

commit/commitAllowingStateLoss

两者都可以提交fragment的操作,唯一的不同是第二种方法,允许丢失一些界面的状态和信息,几乎所有的开发者都遇到过这样的错误:无法在activity调用了onSaveInstanceState之后再执行commit(),这种异常时可以理解的,界面被系统回收(界面已经不存在),为了在下次打开的时候恢复原来的样子,系统为我们保存界面的所有状态,这个时候我们再去修改界面理论上肯定是不允许的,所以为了避免这种异常,要使用第二种方法。


1.8 事件分发机制?

先看下面的图:
在这里插入图片描述

  1. ViewGroup不拦截事件的时候,默认传入到最里层的View处理;
  2. View若处理了事件,则事件结束,若未处理事件,则由ViewGroup处理
  3. Activity -> ViewGroup -> View -> ViewGroup -> Activity,事件总会经过acticity.
  4. 如果ViewGroup的onInterceptTouchEvent进行拦截,先调用onTouchListener中的onTouch ,再调用onClickerListener中的onClick;dispatchTouchEvent -> onTouch(setOnTouchListener) -> onTouchEvent -> onClick
  5. onTouch和onTouchEvent的区别
    两者都是在dispatchTouchEvent中调用的,onTouch优先于onTouchEvent,如果onTouch返回true,那么onTouchEvent则不执行,及onClick也不执行

1.9 View三种测量模式的区别

UNSPECIFIED : 没有固定限制
EXACTLY : 当前View应该取的尺寸 match_parent 和 固定dp
AT_MOST : wrap_content

在这里插入图片描述
在这里插入图片描述

ViewGroup的区别
在这里插入图片描述
在这里插入图片描述

Android自定义ViewGroup(四、打造自己的布局容器) - 进击的小怪兽 - CSDN博客 https://blog.csdn.net/ldld1717/article/details/80458917


1.10 Activity启动模式?

默认启动模式standard:依照启动顺序被依次压入Task中。
栈顶复用模式singleTop:只在栈顶复用,其余重新创建。
栈内复用模式singleTask:复用,顶部Activity出栈。
全局唯一模式singleInstance:新的Task有且只有这一个Activity实例。

启动模式决定了你的activity和task的关联性。当一个activity启动的时候,有两种方式指定它与task的关联:使用manifest文件和使用intent flag。当intent的flag和manifest所指定的启动模式发生冲突的时候,此时就以intent flag指定的模式为准。
使用manifest清单文件设置启动模式

  1. 通过在manifest文件中添加launchMode属性来指定启动模式:
    在这里插入图片描述
  2. 使用Intent的flag设置启动模式
    当你启动一个Activity时,你也可以动态的设置intent的flag,然后通过startActivity()方法启动activity,从而修改其启动的activity与它的task的关联模式。具体可以使用的flag有:
  • FLAG_ACTIVITY_NEW_TASK:对应之前的“singleTask”,在新的task中启动activity,如果一个你需要的activity的task已经存在,则将它推向前台,恢复其上一个状态,它通过onNewIntent()收到这个新的intent。

  • FLAG_ACTIVITY_SINGLE_TOP:对应之前的“singleTop”,如果被启动的activity是当前顶部的activity,则已经存在的实例会收到onIntent(),而不会重新去创建这个实例。

  • FLAG_ACTIVITY_CLEAR_TOP:这个行为在launchMode属性中没对应的属性值,若被启动的activity已经在当前task中运行,则不会创建它的新实例,而是的销毁在它之上的其他所有的activities,然后通过 onNewIntent()传递一个新的intent给这个恢复了的activity,它一般会与FLAG_ACTIVITY_NEW_TASK一起使用。值得注意的是,如果activity的启动模式是"standard",它自己也将被移除,然后一个新的实例将被启动。这是因为当启动模式是"standard"时,为了接收新的intent必须创建新的实例


1.11 SharedPreferences数据存储的apply()和commit()区别?

SharedPreference 相关修改使用 apply 方法进行提交会先写入内存,然后异步写入磁盘,commit方法是直接写入磁盘。如果频繁操作的话 apply 的性能会优于 commit,apply会将最后修改内容写入磁盘。但是如果希望立刻获取存储操作的结果,并据此做相应的其他操作,应当使用 commit。


1.12 SurfaceView & View 的区别?

view的更新必须在UI thread中进行

surfaceview会单独有一个线程做ui的更新。

surfaceview 支持open GL绘制。


1.13 线程通信与进程通信的不同?

进程通信方式:
bind机制(IPC->AIDL):Client,service,Service Manager运行在用户空间,Binder驱动程序是运行在内核空间的。
linux级共享内存
BroadcastReceiver :占用资源。
Messenger :请求队列是同步进行的,无法并发执行。


1.14 Android类加载器的不同?

DexClassLoader :可以加载外部APK
PathClassLoader :只能加载内部的Dex文件。

Android中常用的类加载器有两种,DexClassLoader和PathClassLoader,它们都继承于BaseDexClassLoader。区别在于调用父类构造器时,DexClassLoader多传了一个optimizedDirectory参数,这个目录必须是内部存储路径,用来缓存系统创建的Dex文件。而PathClassLoader该参数为null,只能加载内部存储目录的Dex文件。所以我们可以用DexClassLoader去加载外部的apk文件,这也是很多插件化技术的基础。

1.15 IntentService ?

IntentService是一个抽象类,继承自Service,内部存在一个ServiceHandler(Handler)和HandlerThread(Thread)。IntentService是处理异步请求的一个类,在IntentService中有一个工作线程(HandlerThread)来处理耗时操作,启动IntentService的方式和普通的一样,不过当执行完任务之后,IntentService会自动停止。另外可以多次启动IntentService,每一个耗时操作都会以工作队列的形式在IntentService的onHandleIntent回调中执行,并且每次执行一个工作线程。IntentService的本质是:封装了一个HandlerThread和Handler的异步框架

1.16 APK打包原理 ?

Android的包文件APK分为两个部分:代码和资源,所以打包方面也分为资源打包和代码打包两个方面,这篇文章就来分析资源和代码的编译打包原理。

具体说来:

通过AAPT工具进行资源文件(包括AndroidManifest.xml、布局文件、各种xml资源等)的打包,生成R.java文件。
通过AIDL工具处理AIDL文件,生成相应的Java文件。
通过Javac工具编译项目源码,生成Class文件。
通过DX工具将所有的Class文件转换成DEX文件,该过程主要完成Java字节码转换成Dalvik字节码,压缩常量池以及清除冗余信息等工作。
通过ApkBuilder工具将资源文件、DEX文件打包生成APK文件。
利用KeyStore对生成的APK文件进行签名。
如果是正式版的APK,还会利用ZipAlign工具进行对齐处理,对齐的过程就是将APK文件中所有的资源文件举例文件的起始距离都偏移4字节的整数倍,这样通过内存映射访问APK文件的速度会更快。

在这里插入图片描述

1.17 APK安装流程 ?

Android apk的安装过程主要氛围以下几步:

复制APK到/data/app目录下,解压并扫描安装包。
资源管理器解析APK里的资源文件。
解析AndroidManifest文件,并在/data/data/目录下创建对应的应用数据目录。
然后对dex文件进行优化,并保存在dalvik-cache目录下。
将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中。
安装完成后,发送广播。
可以使用下面的图表示:
在这里插入图片描述


1.18 Handle的原理

Android中主线程是不能进行耗时操作的,子线程是不能进行更新UI的。所以就有了handler,它的作用就是实现线程之间的通信。
handler整个流程中,主要有四个对象,handler,Message,MessageQueue,Looper。当应用创建的时候,就会在主线程中创建handler对象,我们通过要传送的消息保存到
Message中,handler通过调用sendMessage方法将Message发送到MessageQueue中,Looper对象就会不断的调用loop()方法不断的从MessageQueue中取出Message交给handler进行处理。从而实现线程之间的通信。

在这里插入图片描述
出现内存泄露处理方案?

(1). 使用静态内部类+弱引用的方式:

静态内部类不会持有外部类的的引用,当需要引用外部类相关操作时,可以通过弱引用还获取到外部类相关操作,弱引用是不会造成对象该回收回收不掉的问题,不清楚的可以查阅JAVA的几种引用方式的详细说明。

private Handler sHandler = new TestHandler(this);

static class TestHandler extends Handler {
    private WeakReference<Activity> mActivity;
    TestHandler(Activity activity) {
        mActivity = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Activity activity = mActivity.get();
        if (activity != null) {
            //TODO:
        }
    }
}

(2). 在外部类对象被销毁时,将MessageQueue中的消息清空。例如,在Activity的onDestroy时将消息清空。

@Override
protected void onDestroy() {
    handler.removeCallbacksAndMessages(null);
    super.onDestroy();
}

sendMessageDelayed是如何实现延时发送消息的?
Handler在发送消息的时候,MessageQueue里的消息是按照发送时间点从小到大排列的,如果最近的Message未到达发送的时间则阻塞。

sendMessageDelayed是通过阻塞来达到了延时发送消息的结果,那么会不会阻塞新添加的Message?

新加入的数据会根据时间点的大小判断需要插入的位置,同时还需要判断是否需要唤醒线程去发送当前的队首的消息。


2 性能类

2.1 内存泄露原因?

内存泄露的根本原因:长生命周期的对象持有短生命周期的对象。短周期对象就无法及时释放。

  1. 静态集合类引起内存泄露

主要是hashmap,Vector等,如果是静态集合 这些集合没有及时setnull的话,就会一直持有这些对象。

  1. remove 方法无法删除set集

Objects.hash(firstName, lastName);

经过测试,hashcode修改后,就没有办法remove了。

  1. observer 我们在使用监听器的时候,往往是addxxxlistener,但是当我们不需要的时候,忘记removexxxlistener,就容易内存leak。

广播没有unregisterrecevier

  1. 各种数据链接没有关闭,数据库contentprovider,io,sokect等。cursor

  2. 内部类:

java中的内部类(匿名内部类),会持有宿主类的强引用this。

所以如果是new Thread这种,后台线程的操作,当线程没有执行结束时,activity不会被回收。

Context的引用,当TextView 等等都会持有上下文的引用。如果有static drawable,就会导致该内存无法释放。

  1. 单例

单例 是一个全局的静态对象,当持有某个复制的类A是,A无法被释放,内存leak。

  1. 使用Rxjava在生命周期结束后没有取消订阅,会发生内存泄露。

可以使用RxLifecycle或者AutoDispose进行 解决。详情见下文第三方库类的解析。


2.2 如何避免OOM?

减少内存对象的占用

I.ArrayMap/SparseArray代替hashmap

II.避免在android里面使用Enum

III.减少bitmap的内存占用

inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。
decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。
IV.减少资源图片的大小,过大的图片可以考虑分段加载

内存对象的重复利用

大多数对象的复用,都是利用对象池的技术。

I.listview/gridview/recycleview contentview的复用

II.inBitmap 属性对于内存对象的复用ARGB_8888/RBG_565/ARGB_4444/ALPHA_8

这个方法在某些条件下非常有用,比如要加载上千张图片的时候。

III.避免在ondraw方法里面 new对象

IV.StringBuilder 代替+


2.3 怎么避免ANR?

ANR的关键

是处理超时,所以应该避免在UI线程,BroadcastReceiver 还有service主线程中,处理复杂的逻辑和计算

而交给work thread操作。

1)避免在activity里面做耗时操作,oncreate & onresume

2)避免在onReceiver里面做过多操作

3)避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。

4)尽量使用handler来处理UI thread & workthread的交互。


2.4 APK如何瘦身?

先拖入APK到AS中,进行分析。

  1. so库只保留armeabi-v7a。

armeabi-v7主要不支持ARMv5(1998年诞生)和ARMv6(2001年诞生).目前这两款处理器的手机设备基本不在我公司的适配范围(市场占比太少)。
而许多基于 x86 的设备也可运行 armeabi-v7a 和 armeabi NDK 二进制文件。对于这些设备,主要 ABI 将是 x86,辅助 ABI 是 armeabi-v7a。

  1. 手动lint检查,手动删除无用资源

在Android Studio中打开“Analyze” 然后选择"Inspect Code…",范围选择整个项目,然后点击"OK"。

  1. 使用tinypng等图片压缩工具对图片进行压缩。

打开网址https://tinypng.com/,将大图片导入到tinypng,替换之前的图片资源。

  1. 大部分图片使用Webp格式代替。

  2. 尽量不要在项目中使用帧动画

这里可以参考使用lottie-android https://github.com/airbnb/lottie-android

  1. 使用gradle开启shrinkResources

minifyEnable true
shrinkResources true

  1. 减少chasses.dex大小

尽量减少第三方库的引用;
慎重使用枚举(内存占用较大);

  1. 删除翻译资源,只保留中英文
  2. 尝试将andorid support库彻底踢出你的项目。
  3. 尝试使用动态加载so库文件,插件化开发。
  4. 将大资源文件放到服务端,启动后自动下载使用。
  5. resConfigs剔除第三方库或者SDK中的资源

resConfigs “zh” //表示只使用中文
resConfigs “xxhdpi” // 表示只是用xxhdpi目录下的资源文件

  1. 使用shape(Vector)替换图片。

2.5 热修复的原理

我们知道Java虚拟机 —— JVM 是加载类的class文件的,而Android虚拟机——Dalvik/ART VM 是加载类的dex文件,而他们加载类的时候都需要ClassLoader,ClassLoader有一个子类BaseDexClassLoader,而BaseDexClassLoader下有一个数组——DexPathList,是用来存放dex文件,当BaseDexClassLoader通过调用findClass方法时,实际上就是遍历数组,找到相应的dex文件,找到,则直接将它return。而热修复的解决方法就是将新的dex添加到该集合中,并且是在旧的dex的前面,所以就会优先被取出来并且return返回。

2.6 APP优化方案

App启动优化
Application的onCreate(特别是第三方SDK初始化),首屏Activity的渲染都不要进行耗时操作,如果有,就可以放到子线程或者IntentService中

布局优化
尽量不要过于复杂的嵌套。Hierarchy Viewer 分析布局

响应优化
Android系统每隔16ms会发出VSYNC信号重绘我们的界面(Activity)。
页面卡顿的原因:
(1)过于复杂的布局.
(2)UI线程的复杂运算
(3)频繁的GC,导致频繁GC有两个原因:1、内存抖动, 即大量的对象被创建又在短时间内马上被释放.2、瞬间产生大量的对象会严重占用内存区域。

内存优化

电池使用优化
Batterystats & bugreport
(1)优化网络请求 Network Monitor
(2)定位中使用GPS, 请记得及时关闭

网络优化

电量优化



2.7 Android 插件化开发?


3 Java与JVM类

3.1 ThreadFactory与BlockingQueue?

java线程池技术(一):ThreadFactory与BlockingQueue - WangLei_ClearHeart - 博客园


3.2 线程池创建方法?

线程


3.3 如何确保线程安全的方法?

确保线程安全的方法有这几个:

  • 竞争与原子操作
  • 同步与锁、
  • 可重入、
  • 过度优化。volatile。

3.4 synchronized的作用

应用场景
保证线程安全,解决多线程中的并发同步问题(实现的是阻塞型并发),具体场景如下:
a. 修饰 实例方法 / 代码块时,(同步)保护的是同一个对象方法的调用 & 当前实例对象
b. 修饰 静态方法 / 代码块时,(同步)保护的是 静态方法的调用 & class 类对象

原理
a. 依赖 JVM 实现同步
b. 底层通过一个监视器对象(monitor)完成, wait()、notify() 等方法也依赖于 monitor 对象
注:监视器锁(monitor)的本质 依赖于 底层操作系统的互斥锁(Mutex Lock)实现

在这里插入图片描述
底层通过一个监视器对象实现,依赖于操作系统的互斥锁实现,操作系统实现线程切换需从用户态转为内核态。

与其他控制并发 / 线程同步方式对比
此处主要对比的是:LockReentrantLock

在这里插入图片描述
公平锁、可中断、绑定多个condition对象。


3.5 公平锁 VS 非公平锁的区别?

公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。

公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。

注意在condition.await()方法调用之前,必须先lock.lock()获得锁。


3.6 四大引用?

引用(Reference)为垃圾收集提供了更大的灵活性,根据不同的适用场景,引用可以分为4类:
•强引用(Strong Reference)

强引用就是指在程序代码中普遍存在的,类似于“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象;
•软引用(Soft Reference)——SoftReference

软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。(只有内存不足时,才会回收软引用对象)。
•弱引用(Weak Reference)——WeakReference

弱引用也是用来描述非必需对象的,但它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存释放足够,都会回收掉只被弱引用关联的对象。(只要发生了垃圾收集,弱引用对象都会被回收)
•虚引用(Phantom Reference)——PhantomReference

虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用获取一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

3.7 线程中sleep和wait的区别?

(1)这两个方法来自不同的类,sleep是来自Thread,wait是来自Object;
(2)sleep方法没有释放锁,而wait方法释放了锁。
(3)wait,notify,notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

3.8 String,StringBuffer,StringBuilder区别?

1、三者在执行速度上:StringBuilder > StringBuffer > String (由于String是常量,不可改变,拼接时会重新创建新的对象)。
2、StringBuffer是线程安全的,StringBuilder是线程不安全的。(由于StringBuffer有缓冲区)


4 概念类

4.1 Http https区别?

1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

https实现原理
(1)客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
(2)Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
(3)客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
(4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
(5)Web服务器利用自己的私钥解密出会话密钥。
(6)Web服务器利用会话密钥加密与客户端之间的通信。

4.2 OSI网络分层结构

在这里插入图片描述
在这里插入图片描述
物理层:设备连接媒介
链路层:ARP (MAC)
网络层:IP (子网络)
传输层:TCP 、UDP (端口号)
应用层:HTTP、FTP (应用格式)

4.3 Tcp/IP三次握手,四次挥手

三次握手:
1. 主动发起请求端, 发送 SYN
2. 被动建立连接请求端 , 应答ACK 同时 发送 SYN
3. 主动发起请求端,发送应答 ACK
* 标志 TCP 三次握手建立完成。 —— server:Accept() 返回 。— client:Dial() 返回。

四次挥手:
1. 主动关闭连接请求端, 发送 FIN
2. 被动关闭连接请求端 ,应答 ACK
标志。半关闭完成。 —— close()
3. 被动关闭连接请求端 ,发送 FIN
4. 主动关闭连接请求端,应答 ACK
标志。四次挥手建立完成。 —— close().

在这里插入图片描述
TCP转换图解析:
在这里插入图片描述
1,主动发起连接请求端口close>完成三次握手>EATABLISEHED(数据通信状态)> Dial()函数返回

2,被动发起连接请求端: CLOSED > 调用Accept()函数>LISTEN 完成三次握手 >ESTABLISEHED (数据通信状态)>Accept()函数返回>数据传递期间 —— ESTABLISEHED (数据通信状态)

主动关闭连接请求端:ESTABLISEHED>FIN_WAIT_2 (半关闭)>TIME_WAIT >2MSL >确认最后一
个ACK被对端成功接收>CLOSE>半关闭、TIME_WAIT、2MSL

只会出现在 “主动关闭连接请求端”

被动关闭连接请求端:ESTABLISEHED>CLOSE

查看状态命令:
windows:netstat -an | findstr 8001(端口号)
Linux: netstat -apn | grep 8001



5 算法类

5.1 什么是红黑树?

参考链接:https://www.jianshu.com/p/e136ec79235c

红黑树定义和性质
红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须满足下面性质:

性质1:每个节点要么是黑色,要么是红色。
性质2:根节点是黑色。
性质3:每个叶子节点(NIL)是黑色。
性质4:每个红色结点的两个子结点一定都是黑色。
性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。

从性质5又可以推出:

性质5.1:如果一个结点存在黑子结点,那么该结点肯定有两个子结点

在这里插入图片描述

  • 左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。如图3。

  • 右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。如图4。

  • 变色:结点的颜色由红变黑或由黑变红。

查找最坏时间复杂度为O(2lgN),也即整颗树刚好红黑相隔的时候。


5.2 冒泡算法?

原理:两两对比,大的数子靠后排。

冒泡排序法
冒泡排序法


6 第三方框架类

6.1 RxLifecycle

参考链接:
解决RxJava内存泄漏(前篇):RxLifecycle详解及原理分析

6.2 AutoDispose

Android架构中添加AutoDispose解决RxJava内存泄漏 - 却把清梅嗅的博客 - CSDN博客

6.3 MVVM

开源demo
[GitHub - qingmei2/MVVM-Rhine: The MVVM Architecture in Android(MVVM+Jetpack的开发实践)](https://github.com/qingmei2/FlutterGitHubApp https://github.com/qingmei2/MVVM-Rhine)

6.4 AndroidX

如何迁移?
1、将 IDEA 升级到 3.2 以上的版本
2、升级 Gradle 的版本
打开 gradle-wrapper.properties,将 gradle 的版本升级为 4.10
3、升级插件版本号
打开 project 工程下的 build.gradle 文件(注意不是 module 下的)
将 gradle 插件的版本升级为 3.2.0+
dependencies {
classpath “com.android.tools.build:gradle:3.2.1”

}
打开 module 下面的 build.gralde 文件
compileSdkVersion 更改为 28
targetSdkVersion 更改为 28
buildToolsVersion 更改为 28.0.2
修改gradle.properties
android.useAndroidX=true
android.enableJetifier=true
其中
android.useAndroidX=true 表示当前项目启用 androidx
android.enableJetifier=true 表示将依赖包也迁移到androidx。如果取值为false,表示不迁移依赖包到androidx,但在使用依赖包中的内容时可能会出现问题,当然了,如果你的项目中没有使用任何三方依赖,那么,此项可以设置为false
4、使用 Migrate to androidx
点击 Android Studio 导航条的 Refactor 中的 Migrate to androidx, 即可一键转为 androidX

5迁移后续
5.1 手动修改错误包名
由于"Migrate to AndroidX"执行之后,部分控件的包名/路径名转换的有问题,所以还需要我们手动调整(包括修改xml布局文件和.java/.kt 文件)。
5.2 修复DataBinding中的错误(重名id错误)
在 AndroidStudio3.2 + androidx 环境下,对错误的检查和处理更为严格。如果同一个xml布局文件中存在同名id,在之前的版本中,我们可以正常编译和运行,但是,在新的环境下, 必然会报错。
5.3 去除 attr.xml 中重复的属性名称
在迁移到 androidX 之前,我们为自定义控件编写自定义属性时,可以与android已有的属性重名,但是,在AndroidX环境下不行了,如果存在重名的情况, 必然会报错——会提示你重复定义(详细错误信息没截图,但翻译过来就是重复定义了attr/xxx)。
5.4 Glide中的注解不兼容androidX
5.5报错:
error: resource android:attr/dialogCornerRadius not found
error: resource android:attr/fontVariationSettings not found
error: resource android:attr/ttcIndex not found
错误分析:
这个报错是因为 Android P,也就是 sdk api level 为 28
如果有 Cordova 插件使用了 com.android.support:xxxxx-v7:+ 这种不带版本号的方式引入 Android Support 库,那么最新的 com.android.support v28 就会被用上,但是 cordova 目前还不支持 28 的 Support 库
所以报错。


6.5 LeakCanary

用法?原理?
以下用法可以监控Activity

public class ExampleApplication extends Application {
    @Override 
    public void onCreate() {
        super.onCreate();
        // 如果是在HeapAnalyzer进程里,则返回,因为该进程是专门用来堆内存分析的。
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        //调用LeakCanary.install()的方法来进行必要的初始化工作,来监听内存泄漏。
        LeakCanary.install(this);
        // Normal app init code...
    }
}

监控Fragment

public class ExampleApplication extends Application {

    public static RefWatcher getRefWatcher(Context context) {
        ExampleApplication application = (ExampleApplication) context.getApplicationContext();
        return application.refWatcher;
    }
    private RefWatcher refWatcher;
    @Override public void onCreate() {
        super.onCreate();
        refWatcher = LeakCanary.install(this);
    }
}

public abstract class BaseFragment extends Fragment {

    @Override 
    public void onDestroy() {
        super.onDestroy();
        RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
        refWatcher.watch(this);
    }
}

原理:
从LeakCanary的install()方法可以看到,它主要初始化一些参数,为监控对象是否发生了泄漏做准备工作。其主要的工作有:
1.通过Builder模式,构建一个RefWatcher对象,RefWatcher对象里面包含了很多内存泄漏相关的辅助类。
2.通过Application的registerActivityLifecycleCallbacks()方法注册生命周期回调接口,可以让RefWatcher在Activity被销毁的时候OnDestory()中,开始监控Activity引用是否发生了内存泄漏。

RefWatcher refWatcher = LeakCanary.install(context);
// 监控
refWatcher.watch(objReference);

内部维护一个弱引用列表,所有可回收的引用都在列表内,然后启动一次内存gc,如果还在列表内,证明改引用被持有,发生了内存泄漏。

6.6 Retrofit

App应用程序通过Retrofit请求网络,实际上是使用Retrofit接口层封装请求参数,Header、URL等信息,之后由OKHttp完成后续的请求,在服务器返回数据之后,OKHttp将原始的结果交给Retrofit,最后根据用户的需求对结果进行解析。

6.7 图片加载库对比

大小对比:
Picasso:120K
Glide:475K
Fresco:3.4M
Android-Universal-Image-Loader:162K

专业性能:Fresco(图片社交类APP)
Picasso < Android-Universal-Image-Loader < Glide < Fresco

Fresco
Fresco 是 Facebook 推出的开源图片缓存工具,主要特点包括:两个内存缓存加上 Native 缓存构成了三级缓存,
优点:

  1. 图片存储在安卓系统的匿名共享内存, 而不是虚拟机的堆内存中, 图片的中间缓冲数据也存放在本地堆内存, 所以, 应用程序有更多的内存使用, 不会因为图片加载而导致oom, 同时也减少垃圾回收器频繁调用回收 Bitmap 导致的界面卡顿, 性能更高。

  2. 渐进式加载 JPEG 图片, 支持图片从模糊到清晰加载。

  3. 图片可以以任意的中心点显示在 ImageView, 而不仅仅是图片的中心。

  4. JPEG 图片改变大小也是在 native 进行的, 不是在虚拟机的堆内存, 同样减少 OOM。

  5. 很好的支持 GIF 图片的显示。

  6. 缺点:1. 框架较大, 影响 Apk 体积2. 使用较繁琐

Universal-ImageLoader
依赖于HttpClient,已经停止维护。


6.8 Gson与Json的对比

fastJson性能要好于Gson,但是在Bean转Json可能会有问题。


6.9 Volley

GitHub - google/volley https://github.com/google/volley

在这里插入图片描述
在这里插入图片描述

6.10 EventBus

EventBus原理

6.11 OKHTTP

OKHTTP原理

参考链接

参考链接:https://www.jianshu.com/p/70de36ea8b31
原文链接:https://blog.csdn.net/xiangzhihong8/article/details/96280254
链接:https://www.jianshu.com/p/dab1fcf0109d

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值