Android 面试知识总结

Android知识点

1. 四大组件

分别是Activity、Service、ContentProvider、BroadcastReceiver。

Activity称为活动,属于展示型组件,主要负责显示界面布局、跟用户进行交互。活动之间通过Intent进行通信。

Service称为服务,属于计算型组件,主要负责在后台处理一些长期的计算型工作。其没有界面,生命周期较长,主要在后台运行。

ContentProvider称为内容提供者,属于数据共享型组件,主要负责组件或应用之间的数据共享。通过ContentProvider,可以实现应用间共享数据的存储,以及SQLite数据库的操作等。

BroadcastReceiver称为广播提供器,属于消息型组件,主要负责监听外界事件的变化以及对外部事件作出反应,自定义的广播提供器可以用来在不同组件或应用之间传递数据等。

2. Activity生命周期

onCreate() -> onStart() -> onResume() -> onPause() -> onStop() -> onDestory()

Activity首次创建时:

刚上来系统会调用onCreate()方法,在此方法中需要调用setContent()方法进行布局文件的引入。

接下来会调用onStart()方法,在此方法调用中,应用仍处在后台,无法与用户进行交互。

接下来会调用onResume()方法,此方法调用后应用就来到前台,开始跟用户交互。

如果此时,界面弹出一个Dialog,挡在了Activity前面,会调用onPause()方法。此处可以进行数据存储、停止动画、注销广播监听等操作,但不能做耗时操作。如果接着,Dialog退出,此时系统会紧接着调用onResume()方法,然后恢复界面原有的布局

如果此时,继续按下电源键,系统锁屏,系统会陆续调用onPause()、onStop()方法。onStop中可进行一些回收工作,进行资源释放等等。

接下来,进行解锁,系统会调用onRestart()方法,使得Activity回到界面最顶层,此时可以进行一些数据恢复等操作,接着系统会陆续调用onStart()、onResume()等方法。

如果锁定屏幕时,应用被系统杀掉进程,此时系统就会调用onDestory()方法,此处可以进行一些资源回收操作。再次打开应用,可以发现应用已经退出。

Fragment 的启动顺序

  • 当 Activity 回调 onCreate() 时,Fragment 会依次回调 onAttach()、onCreate()、onCreateView()、onViewCreated() (Activity 回调 onCreate() 前调用)、onActivityCreated()(Activity 回调 onCreate() 后调用) 方法。
  • 当 Activity 回调 onDestory() 时,Fragment 会依次回调 onDestoryView()、onDestory()、onDetach() 方法(Activity 回调 onDestory() 前调用)
  • 除以上指出的两个回调方法外,其余 Fragment 启动方法的回调顺序与 Activity一致。

Activity 与 Fragment 联合起来的启动顺序如下:

3. Activity的启动模式

Activity使用任务栈管理,后进先出,返回栈——每按一个返回键,就有Activity出栈

a) Standard 标准模式。如果要启动的这个Activity是标准模式,这个Activity就会位于栈顶。

b) singleTop栈顶复用模式。如果要启动的这个Activity是栈顶复用模式,其又在当前栈顶,就不会启动新的Activity。此时会回调onNewIntent()方法。

c) singleTask栈内复用模式。如果要启动的这个Activity是栈内复用模式,其又在当前返回栈内,就不会启动新的Activity。此时会回调onNewIntent()方法。

d) singleInstance单例模式。如果要启动的这个Activity是单例模式,直接创建一个新的任务栈管理Activity,不同应用共享该栈的Activity实例。

如果Activity启动过程由一个栈跳向了另一个栈,在返回过程中,当前栈Activity全部退出后,才会切换到另外一个栈。除单例模式的Activity外,在Activity启动过程中,不会新增别的任务栈。

4. Service加载过程

Service主要分类有LocalService和RemoteService、BackgroundService和ForegroundService以及可通信的Service和不可通信的Service。

远程服务与本地服务最大的区别是:远程Service与调用者不在同一个进程里(即远程Service是运行在另外一个进程);而本地服务则是与调用者运行在同一个进程里。

服务使用的方式有三种,一种是通过手动调用startService()启动服务,第二种是通过bindService()绑定服务,第三种是先通过startService()启动服务,再通过bindService()绑定服务。

第一种方式的过程:外界调用startService()后,系统调用Service的onCreate()创建Service,然后通过onStartCommand()执行一些操作。当外界手动调用stopService()后,系统调用Service的onDestroy()方法销毁服务。

第二种方式的过程:外界通过bindService()后,系统调用Service的onCreate()创建Service,然后通过onBind()方法执行一些操作,并返回一个IBinder对象。当外界手动调用unBindService()后,系统会依次调用onUnbind()、onDestory()方法销毁Service。

第三种方式的过程:外界调用startService()后,系统调用Service的onCreate()创建Service,然后通过onStartCommand()执行一些操作。之后外界调用onBind()方法绑定Service()。绑定过的Service,外界要是想停止,只能通过先unbindService()解绑Service,再通过stopService()停止Service,此时Service才会被系统正常销毁。

多次调用startService,该Service只能被创建一次,即该Service的onCreate方法只会被调用一次。但是每次调用startService,onStartCommand方法都会被调用。

调用多次bindService,onCreate和onBind也只在第一次会被执行。
调用unbindService结束服务,生命周期执行onDestroy方法,并且unbindService方法只能调用一次,多次调用应用会抛出异常。

当onBind()返回的IBinder不为null时,意味着绑定方可以与此Service进行通信。bindService()方法要求传入一个ServiceConnection对象。

5. AIDL通信

AIDL是Android Interface Definition Language,意为Android接口定义语言,为实现应用间IPC而引入。AIDL是基于服务的。

IPC是Inter-Process Communication,意为跨进程通信。

AIDL实际上是一个模板,我们编辑好AIDL文件,Android系统会据此生成一系列实例代码。

进程间通信有2个角色,一个是服务器,一个是客户端。服务端通过新增AIDL文件,声明该服务需要向客户端提供的接口;还要在Service子类中实现AIDL中定义的接口方法,并在AndroidManifest文件中注册声明为远程服务;

客户端通过复制服务端新增的AIDL文件,获取服务器的Binder,并根据需要调用服务提供的接口方法,来达到实现跨进程通信的目的。

6. BroadcastReceiver注册方式

一是静态注册,指的是在AndroidManifest文件中通过receive标签进行BroadcastReceiver类文件注册。此类注册适合需要常驻注册的广播事件;

另一种是动态注册,指的是在代码中调用registerReceiver()方法进行注册。此类注册不会常驻系统,比较灵活,跟随组件的声明周期变化,在组件结束前需手动反注册。动态广播最好在Activity 的 onResume()注册、onPause()注销

7. 各大组件超时未响应发生ANR的超时时间:

Service:前台20s,后台200s

BroadcastReceiver:前台10s,后台60s

Activity:5s

8. 常用布局

LinearLayout(线性布局),RelativeLayout(相对布局),GridLayout(网格布局),ConstraintLayout(约束布局)

9. 常用View

TextView、Button、Image、CheckBox、ProcessBar、ListView、Dialog、RecyclerView、ViewPager

10. 自定义View基础

View,称为视图,可分为两类:

一类是单个View,不包含子View的,比如TextView;

另一类是由多个View组成的ViewGroup,包含子View,比如LinearLayout。

View是各个组件的基类。

根据创建View的方式不同,比如从Java代码中创建、在布局文件中声明、在布局文件中声明且含有style属性等方法,可知自定义View的构造函数有4个,实现自定义View至少需要重写一个构造函数。

ViewGroup是树形结构。在View绘制过程中,都是从View树的顶端根节点开始,一层一层、一个一个分支地自上而下往下遍历进行,最终到达终端节点,绘制完毕。

View的位置是相对于父控件而言的。

11. View绘制的过程

大致上有三大过程:一是Measure过程,二是Layout过程,三是Draw过程。绘制是由ViewRoot类完成的。

Measure用来测量View的宽或高;Layout用来计算View的位置;Draw用来绘制具体的试图。

12. View的事件分发机制

这里

13. Handler机制

这里

14. 多线程

基础使用:

- 继承Thread类:单继承局限,不适合资源共享
- 实现Runnable接口:可实现多个接口,适合资源共享

高级使用:AsyncTask、HandlerThread、IntentService(所有任务依次运行)、线程池(ThreadPool)

线程池中的核心线程会一直存活

15. 常用框架

Glide、EventBus、Volley

Java知识点

1. 面向对象的概念

继承、封装和多态。

继承指的是用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的成员,也可以使用父类的成员,但不能选择性继承父类。子类拥有父类非private的属性和方法,也可以拥有自己的属性和方法,也可以通过自己的方式实现父类的方法。

封装指的是利用抽象数据类型将数据和基于数据的操作封装在一起,使之构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。

多态指的是程序中定义的引用变量锁所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才能确定。多态实现有三个必要条件:继承、重写、向上转型。多态的两个实现方式分别是继承和接口。

2. 重载和重写的区别

重载指的是方法的重载,也就是 Java 类中方法名相同而参数不同的方法,可构成方法的重载。重载的方法,返回值类型可以相同,也可以不相同

重写指的是子类继承父类时,父类的方法可以被子类重写,也就是说子类可以根据自己的需要,实现自己特定的行为。 重写时,子类中的访问权限可以修改(访问权限可以同级或者放大,不能缩小)

3. 接口和抽象类的区别

接口中只有方法的定义,没有方法的实现(default方法体除外);抽象类可以有方法的定义和实现(非抽象方法)

接口成员变量默认为public static final,必须赋初始值、修改,成员方法均为public abstrac的;抽象类中成员变量默认为default,可在子类中重写定义,也可以重写赋值,抽象方法被abstract修饰,无法被private、static、synchronized和native等修饰,没有方法体。

接口只能继承接口,可以同时继承多个接口(接口可以多继承);抽象类可以继承抽象类,可以继承实体类,可以实现接口,不能多继承

抽象类和接口都不能被实例化。

5. 进程和线程的区别

  • 进程:并发执行的程序在执行过程中分配和管理资源的基本单位。
  • 线程:线程是进程的一个执行单元,是进程的一个执行单位,是比进程更小的独立运行的基本单位
  • 进程让操作系统的并发性成为了可能,而线程让进程的内部并发成为了可能。

进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位。

6. Java常用设计模式

  1. 单例模式

    单例模式要求在代码的任何位置获取到的相关类的实例都是唯一的。Android中很多地方都用到了单例模式,比如输入法管理者的方法InputMethodManager.getInstance()

  2. 观察者模式

    观察者模式使用的地方,有一个是ContentObserver,叫做内容观察者,ContentObserver通过观察特定Uri引起的数据库的变化,进而做一些相应的处理。多个观察者对象同时监听同一目标对象,目标的状态发生变化时,会通知所有的观察者对象,使他们进行一些相应的处理。

  3. 建造者模式

    建造者模式比较明显的特点就是链式调用,通过一连串的调用完成对某一对象的创建。建造者模式使用的场景,通常见于创建一个类的构造函数有多个,且部分参数为可选参数的对象时,此时使用传统方式会导致样板代码过多,尤其是构造函数过多,而且构建对象的过程中对象的状态容易发生错误导致变化,使用建造者模式就可以解决这个痛点。

  4. 工厂模式

    工厂模式提供了一种创建对象的最佳方式,工厂模式使对象的创建过程延迟到接口的实现进行。

  5. 适配器模式

    适配器模式主要用于列表样式中数据的适配,比如说RecyclerView中的数据适配。

  6. 发布/订阅模式

    发布/订阅模式和观察者模式类似,但也有一些不同。发布/订阅的核心是事件。该模式中,事件的发布者不会直接将消息发送给特定的接收者(订阅者),而是需要一个中间者去进行协调,这个中间者就是事件中心,协调发布者和订阅者的直接通信。订阅者在订阅事件的时候,只关注事件本身,而不关心谁会发布这个事件;发布者在发布事件的时候,只关注事件本身,而不关心谁订阅了这个事件。比如说在EventBus框架中,定义一个简单Java对象作为事件,使用注解指定订阅者方法并在Activity生命周期中订阅和注销事件,事件的发布只需在任意代码的任意位置使用Event.getDefault().post()即可。

5. Java单例模式的实现

特点:单例类只有一个实例;单例类必须自己创建自己的一个实例;单例类必须给所有其他对象提供这一实例。

实现方式:

  1. 懒汉式:在第一次调用的时候进行实例化。

    • 静态内部类方式
      • 构造方法私有化
      • 私有静态内部类初始化一个私有静态final实例
      • 获取实例方法公开化,返回的示例即为静态内部类所创建的实例
  2. 饿汉式:初始化时已自行实例化。

    • 构造方法私有化

    • 在类中创建一个静态final实例

    • 获取实例方法公开化,返回的实例即为类中创建实例

6. 如何实现同步

Java使用Synchronized关键字实现同步。保证同一时间只有1个线程执行被Synchronized修饰的方法或代码块。

修饰 实例方法 / 代码块时,(同步)保护的是同一个对象方法的调用 & 当前实例对象——对象锁
修饰 静态方法 / 代码块时,(同步)保护的是 静态方法的调用 & class 类对象——类锁

保证原子性、可见性、有序性。

7. 引用的类型

引用(只要不为null就不会被回收)、引用(不论内存是否不足都会回收)、引用(内存不足时会回收)、引用(随时可能被回收,与引用队列一起使用)

JDK1.2 之前,一个对象只有“已被引用”和"未被引用"两种状态,这将无法描述某些特殊情况下的对象,比如,当内存充足时需要保留,而内存紧张时才需要被抛弃的一类对象。
所以在 JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4 种,这 4 种引用的强度依次减弱。
一,强引用
Object obj = new Object(); //只要obj还指向Object对象,Object对象就不会被回收 obj = null; //手动置null
只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了
二,软引用
软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。
在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。
三,弱引用
弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。
四,虚引用
虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。

8. 构造函数的继承

一句话:父类的构造函数不会被子类所继承,只会被子类显式或隐式调用

父类构造函数的调用方法:super();

  • 父类(所有类都是)无构造函数时,系统会为父类创建一个空方法体的无参构造函数。
  • 父类(所有类都是)有有参构造函数时,系统将不再为父类创建一个空方法体的无参构造函数,除非在代码中再次声明无参构造函数。
  • 隐式调用 :父类有无参构造函数时(不论是系统自动创建的还是在代码中声明的),子类的构造函数(不论无参还是有参)将自动调用父类的无参构造函数。
  • 显式调用 :父类有有参构造函数,无无参构造函数时,必须在子类的构造函数(不论无参还是有参)中显式调用父类的有参构造函数,否则编译将无法通过。

9. final 关键字

final 作为 Java 中的关键字,可以修饰类、类的成员变量和类的成员方法。

final 修饰类时,表示此类无法被继承,并且此类的成员方法都会被隐式指定为final方法。

final 修饰类的成员变量时,分为修饰普通变量和引用型变量。

修饰普通变量时,它能使变量的值不再进行改变,即进行初始化赋值后不能再进行修改。

修饰引用变量时,它能使变量引用的目标不再改变,但无法确保引用型变量的"值"不再改变。引用型变量自身是可以进行改变的。

final 和 static 连用,可以实现Java中的常量。

final 确保了变量在使用前必须初始化,有助于减少空指针异常的发生。

final 修饰成员方法时,如果方法所属的类被继承,其在子类中无法被覆盖重写。类的私有方法会被隐式指定为final方法。

10. 开辟线程的方式

使用 Thread 类

  • 新建一个 MyThread 类,继承 Thread 类,并重写 run() 方法。新建一个 MyThread 对象,使用其 start() 开启线程。
public class Main {

    public static void main(String[] args) {
        System.out.println("Hello World!");
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println("main end");
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread run");
    }
}

可以确定的是,main线程中,先输出 Hello World,再输出 main end;但是无法确定自定义线程中 MyThread runmain线程中两句语句执行的先后顺序。
这是因为从MyThread线程开始运行以后,两个线程就开始同时运行了,并且由操作系统调度,程序本身无法确定线程的调度顺序

  • 新建一个 Thread 对象,在初始化的同时,重写 run() 方法。使用其 start() 方法开启线程。
public class Main {

    public static void main(String[] args) {
        System.out.println("Hello World!");
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("thread run");
            }
        };
        thread.start();
        System.out.println("main end");
    }
}

使用 Runnable 接口

  • 新建一个类,实现 Runnable 接口,并实现 run() 方法。新建一个 Thread 对象,在构造函数中传入自定义的类。在 Thread 对象中使用 start() 开启线程。
public class Main {

    public static void main(String[] args) {
        System.out.println("Hello World!");
        MyRunnable myRunnable = new MyRunnable();
        Thread myThread = new Thread(myRunnable);
        myThread.start();
        System.out.println("main end");
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("MyRunnable run");
    }
}
  • 新建一个 Runnable 接口,同时实现 run() 方法。新建一个 Thread 对象,在构造函数中传入新建的接口对象。在 Thread 对象中使用 start() 开启线程。
public class Main {

    public static void main(String[] args) {
        System.out.println("Hello World!");
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("runnable run");  
            }
        };
        Thread myThread = new Thread(runnable);
        myThread.start();
        System.out.println("main end");
    }
}
posted @ 2022-03-05 20:58  wx2020  阅读(729)  评论(0编辑  收藏  举报