1. JNI和NDK
1.谈谈你对JNI和NDK的理解
答:JNI(Java Native Interface
)是Java提供的一种机制,用于实现Java与其他编程语言(如C、C++)之间的交互。它允许Java代码调用本地代码(Native Code)并与其进行数据交换。
NDK(Native Development Kit
)是Android提供的一个工具集,用于在Android应用中使用本地代码。它包含了一系列的工具和库,可以将C、C++代码编译成与特定平台相关的本地库文件(例如.so文件),然后通过JNI将这些本地库文件嵌入到Android应用中。
JNI和NDK的关系是,JNI提供了Java与本地代码交互的机制,而NDK则提供了将本地代码集成到Android应用中的工具和库。通过JNI和NDK,开发者可以在Android应用中使用C、C++等本地语言编写高性能、底层的功能模块,比如图像处理、音频处理、加密算法等。
使用JNI和NDK可以带来以下优势:
1.性能优化:某些计算密集型任务,使用本地代码可以提高执行效率。
2.跨平台开发:通过使用本地代码,可以在不同平台上共享代码和库。
3.重用现有代码:如果已经有了C、C++等语言的现有代码,可以通过JNI和NDK将其集成到Android应用中,避免重复开发。
需要注意的是,在使用JNI和NDK时,需要谨慎处理内存管理、类型转换和异常处理等问题,以确保代码的正确性和稳定性。
2.简要的JNI调用过程:
1、编写本地代码:首先,需要编写包含所需功能的本地代码,通常是用C或C++编写。这些本地代码将被编译成共享库(.so文件)。
2、创建JNI接口:在Java代码中,需要声明与本地代码对应的JNI接口。这些接口方法将与本地代码中的函数进行绑定,以便在Java中调用。
3、生成头文件:使用Java Development Kit(JDK)
中的工具javah,生成包含JNI接口方法定义的头文件。
4、实现JNI接口:在本地代码中,需要实现JNI接口中声明的方法。这些方法将提供与Java代码之间的桥梁,使得Java可以调用本地代码。
5、编译本地代码:使用C/C++编译器将本地代码编译成共享库(.so文件)。
6、将共享库加载到应用程序:在Android应用程序中,可以使用System.loadLibrary()
方法加载共享库。
7、调用JNI方法:在Java代码中,通过调用JNI接口中声明的方法来调用本地代码。可以使用native关键字标记这些方法。
8、运行应用程序:在应用程序运行时,当Java代码调用JNI方法时,将触发与本地代码的交互,完成相应的功能操作。
需要注意的是,JNI调用涉及到本地代码的编写和编译,因此需要对C/C++有一定的了解。此外,在JNI调用过程中,需要注意内存管理和数据类型转换等问题,以确保安全性和正确性。
JNI调用过程包括编写本地代码、创建JNI接口、生成头文件、实现JNI接口、编译本地代码、加载共享库、调用JNI方法等步骤,通过这些步骤可以实现Java代码与本地代码之间的交互。
2. 线程、同步、异步
1.Java创建线程的方式有几种?start()方法和 run()方法的区别
答:在Java中,有两种主要的方式可以创建线程:
1.继承Thread类:通过继承Thread
类并重写其run()
方法来创建线程。首先,创建一个继承自Thread的子类,并在子类中实现run()
方法来定义线程的具体逻辑。然后,通过创建子类的实例对象,并调用其start()
方法来启动线程。
2.实现Runnable接口:通过实现Runnable
接口来创建线程。首先,创建一个实现了Runnable接口的类,并在该类中实现run()
方法。然后,创建Thread类的实例对象,将实现了Runnable接口的类的实例作为参数传递给Thread的构造函数。最后,调用Thread对象的start()
方法来启动线程。
区别:
- 使用继承Thread类的方式,线程的逻辑直接写在子类中的run()方法中,但这样会限制了子类的继承关系。
- 使用实现Runnable接口的方式,线程的逻辑在实现了Runnable接口的类中定义,可以更灵活地共享代码和资源,并且避免了单继承的限制。
关于start()方法和run()方法的区别:
- start()方法用于启动线程并异步执行线程的逻辑。调用start()方法后,系统会为线程分配资源并调用线程的run()方法。
- run()方法是线程的入口点,可以将线程的具体逻辑在run()方法中实现。但直接调用run()方法并不会启动新的线程,而是在当前线程中同步执行run()方法的代码块。
总结来说,Java创建线程的方式有继承Thread类和实现Runnable接口。start()
方法用于启动线程并异步执行线程的逻辑,而run()
方法是线程的入口点,在调用start()方法后由系统自动调用。
2.Handler 机制和原理
Handler
主要用于异步消息的处理:当发出一个消息之后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息进行处理,也就是发送消息和接收消息不是同步的处理。
基础概念
- UI线程
主线程ActivityThread
,主线程也是Ui线程,应用启动的时候会启动一个ui线程 - Handler
负责发送消息和处理消息。 - Looper
负责消息循环,循环取出MessageQueue
里面的Message
,并交给相应的Handler
进行处理。 - MessageQueue
消息队列,用来存放通过Hangdler
发送的消息,按照先进先出的顺序取出消息,内部使用的是单链表结构(优先级链表) - Message
Handler发送和处理的消息个体,有MessageQueue
管理,携带Handler信息和具体消息
抽象概念具体化:
Handler:快递员(收件(发送消息)和派件(处理消息))(收发都是统一快递员,只属于同一家快递公司)
Message:快递包裹(包裹中携带物品,和快递员身份消息)
MessageQueue:快递分拣中心(将快递按照时间优先级整理好包裹)
Looper:快递公司(不停地发送消息)
- 首先在UI线程我们创建了一个Handler实例对象,无论是匿 名内部类还是自定义类生成的Handler实例对象,我们都需 要对
handleMessage
方法进行重写, - 在
handleMessage
方法中我们可以通过参数msg来写接受消息过后UIi线程的逻辑处理,接着我们创建子线程 - 在子线程中需要更新U的时候,新建一个
Message
对象,并且将消息的数据记录在这个消息对象Message
的内部,比如arg1,arg2,obj等,然后通过前面的Handler实例对象调用sendMessge
方法把这个Message实例对象发送出去,之后这个消息会被存放于MessageQueue
中等待被处理, - 此时
MessageQueue
的管 家Looper正在不停的把MessageQueue
存在的消息取出 来,通过回调dispatchMessage
方法将消息传递给Handler的handleMessage
方法,最终前面提到的消息会被Looper 从MessageQueue
中取出来传递handleMessage
方法。
handler的使用方法
1.post(runnable) Runnable
则是直接给出处理的方法)在未来的某个时间进行相应处理源码底层还是调用了sendmassage(massage)
方法
2.sendmassage(massage)
放置信息,可以传递一些参数 ,可以定时处理更新UI
handler引起的内存泄露及解决办法
handler发送的消息在当前handler的消息队列中,如果此时activity finish掉了,那么消息队列的消息依旧会由handler进行处理,若此时handler声明为内部类(非静态内部类),我们知道内部类天然持有外部类的实例引用,这样在GC垃圾回收机制进行回收时发现这个Activity居然还有其他引用存在,因而就不会去回收这个Activity,进而导致activity泄露。
解决方案:
1.把handler
设置成静态内部类,因为静态内部类不持有外部类的引用,所以使用静态的handler不会导致activity的泄露
2.onDestroy
生命周期中调用 handler.removeCallbacks()
进行释放
3.handler
内部类持有外部activity
的弱引用
3.为什么在子线程中创建Handler会抛异常?
不能在还没有调用 Looper.prepare()
方法的线程中创建Handler
.因为抛出异常的地方,在mLooper
对象为null
的时候会抛出异常。说明这里的Looper.myLooper0
;的返回值是null
。 只有调用了Looper.prepare()
方法,才会构造一个Looper对象并在 ThreadLocal
存储当前线程的Looper对象
这样在调用 Looper.myLooper()
时,获取的结果就不会为null
4.Android中的ANR的解决方法
答:ANR(Application Not Responding
)指的是Android应用在主线程上执行耗时操作时出现的无响应现象,这可能导致应用无法响应用户输入,给用户带来不良体验。下面是几种常见的解决ANR问题的方法:
1.将耗时操作放在子线程中:将耗时的操作(如网络请求、数据库查询等)放在子线程中执行,避免阻塞主线程。可以使用Thread
、AsyncTask
、Handler
等方式创建子线程,确保主线程保持响应。
2.使用异步操作:对于一些需要等待结果的操作,例如网络请求或数据库查询,使用异步方式执行,如使用AsyncTask
、Handler
、RxJava
等框架来处理。
3.使用多线程技术:合理使用多线程技术,将一些繁重的计算或IO操作放在后台线程中执行,以减轻主线程的负担。
4.避免在主线程进行耗时的IO操作:例如文件读写、数据库操作等,这些操作可以使用异步方式或将其放在子线程中执行。
5.使用合适的数据结构和算法:优化代码逻辑,使用高效的数据结构和算法,减少不必要的计算和迭代。
6.避免频繁的UI更新:减少UI更新的次数,尽量批量更新UI,避免频繁调用UI更新方法。
7.使用定时器避免阻塞主线程:通过使用定时器(Timer)或者Handler的postDelayed()方法,将一些可能引起阻塞的操作延后执行,避免在主线程中长时间执行。
8.使用线程池:使用线程池来管理线程,避免频繁创建和销毁线程的开销。
9.检查和优化代码:定期检查代码,查找和消除潜在的性能问题,如内存泄漏、死锁等。
10.使用性能分析工具:使用Android Studio提供的性能分析工具,如Profiler、Traceview等,来分析应用的性能瓶颈,并优化相应的代码。
综上所述,通过合理的线程管理、使用异步操作、优化代码逻辑等方法,可以有效解决Android应用中的ANR问题,提升应用的响应性能
5.intentservice有什么优点?
答:IntentService是Android中的一个特殊Service,它主要用于在后台执行异步任务,处理Intent请求,并在任务完成后自动停止。
以下是IntentService的几个优点:
- 简化了异步任务的处理:IntentService封装了异步任务的处理逻辑,使得开发者可以更加专注于实现具体的任务逻辑,而无需关注线程管理和任务调度等细节。它通过创建一个单独的工作线程来处理任务,避免了在主线程中执行耗时操作导致的ANR(Application Not Responding,在主线程上执行耗时操作时出现的无响应现象)问题。
- 自动停止:IntentService在任务执行完成后会自动停止,无需手动调用stopSelf()方法来停止Service。这样可以避免Service长时间运行而消耗系统资源。
- 顺序执行:IntentService会按照任务的顺序依次处理Intent请求,确保每个Intent请求都能被正确处理。每次处理一个Intent请求时,它会将其他Intent请求放入队列中等待处理,保证了任务的顺序性。
- 线程安全:IntentService内部使用单个工作线程来处理任务,因此避免了多线程并发访问的问题。这样可以简化任务的编写,并减少并发导致的竞态条件和同步问题。
-
可以与其他组件进行通信:IntentService可以通过广播、回调或发送消息等方式与其他组件进行通信,便于任务的状态更新、结果的传递等。
需要注意的是,IntentService适用于执行一系列相对独立的任务,每个任务都是通过Intent进行触发和处理的。如果需要执行长时间运行的任务或与UI交互的任务,可能需要考虑其他方式,如使用HandlerThread或AsyncTask等。
6.okhttp异步请求流程
- 构建
OkhttpClient
对象
OkhttpClient:相当于配置中心,所有的请求都会共享这些配置,OkhttpClient
中定义了网络协议、DNS
、请求时间等等。创建对象的方式有两种,一种是通过直接new
对象的方式,另一种是通过Builder
模式设置参数来进行创建。 - 构建
Request
对象
Request:网络请求信息的封装类,内置url、head
、get/post
请求等。Request
对象的构建只能通过builder
模式来构建,具体的构建过程同OkhttpClient是一样的,都是使用了Builder构建模式。 - 创建
Call
对象
Call:网络请求的执行者,Call用来描述一个可被执行、中断的请求,client.newCall(request)
方法就是指创建一个新的将要被执行的请求,每一个Request最终将会被封装成一个Realcall对象。Realcall是Call接口唯一的实现类,AsyncCall是Realcall
中的内部类,你也可以把RealCall
理解为同步请求操作,而AsyncCall
则是异步请求操作。 - 发送异步请求
在准备工作(OkhttpClient
、Request
、Call
)都完成之后,接下来我们就要调用call.enqueue()
正式的开始进行网络请求了。前面我们提到,RealCall
是Call唯一的 实现类,所以我们来查看RealCall
中的enqueue()
。在enqueue
方法中首先会判断当前的call是否已经执行过一次,如果已经执行过的话,就会抛出一个异常,如果没有执行的话会给executed
变量进行赋值,表示已经执行过,从这里也可以看出call只能执行一次。接着我们看最后一行,一共做了两件事,先是封装了一个AsyncCall对象,然后通过client.dispatcher().enqueue()
方法开始实际的异步请求。进入AsyncCall中查看代码。看到AsyncCall类继承自NamedRunnable,紧接着我们再进入到NamedRunnable中可以看到它实现了Runnable接口,所以最终确定AsyncCall
就是一个Runnable
,看完封装AsyncCall对象之后,我们再来看一下client.dispatcher().enqueue()
,先是通过client.dispatcher()
获取到dispatcher对象,然后调用Dispatcher中的enqueue方法看完封装AsyncCall
对象之后,我们再来看一下client.dispatcher().enqueue()
,先是通过client.dispatcher()获取到dispatcher
对象,然后调用Dispatcher中的enqueue
方法 - Dispatcher分发器
Dispatcher:Dispatcher
是一个任务分发器,用于管理其对应OkhttpClient
的所有请求。它的主要功能如下:
发起/取消网络请求API:execute
、enqueue
、cancel。
线程池管理异步任务。
记录同步任务、异步任务及等待执行的异步任务(内部维护了三个队列)。 -
Interceptors
拦截器 - 获取
Response
响应 - 调用
finished
通过getResponseWithInterceptorChain()
拿到了服务器返回的响应Response
,然后进行请求成功或者请求失败的回调,到这里为止,一次完整的网络请求请求已经结束了。代码中有一个finally
,那代表着我们的请求不管成功与否,都会进入到这个finally当中
7.Okhttp的同步请求
- 创建
OkhttpClient
对象 - 请求报文创建,包含常用的请求信息,如
url
、get/post
方法,设置请求头等 - 创建
Call
对象 - 同步请求,发送请求后,就会进入阻塞状态,直到收到响应
8. 如何避免 OOM 异常
当程序需要申请一段“大”内存,但是虚拟机没有办法及时的给到,即使做了GC操作以后
这就会抛出 OutOfMemoryException
也就是OOM
如何避免OOM
减少内存对象的占用
-
ArrayMap/SparseArray
代替hashmap
- 避免在android里面使用Enum
- 减少
bitmap
的内存占用inSampleSize
:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。decode format
:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。 - 减少资源图片的大小,过大的图片可以考虑分段加载
9. Android 线程间通信有哪几种方式
1)共享变量(内存)
2)管道
3)handle机制
runOnUiThread(Runnable)
view.post(Runnable)
3. UI
1.谈谈你对 Bitmap 的理解,什么时候应该手动调用 bitmaprecycle():
答:Bitmap是Android中用于表示图像的类,它可以加载、创建和操作位图图像。它是像素的二维数组,每个像素用于表示图像的颜色和透明度。Bitmap类提供了各种方法来操作图像,包括缩放、裁剪、旋转、像素操作等。
手动调用bitmap.recycle()是用于释放Bitmap占用的内存资源。当不再需要一个Bitmap
对象时,可以调用recycle()
方法来显式释放内存,以便及时回收内存资源。这在以下情况下特别重要:
-
内存敏感性:当应用程序使用大量的位图资源时,尤其是较大的位图,及时释放不再使用的位图可以减少内存占用,避免
OutOfMemoryError
等内存相关问题。 -
频繁创建位图:如果应用程序频繁创建位图对象,但又没有及时释放旧的位图对象,会导致内存占用不断增加,可能会造成内存泄漏。因此,在创建新的位图对象之前,应该确保旧的位图对象已经被回收。
需要注意的是,调用recycle()方法后,Bitmap对象将变为无效状态,不能再对其进行任何操作。因此,在调用recycle()方法之后,应该避免对该Bitmap对象进行任何读取或写入操作。
另外,从Android 3.0(API级别11)开始,Bitmap的内存会自动进行垃圾回收,不再需要手动调用recycle()方法。因此,在较新的Android版本上,手动调用recycle()方法的必要性可能会降低。但对于旧版本的Android系统,特别是内存敏感的环境下,仍然建议及时调用recycle()方法来释放Bitmap对象占用的内存。
2. Android布局的五个常见类型:
1、LinearLayout(线性布局):LinearLayout是最简单和常见的布局类型之一。它按照水平或垂直方向排列子视图。可以使用android:orientation
属性设置为horizontal
(水平)或vertical
(垂直)。
2、RelativeLayout(相对布局):RelativeLayout允许子视图相对于父视图或其他子视图进行定位。可以使用各种规则(如alignParentTop
、alignParentLeft
、above
、below
等)来定义子视图之间的相对位置。
3、ConstraintLayout(约束布局):ConstraintLayout
是一个灵活且功能强大的布局类型。它使用约束条件来定义子视图之间的关系。可以通过拖拽、连接线、属性设置等方式将子视图与父视图或其他子视图进行关联。
4、FrameLayout(帧布局):FrameLayout
是一种简单的布局,用于在屏幕上叠放多个子视图。默认情况下,子视图会叠放在左上角,可以使用android:layout_gravity
属性来调整子视图的位置。
5、GridLayout(网格布局):GridLayout
将子视图组织成网格状的结构。它可以指定行数和列数,并可以控制子视图在网格中的位置。可以使用android:layout_row
和android:layout_column
属性来指定子视图所在的行和列。
3. Android 中的动画有哪几类,它们的特点和区别是什么
- 视图动画,或者说补间动画。只是视觉上的一个效果,实际view属性没有变化,性能好,但是支持方式少。
- 属性动画,通过变化属性来达到动画的效果,性能略差,支持点击等事件。android 3.0
- 帧动画,通过drawable一帧帧画出来。
- Gif动画,原理同上,canvas画出来。
4. 组件
1. Android四大组件是指Activity、Service、BroadcastReceiver和ContentProvider。
1、Activity(活动):Activity是Android应用程序的用户界面的基本构建块。它负责处理用户与应用程序的交互,并负责显示用户界面。在我的项目中,通常使用Activity来展示各个界面,如欢迎页面、登录页面、主页面等。我会在Activity中处理用户的输入、展示数据、与其他组件进行通信等操作。
2、Service(服务):Service是一种在后台运行的组件,它不与用户交互,用于执行长时间运行的任务或处理一些不需要用户界面的操作。在我的项目中,我会使用Service来处理一些耗时操作,如网络请求、音乐播放等。Service可以与Activity进行通信,通过Intent或绑定方式进行交互。
3、BroadcastReceiver(广播接收器):BroadcastReceiver
用于接收和响应系统或其他应用程序发送的广播消息。它允许应用程序在后台监测并响应各种系统事件或自定义事件。在我的项目中,我会使用BroadcastReceiver
来接收系统广播或自定义广播,如网络状态变化、电量变化等。通过注册广播接收器,并实现相应的逻辑,我可以在特定事件发生时执行相应的操作。
4、ContentProvider(内容提供器):ContentProvider
用于管理应用程序中的共享数据,并提供对外访问数据的接口。通过ContentProvider
,应用程序可以与其他应用程序共享数据,并实现数据的增删改查操作。在我的项目中,我会使用ContentProvider
来管理应用程序中的数据库或文件数据,提供对外的数据访问接口。
在我的项目中,通常会根据业务需求结合使用这些组件。例如,一个典型的流程可能是在Activity
中展示用户界面,并通过Service
进行后台任务处理,通过BroadcastReceiver
接收相关事件的通知并触发相应的操作。同时,通过ContentProvider
来管理数据的访问和共享。每个组件都有其特定的作用和用法,合理使用这些组件可以提高应用程序的灵活性和扩展性。
5. 四大组件详解
1. activity
(1) 一个Activity通常就是一个单独的屏幕 (窗口)
(2) Activity之间通过Intent
、putString(key,value)
进行通信。利用Bundle
进行传值
(3) android应用中每一个Activity都必须要在AndroidManifest.xml
配置文件中声明,否则系统将不识别也不执行该Activity。
Activity向Fragment通信
1、利用Bundle+getArguments()
在Activity中实例化Fragment并使用setArguments
绑定Bundle对象,在Fragment中拿到Bundle实例
2、强转型为Avtivity,调用对应Activity的方法
Activity与Fragment之间通信,同属于一个Activity的Fragment之间的通信。主要的方式有
- 方法一、通过
setArguments()
方法,在 Activity 中实例化Fragment对象,通过Fragment.setArguments(Bundle)
传递信息,在 Fragment 中通过getArguments()
方法获得 Bundle 对象 - 方法二、通过 Fragment 的
onAttach(Context context)
方法,在 Activity 中定义getData()
的公共方法,返回希望传送的数据的类型,在 Fragment 的onAttach(Context context)
方法中通过 ((MainActivity) context) 类型转换,获得 MainActivity 的上下文对象,再通过上面定义的共有方法获得数据。
Fragment 向 Activity 传递数据
- 以定义接口的形式,在fragment中定义接口,然后Activity中实现接口,从而实现数据的传递
- 在 Fragment 中定义一个接口
MessageSend
,并定义方法sendMessage
- 定义
MessageSend
对象,并在 Fragment 的onAttach()
方法中初始化接口(当然也可以在onCreate 或onCreateView
等方法中初始化接口,只要在传送数据之前初始化就可以),在 Fragment 中设置需要传送的数据,在 Activity 中实现回调接口,即可获取数据
Activity正常情况下生命周期
onCreate:在activity被创建时调用
onStart:Activity将被启动,此时Activity已可见,但还在后台(无法与用户交互)
onResume:在获取用户焦点与用户互动时调用
onPause:在activity被其他activity覆盖时或者锁屏时调用
onStop:在activity对用户不可见时调用
onDestroy:在Activity销毁时调用,回收工作和最终资源释放
onReStart:在activity停止状态重新开启时调用
Activity第一次打开:onCreate->onStart->onResume
用户返回桌面或打开新Activity,当前Activity:onPause->onStop,特殊情况当新Activity采用透明模式,当前Activity不会调用onStop
用户退出当前Activity:onPause->onStop->onDestroy
Activity异常情况下生命周期
android横竖屏切换的时候Activity的生命周期
答:当Android设备的屏幕从横屏切换为竖屏,或者从竖屏切换为横屏时,Activity的生命周期会经历以下过程:
1.onPause(): 在屏幕旋转之前,系统会调用当前Activity的onPause()
方法。这表示Activity正在失去焦点,并即将进入停止状态。
2.onSaveInstanceState(): 在屏幕旋转之前,系统会调用当前Activity的onSaveInstanceState()
方法,用于保存Activity的状态信息。你可以在此方法中保存必要的数据,以便在Activity重新创建后进行恢复。
如果你不希望在横竖屏切换时保存数据,可以在 Activity 的清单文件中对应的 标签中添加android:configChanges
属性,并指定要忽略的配置变化。例如,可以添加 orientation 和 screenSize 以忽略横竖屏切换
3.onStop(): 当屏幕旋转完成并且新的布局已经加载完毕后,系统会调用当前Activity的onStop()
方法。此时,Activity已经完全不可见。
4.onDestroy(): 如果屏幕旋转导致当前Activity被销毁并重新创建,则系统会调用当前Activity的onDestroy()
方法。你可以在此方法中释放资源和执行清理操作。
5.onCreate(): 在屏幕旋转导致Activity重新创建时,系统会调用当前Activity的onCreate()
方法。你可以在此方法中重新初始化UI和恢复之前保存的数据。
6.onStart(): 在屏幕旋转之后,系统会调用当前Activity的onStart()
方法。此时,Activity已经可见,但还没有获得焦点。
7.onResume(): 最后,系统会调用当前Activity的onResume()
方法。在此方法中,你可以恢复之前的操作,并开始响应用户的交互。
当屏幕旋转时,Activity会被销毁并重新创建,因此需要适当地处理数据保存和恢复的逻辑,以保证用户体验的连续性。onSaveInstanceState()
方法保存和恢复Activity的状态信息,在onCreate()
和onRestoreInstanceState()
方法中进行处理。
想让某些配置在发生改变的时候不重启Activity,需要为Activity添加android:configChanges属性
后台应用被系统杀死 onDestroy()
具有返回值的启动 onActivityResult -> OnRestart -> OnResume
重复启动 onPause -> onNewIntent -> onResume
退出当前 Activity 时–>onPause()–>onStop()–>onDestroy()
activity前后台切换场景 生命周期
点击 home 键回到桌面–>onPause()–>onStop()
再次回到原 Activity–>onRestart()–>onStart()–>onResume()
一个activity切换到另一个activity怎么传递数据和生命周期
使用Intent传递消息
从ActJumpActivity跳转到ActNextActivity,调用方法的顺序为:上一个页面onPause→下一个页面 onCreate→onStart→onResume→上一个页面onStop。
一个软件切换到另一个软件 怎么传递数据和生命周期
一个软件切换到另一个软件可以通过以下两种方式传递数据1:
使用Intent与BroadCastReceiver沿着在应用之间传递数据
1)应用内的动态广播接收器特定事件调用sendBroadcast(Intent)上的一个活动
不要像上面提到的那样调用Activity。使用某个操作在appTwo中注册广播接收器,并使用appTwo中提到的相同操作从appOne中发送Broadacst Intent。因此,当您触发sendbradcast时(意图)来自appOne,监听该Intent的appTwo将被触发,此时您将调用appTwo的Activity(您的主活动)在appTwo的onReceive方法中检查您的活动是否处于活动状态(如果活动处于活动状态,则从appTwo发送Broadcast(Intent)以刷新appTwo中的消息(如果未调用活动))
当一个安卓软件切换到另一个软件时,两个软件的生命周期会分别经历以下过程
-
第一个软件:
第一次启动:onCreate() -> onStart() -> onResume()。
打开新的软件或回到桌面:onPause() -> onStop()。
再次回到第一个软件:onRestart() -> onStart() -> onResume()。
按back键回退:onPause() -> onStop() -> onDestroy()。 -
第二个软件:
第一次启动:onCreate() -> onStart() -> onResume()。
切换到第一个软件或回到桌面:onPause() -> onStop()。
再次回到第二个软件:onRestart() -> onStart() -> onResume()。
按back键回退:onPause() -> onStop() -> onDestroy()。
当内存不足将导致低优先级的Activity被杀死
优先级分为三级
- 前台Activity-与用户正在交互
- 可见但非前台Activity-比如说弹出一个对话框,导致Activity可见但与用户不可交互
-
后台Activity-已经被暂停的Activity,比如说执行了onStop,优先级最低
当内存不足,会以此优先级以此杀死Activity所在进程,无四大组件运行的进程,很容易被杀死
Activity四大启动模式
Standard:标准模式(系统默认模式),当启动一个Activity,就会创建一个实例,无论实例是否已存在。Standard的Activity默认进入启动它的Activity所属的工作栈中(若ActivityA启动了ActivityB(标准模式),ActivityB就会进入ActivityA的工作栈)
SingleTop:栈顶复用模式,当此Activity位于栈顶时,将不会创建新实例,同时回调它的onNewIntent方法,通过此参数可以取出当前请求的信息。但若此Activity不位于栈顶,将会重新创建
SingleTask:栈内复用模式,单实例模式,若有Activity所需任务栈,则看此Activity是否存在于栈中,当此Activity存在,则调用此Activity位于栈顶,若此Activity未存在于栈中,则创建此Activity实例压入栈中(默认有clearTop效果,将位于它之上的Activity全部出栈)
SingleIntance:单独实例模式,加强版SingleTask模式,此种模式的Activity只能单独存在于一个工作栈,且不许后续创建新Activity
Activity标志位FLAGS
- FLAG_ACTIVITY_NEW_TASK
此标志位作用是为Activity指定SingleTask(栈内复用模式)启动模式,其效果和在XML中指定相同 - FLAG_ACTIVITY_SINGLE_TOP
此标志位作用是为Activity指定SingleTop(栈顶复用模式)启动模式,其效果和在XML中指定相同 - FLAG_ACTIVITY_CLEAR_TOP
具有此标志位的Activity,启动时位于它栈顶的Activity都要出栈,通常与SingleTask启动模式联用。标志位与SingleTask(栈内复用模式)联用时,会把它连同它之上的Activity出栈,并创建新Activity位于栈顶(SingleTask启动模式默认有此标志位效果) - FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有此标志位的Activity不会出现在历史Activity列表中,例如 点击进入顺序:A->B->C,B具有此标志位,当从C返回时直接返回A,而不经过B
2 .service
(1)service用于在后台完成用户指定的操作,无可视化界面展示。需要在应用程序配置文件中声明全部的service,使用<service> </service>
标签Service通常位于后台运行,它一般不需要与用户交互,因此Service组件没有图形用户界面。Service组件需要继承Service基类。Service组件通常用于为其他组件提供后台服务或监控其他组件的运行状态。
service生命周期
onCreate:若Service没被创建过,将调用startService()执行onCreate,若已创建Service,多次调用startService
不会执行onCreate
onStartComand:服务启动时调用,完成一些数据加载工作
onBind:服务被绑定时调用
onUnBind:服务和组件解绑时调用
onDestroy:服务停止时调用
两种启动方式
startService:应用组件通过startService()
启动服务,服务处于started状态,生命周期与组件无关,若无组件调用stopService
或服务自身调用stopSelf
,则会在后台一直调用
bindService:应用组件和服务同生共死,服务处于Bound状态,组件可调用unbindService()
方法与服务解绑,回调顺序为onUnbind->onDestroy
当所有组件与服务解绑后,服务会自动被杀死
保证Service不被杀死
提高Service优先级
系统监听Service状态
开启耗时操作
不能在主线程中进行耗时操作(会导致ANR
2. 线程、同步、异步:Android中的ANR的解决方法),开启子线程进行耗时操作
使用线程和Handler方式
IntentService
对比父类Service,其回调函数onHandlerIntent
中可以进行耗时操作,不必再开线程。当多次调用onHandlerIntent
时多个耗时任务会按顺序依次执行(因为内置Handler关联了任务队列)但仅用Service进行耗时操作就很难管理
ActivityManagerService
从字面意义上看,活动管理服务,管理Android四大组件的启动、运行管理调度等
startService()与bindService() 区别:
-
started service
(启动服务)是由其他组件调用startService
方法启动的,这导致服务的onStartCommand()
方法被调用。当服务是started状态时,其生命周期与启动它的组件无关并且可以在后台无限期运行,即使启动服务的组件已经被销毁。因此,服务需要在完成任务后 调用stopSelf()
方法停止,或者由其他组件调用stopService()
方法停止 - 使用
bindService()
方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。
3 .content provider
以封装的方式提供统一访问接口供不同应用进程访问同一共享数据
三个重要函数:
ContentProdiver:以封装形式提供统一访问接口
ContentResolver:根据不同的URI对ContentProdiver
进行不同操作
ContentObserver:观察ContentProdiver
数据变化并传递变化
- android平台提供了
Content Provider
使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过ContentResolver
类从该内容提供者中获取或存入数据。 - 只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录数据被多个应用 程序使用,且必须存储在一个内容提供者中。它的好处是统一数据访问方式
- ContentProvider实现数据共享。
ContentProvider
用于保存和获取数据,并使其对 所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为android没有提供所有应用共同访问的公共存储区 - 开发人员不会直接使用
ContentProvider
类的对象,大多数是通过ContentResolver
对象实现对ContentProvider
的操作。 -
ContentProvider
使用URI
来唯一标识其数据集,这里的URI以content://作为前缀,表示该数据由ContentProvider
来管理。
4 .broadcast receiver
在应用程序之间传输信息的机制,能够对广播进行过滤并做出响应
(1)你的应用可以使用它对外部事件进行过滤,只对感兴趣的外部事件(如当电话呼入时,或者 数据网络可用时)进行接收并做出响应。广播接收器没有用户界面。然而,它们可以启动一个 activity或 serice来响应它们收到的信息,或者用NotificationManager
来通知用户。通知可以用很多种方式来吸引用户的注意力,例如闪动背灯、震动、播放声音等。一般来说是在状 态栏上放一个持久的图标,用户可以打开它并获取消息。
广播形式:
本地广播:只在应用内部传播,安全但只能用动态注册
有序广播:顺序接收广播并处理,同一时刻只有一个广播能接收同一条数据,优先级越高的广播优先接收
普通广播:intent的广播,传给所有广播接收者,顺序随机
粘性广播:等待对应的广播接收者注册后,则结束滞留状态与之匹配
两种注册方式:
静态注册:AndroidManifest
文件中注册,只要设备开启,广播则打开
动态注册:使用Context.registerRecevie
()注册,当注册的Activity关闭后,广播即失效
如果后台的activity由于某种原因被系统回收了,如何在被系统回收之前保存当前状态?
- Activity状态的常用方法:onSaveInstanceState() 和onRestoreInstanceState() 方法:
在Activity被销毁之前,系统会调用onSaveInstanceState()
方法。我们可以在该方法中保存需要恢复的数据到 Bundle 对象中。
在Activity重新创建后,系统会调用onRestoreInstanceState()
方法,并将之前保存的 Bundle 对象作为参数传递给该方法。我们可以从 Bundle 中恢复之前保存的数据,并进行相应的处理。 -
使用 SharedPreferences:
在 Activity 的onDestroy()
方法中,将需要保存的状态数据存储到 SharedPreferences 中。
在 Activity 的onCreate()
方法中,从SharedPreferences
中读取数据,并进行恢复。 -
使用数据库或文件存储:
在 Activity 的onDestroy()
方法中,将需要保存的状态数据存储到数据库或文件中。
在 Activity 的onCreate()
方法中,从数据库或文件中读取数据,并进行恢复。
需要注意的是,以上方法并不能保证在所有情况下都能完全恢复 Activity 的状态。在某些极端情况下,如系统内存严重不足或用户主动关闭应用等情况下,Activity 可能无法被恢复到之前的状态。因此,除了保存和恢复 Activity 状态外,还应该设计合理的应用程序结构和逻辑,以确保用户体验的连贯性。
LaunchMode 的应用场景
LaunchMode 有四种,分别为 Standard,SingleTop,SingleTask 和 SingleInstance
,每种模式的实现原理一楼都做了较详细说明,下面说一下具体使用场景:
- Standard:
- Standard 模式是
系统默认的启动模式
,一般我们 app中大部分页面都是由该模式的页面构成的,比较常见的场景是:社交应用中,点击查看用户A信息->查看用户A粉丝->在粉丝中挑选查看用户B信息->查看用户A粉丝…这种情况下一般我们需要保留用户操作 Activity 栈的页面所有执行顺序。
- Standard 模式是
- SingleTop:
- SingleTop 模式一般常见于社交应用中的通知栏行为功能,例如:App 用户收到几条好友请求的推送消息,需要用户点击推送通知进入到请求者个人信息页,将信息页设置为 SingleTop 模式就可以增强复用性。
- SingleTask:
- SingleTask 模式一般用作应用的首页,例如浏览器主页,用户可能从多个应用启动浏览器,但主界面仅仅启动一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。
- SingleInstance:
- SingleInstance 模式常应用于独立栈操作的应用,如闹钟的提醒页面,当你在A应用中看视频时,闹钟响了,你点击闹钟提醒通知后进入提醒详情页面,然后点击返回就再次回到A的视频页面,这样就不会过多干扰到用户先前的操作了。
如何获取绑定后的Service的方法
onBind
回调方法将返回给客户端一个Binder接口实例,Binder允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。我们需要Binder对象返回具体的Service对象才能操作,所以说具体的Service对象必须首先实现Binder对象
6. 机制
1.view的事件分发机制
一个点击事件产生后,它的事件传递顺序遵循:Activity->Window->DecorView->ViewGroup->View
View的事件分发机制主要由事件分发->事件拦截->事件处理三步来进行逻辑控制
事件分发:dispatchTouchEvent
用来进行事件的分发,如果事件能够传递给当前View,则该方法一定会被调用。返回结果受当前View的onTouchEvent
和下级的dispatchTouchEvent
的影响,表示是否消耗当前事件。
return:
ture:当前View消耗所有事件
false:停止分发,交由上层控件的onTouchEvent
方法进行消费,如果本层控件是Activity,则事件将被系统消费,处理
事件拦截:onInterceptTouchEvent
需注意的是在Activity,ViewGroup,View中只有ViewGroup有这个方法。故一旦有点击事件传递给View,则View
的onTouchEvent
方法就会被调用。
在dispatchTouch Event
内部使用,用来判断是否拦截事件。如果当前View拦截了某个事件,那么该事件序列的其它方法也由当前View处理,故该方法不会被再次调用,因为已经无须询问它是否要拦截该事件。
return:
ture:对事件拦截,交给本层的onTouchEvent
进行处理
false:不拦截,分发到子View,由子View的dispatchTouchEvent
进行处理super.onInterceptTouchEvent(ev)
:默认不拦截
事件处理:onTouchEvent
在dispatchTouchEvent
中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一事件序列中,当前View无法再接受到剩下的事件,并且事件将重新交给它的父元素处理,即父元素的onTouchEvent
会被调用。
return:
true:表示onTouchEvent
处理后消耗了当前事件
false:不响应事件,不断的传递给上层的onTouchEvent
方法处理,直到某个View的onTouchEvent
返回true,则认为该事件被消费,如果到最顶层View还是返回false,则该事件不消费,将交由Activity的onTouchEvent
处理。super.onTouchEvent(ev)
:默认消耗当前事件,与返回true一致。
-
Activity对点击事件的分发过程
点击事件用MotionEvent
来表示,当一个点击事件发生后,首先会传递给当前的Activity,,由Activity的dispatchTouchEvent
方法来进行事件派发,具体是由Actiivty内部的window来完成的。window会将事件传递给decorView
,decorView
一般为当前界面的底层容器,通过Activity.getWindow.getDecorView()
可以获得,Activity的点击事件由当前activity的附属window进行分发,如果返回ture,则整个事件的循环结束了。如果返回false,则事件没有人处理,当所有View的onTouchEvent
方法都返回false时,那么Activity的onTouchEvent
方法会被调用。 -
window点击事件的分发过程
PhoneWindow
(Window是个抽象类,PhoneWindow
是Window的实现类)将事件直接传递给了DecorView.DecorView
是PhoneWindow
中一个内部类,在PhoneWindow
中有这样一句。getWindow().getDecorView()
返回的是一个DecorView
。我们可以通过一个setContenView
设置的View是DecorView
的子view,由于DecorView
是继承FramLayout
而且是View的父元素,所以点击事件一定会传递给view
,这个view就是顶级view,DecorView
是根view。 -
顶级View对点击事件的分发过程
顶级view一般是ViewGroup
,当点击事件传递到顶级的ViewGroup
时,ViewGroup
会调用自己的dispatchTouchEvent
方法,一层层分发下去,最后传递给view的onTouchEvent
方法。
具体传递顺序:对于一个根ViewGroup
来讲,首先会调用dispatchTouchEvent
,如果此时它的onInterceptTouchEvent
方法返回ture,表示拦截当前的事件,说明本次事件交给当前的ViewGroup
处理,调用它的onTouchEvent
方法。如果返回false,表示不拦截此次事件,继续分发给它的子元素,接着它的子元素的dispatchTouchEvent
接着会被调用,如此反复直到事件的结束。
7. 性能、内存
1.GC是什么,为什么要有GC
答:GC (Garbage Collection
) 是一种自动内存管理机制,它是一种用于自动检测和回收不再使用的内存的机制。在编程语言中,特别是在像Java、C#等高级语言中,GC负责自动管理内存的分配和释放,以减少开发人员手动管理内存的负担。
GC的主要目的是解决内存泄漏和内存碎片化的问题。内存泄漏指的是程序中分配的内存没有被正确释放,导致内存占用不断增加,最终导致系统性能下降甚至崩溃。而内存碎片化是指内存中存在大量无法利用的碎片空间,这些碎片空间虽然总和很大,但无法满足大块内存的分配请求。
GC的工作原理是通过周期性地检测和标记不再使用的对象,并将其回收释放。它会自动追踪对象之间的引用关系,当一个对象不再被其他对象引用时,就认为该对象可以被回收。GC会从根对象(如全局变量、活动线程等)开始遍历整个对象图,标记所有可达的对象,然后清理掉未标记的对象。
GC的存在有以下几个原因:
- 简化内存管理:GC可以自动处理内存的分配和释放,减轻了开发人员的负担。开发者无需手动跟踪和释放对象,不用担心内存泄漏和野指针等问题。
- 避免内存泄漏:GC可以检测不再使用的对象,并及时回收释放内存,避免了内存泄漏的问题。
- 解决内存碎片化:GC可以对内存进行整理和合并,减少内存碎片的产生,提高内存的利用率。
-
提升性能和稳定性:GC可以在程序运行时动态地回收垃圾对象,释放内存资源,减少了内存占用和频繁的内存分配/释放操作,从而提升了程序的性能和稳定性。
总之,GC的存在使得开发者更专注于业务逻辑的实现,减少了手动内存管理的复杂性,提高了代码的可维护性和可靠性。
2、Android性能优化
- 启动优化:
application
中不要做大量耗时操作,如果必须的话建议做异步耗时操作 - 布局优化: 使用合理的控件选择,少嵌套。 (合理使用
include,merge,viewStub
等使用) - apk优化(资源文件优化,代码优化,lint检查,png合理使用shape替代图片,webp等)
- 性能优化,网络优化,电量优化
- 避免轮询,尽量使用推送
- 应用处于后台时,禁用某些数据传输限制访问频率,失败后不要无限重连选用合适的定位服务(GPS定位,网络定位,被动定 位)
- 使用缓存
-
startActivityForResult
替代发送广播内存
- 优化
- 循环尽量不使用局部变量
- 避免在
onDraw
中创建对象,onDraw会被频繁调用,容易造成内存抖动。循环中创建大的对象,也是如此。 - 不用的对象及时释放
- 数据库的
cursor
及时关闭 -
adapter
使用缓存 - 注册广播后,在生命周期结束时反注册
- 及时关闭流操作
- 图片尽量使用软引用,较大的图片可以通过
bitmapFactory
缩放后再使用,并及时recycler
。另外加载巨图时不要 使用setlmageBitmap
或setlmageResourse
或BitmapFactory.decodeResource
,这些方法拿到的都是bitmap
的对象,占用内存较大。可以用BitmapFactory.decodeStream
方法配合BitmapFactory.Options
进行缩放 - 避免static成员变量引用资源耗费过多实例。
- 避免静态内部类的引用
3、什么情况下会导致内存泄漏问题
- 资源对象没关闭造成的内存泄漏(如:
Cursor
、File
等) -
Listview
的Adapter
中没有使用缓存的Convertview -
Bitmap
对象不在使用时调用recycle
()释放内存 - 集合中对象没清理造成的内存泄漏(特别是static 修饰集合)
- 接收器、监听器注册没取消造成的内存泄漏
-
Activity
的context
造成的泄漏,可以使用ApplicationContext
-
Handler
造成的内存泄漏问题 (一般由于 Handler 生命周期比其外部类的生命周期长引起的)
8. 数据库
1.SQLite 数据库升级——新增字段处理
SQLite 的 ALTER TABLE 命令不通过执行一个完整的转储和数据的重载来修改已有的表。您可以使用 ALTER TABLE 语句重命名表,使用 ALTER TABLE 语句还可以在已有的表中添加额外的列。
9. 权限
1、 Android 程序运行时权限与文件系统权限的区别
文件的系统权限是由linux系统规定的,只读,读写等。
运行时权限,是对于某个系统上的app的访问权限,允许,拒绝,询问。该功能可以防止非法的程序访问敏感的信息
10. 权Android 系统 SDK相关
1、Android系统的架构组成
android系统分为四部分,从高到低分别是文章来源:https://www.toymoban.com/news/detail-617821.html
- Android应用层
Android会同一系列核心应用程序包一起发布,该应用程序 包包括email客户端,SMS短消息程序,日历,地图,浏览 器,联系人管理程序等。所有的应用程序都是使用JAVA语 言编写的。 - Android应用框架层
开发人员也可以完全访问核心应用程序所使用的API框架该应用程序的架构设计简化了组件的重用;任何一个应用程序都可以发布它的功能块并且任何其它的应用程序都可以使用其所发布的功能块(不过得遵循框架的安全性限制)。同样,该应用程序重用机制也使用户可以方便的替换程序组件。 - Android系统运行层
Android 包含一些C/C++库,这些库能被Android系统中不同的组件使用。它们通过 Android 应用程序框架为开发者提供服务。 - Linux内核层
Android 的核心系统服务依赖于 Linux 2.6 内核,如安全性,内存管理,进程管理,网络协议栈和驱动模型Linux 内核也同时作为硬件和软件栈之间的抽象层
3. Serializable和Parcelable的区别?
Android中序列化有两种方式: Serializable
以及Parcelable
。其中Serializable是Java自带的
,而Parcelable是安卓专有的
。Seralizable相对Parcelable而言,好处就是非常简单,只 需对需要序列化的类class执行就可以,不需要手动去处理 序列化和反序列化的过程,所以常常用于网络请求数据处理Activity之间传递值的使用。Parcelable是android特有的序列化API,它的出现是为了解决Serializable在序列化的过程中消耗资源严重的问题,但是因为本身使用需要手动处理序列化和反序列化过程,会与具体的代码绑定,使用较为繁琐,一般只获取内存数据的时候使用。文章来源地址https://www.toymoban.com/news/detail-617821.html
到了这里,关于安卓面试问题记录的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!