2020-Android面试

1。Android全景图
2020-Android面试_第1张图片
底层(LInuxKernel):驱动程序
程序包(Libraries):SQLite;
Application framework: 提供手机开发API(Java开发)
Applications:开发的软件
(从全景图上面向下面的层调用)
2020-Android面试_第2张图片
Android 四大组件

1) Activity: Android软件的界面,包括用户界面的一些组件,用于和用户进行交互。
2) Service:不可见,为整个application提供支持,是实现程序后台运行的解决方案,等程序进程被杀掉时,依赖于该进程的服务也会停止。
3)BroadcastReceiver:用于APP接收(发送)广播消息
4)ContentProvider:为application之间共享数据(来源可以是数据库)

Android 生命周期
2020-Android面试_第3张图片
2020-Android面试_第4张图片

onCreate() :活动第一次创建时被调用,完成活动初始化操作
onStart() : 活动由不可见变为可见时调用
onResume() 当Activity准备好和用户进行交互时调用。该活动处在活动栈栈顶
onPause() 系统准备去启动或恢复另一个活动时调用
onStop() 活动完全不可用时调用
onRestart() 活动被销毁前调用
onDestroy(): 整个Activity被销毁:调用finish();系统资源不够用时
onRestart() 活动由停止变为运行时调用

Activity类中的finish()、onDestory()和System.exit(0) 三者的区别:

Activity.finish() 是Activity的类,仅仅针对Activity,当调用finish()时,只是将活动推向后台,并没有立即释放内存系统只是将最上面的Activity移出了栈,“back”按键的时候,也不会找到这个Activity。

Activity.onDestory() 系统销毁了这个Activity的实例在内存中占据的空间。

System.exit(0) : 杀死进程,活动所占的资源会被释放

2020-Android面试_第5张图片

当在一个ACtivity中点击按钮后弹出提示对话框,则只需在AndroidManifest.xml 文件中对应的activity里添加android:theme= “@android:style/Theme.Dialog ”即可

活动因内存不足被收回导致页面数据丢失
解决方法
用onSaveInstanceState() 该方法可保证活动被收回前调用,该方法携带一个Bundle类型的参数,提供了一系列保存数据的方法,eg putString(键,值)可以保存字符串,获取的时候用getString(键)的方法获得。
Instant也可以结合Bundle一起传递数据。

活动的四种启动模式

启动模式通过 ==AndroidManifest.xml == 文件中指定的 android:launchMode 属性设定

standard 默认启动方式,系统不管活动是否在返回栈中,每次启动都会创建该活动的新的实例

singleTop 启动活动时若发现返回栈栈顶是该活动,直接使用,不用创建新的活动实例

singleTask 启动活动前先查看返回栈中是否存在该活动,若有把该活动之上的所有活动出栈,使用该活动实例,若没有,则重新创建该活动实例

singleInstance 启动新的返回栈管理该活动

Intent 在活动之间进行穿梭
Intent 是Android程序各组件间交互的一种方式

功能

1)指明当前组件执行的动作
2)不同组件间进行数据传递

分类

显示Intent

Intent intent = new Intent(FirstActivity.this, SecondActivity.class); //第一个参数是活动上下文,第二个参数是要启动的目标活动
startActivity(intent);

隐式Intent

不指定启动哪个活动,而是通过action 和category等信息,交由系统分析这个Intent,启动合适的活动

在标签下配置 指定action 和category等信息

Handler

作用消息传递机制,跨线程通信

当子线程中进行耗时操作后需要更新UI时,通过Handler将有关UI的操作切换到主线程中执行。将子线程的数据传递给主线程,或任意两个线程间信息传递。

补充: Android开发中,耗时的一些操作放在子线程(work thread)中去执行,UI的更新只能通过Main thread来进行

四要素:

Message(消息) :需要被传递的消息,其中包含了 消息ID,消息处理对象以及处理的数据 等,由MessageQueue统一列队,最终由Handler处理。

MessageQueue(消息队列) :用来存放Handler发送过来的消息,内部通过单链表的数据结构来维护消息列表,等待Looper的抽取。

Handler(处理者) :负责Message的发送及处理。通过 Handler.sendMessage() 向消息池发送各种消息事件;通过 Handler.handleMessage() 处理相应的消息事件。
Looper(消息泵) :通过Looper.loop()不断地从MessageQueue中抽取Message,按分发机制 将消息分发给目标处理者。

具体流程如图

2020-Android面试_第6张图片

  • Handler.sendMessage()发送消息时,会通过MessageQueue.enqueueMessage()MessageQueue中添加一条消息;
  • 通过Looper.loop()开启循环后,不断轮询调用MessageQueue.next()
  • 调用目标Handler.dispatchMessage()去传递消息,目标Handler收到消息后调用Handler.handlerMessage()处理消息。

ANR

概念
ANR(Application Not Responding,应用无响应):当操作在一段时间内系统无法处理时,会在系统层面会弹出ANR对话框

产生原因

产生ANR可能是因为

  • 5s内无响应用户输入事件
  • 10s内未结束BroadcastReceiver
  • 20s内未结束Service

如何避免

不要在主线程做耗时操作,而是通过开子线程,方法比如继承Thread或实现Runnable接口、使用AsyncTask、IntentService、HandlerThread等

如何快速定位出现ANR问题位置

使用命令导出ANR日志,并分析关键信息
ref:https://www.jianshu.com/p/3959a601cea6

SQLite

是轻量级关系型数据库,满足ACID

了解SQLite中的事务操作吗?是如何做的
SQLite在做CRDU操作时都默认开启了事务,然后把SQL语句翻译成对应的SQLiteStatement并调用其相应的CRUD方法,此时整个操作还是在rollback journal这个临时文件上进行,只有操作顺利完成才会更新db数据库,否则会被回滚。

CRUD:增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)

Q:使用SQLite做批量操作有什么好的方法吗?
使用SQLiteDatabase的beginTransaction方法开启一个事务,将批量操作SQL语句转化为SQLiteStatement并进行批量操作,结束后endTransaction()

Q:如何删除SQLite中表的个别字段?
SQLite数据库只允许增加字段而不允许修改和删除表字段,只能创建新表保留原有字段,删除原表

Q:使用SQLite时会有哪些优化操作?
使用事务做批量操作

及时关闭Cursor,避免内存泄露

耗时操作异步化:数据库的操作属于本地IO耗时操作,建议放入异步线程中处理

ContentValues的容量调整:ContentValues内部采用HashMap来存储Key-Value数据,ContentValues初始容量为8,扩容时翻倍。因此建议对ContentValues填入的内容进行估量,设置合理的初始化容量,减少不必要的内部扩容操作

JNI和NDK理解

阐述你对JNI的理解

JNI的全称是Java Native Interface(Java本地接口)是一层接口,是用来沟通Java代码和C/C++代码的,是Java和C/C++之间的桥梁。通过JNI,Java可以完成对外部C/C++库函数的调用,相对的,外部C/C++也能调用Java中封装好的类和方法。
 Java的优点是跨平台,和操作系统之间的调用由JVM完成,但是一些和操作系统相关的操作就无法完成,JNI的出现刚好弥补了这个缺陷,也完善了Java语言,将java扩展得更为强大。

JNI的应用场景:

实际中的驱动都是C/C++开发的,通过JNI,Java可以调用C/c++实现的驱动,从而扩展Java虚拟机的能力。另外,在高效率的数学运算、游戏的实时渲染、音视频的编码和解码等方面,一般都是用C开发的。

NDK的理解

NDK(Native Development Kit)是Android所提供的一个工具集合,通过NDK可以在Android中更加方便地通过JNI来调用本地代码(C/C++)。NDK提供了交叉编译器,开发时只需要修改mk文件就能生成特定的CPU平台的动态库。

JNI开发的一般步骤

1)创建一个Android工程,在Java代码中中声明一个native方法
加载库

public class TestHelloActivity extends Activity{
   public native String sayHello();
   static{System.loadLibrary(“hellojni”)}//参数在Android.mk中声明
   ....
}

2)使用javah命令生成带有native方法的头文件。

javah com.xxx.TestHelloActivity

注意事项:
JDK1.7 需要在工程的src目录下执行上面的命令,JDK1.6 需要在工程的bin/classes目录下执行以上命令。

3)在该Android工程中创建JNI目录,并在jni目录中创建一个Hello.c文件,根据头文件实现C代码。写C代码时,结构体JNIEnv*对象对个别object对象很重要,在实现的C代码的方法中必须传入这两个参数。具体代码如下:

jstring Java_com_xxx_TestHelloActivity_sayHello(JNIEnv* env,jobject obj){
	char* text = "hello from c!";
	return (**env).NewsStringUTF(env,text);
}

(4)在JNI的目录下创建一个Android.mk文件,并根据需要编写里面的内容,例如:

#LOCAL_PATH是所编译的C文件的根目录,右边的赋值代表根目录即为Android.mk所在的目录
LOCAL_PATH:=$(call my-dir)
#在使用NDK编译工具时对编译环境中所用到的全局变量清零
include $(CLEAR)VARS)
#最后声称库时的名字的一部分
LOCAL_MODULE:=hello
#要被编译的C文件的文件名
LOCAL_SRC_FILES:=Hello.c
#NDK编译时会生成一些共享库
include $(BUILD_SHARED_LIBRARY)

(5)在工程的根目录下执行ndk_build命令,编译.so文件
(6)在调用Native()方法前,加载.so的库文件,例如

static  {
	System.loadLibrary("Hello");
}
//(文件名个Android.mk文件中的LOCAL_MODULE属性指定的值相同)

JNIEnv 和 JavaVM 理解

==JavaVM == 是虚拟机在 JNI 层的代表。

一个进程只有一个 JavaVM。(重要!)

所有的线程共用一个 JavaVM。(重要!)

JNIEnv 表示 Java 调用 native 语言的环境,封装了几乎全部 JNI 方法的指针。

JNIEnv 只在创建它的线程生效,不能跨线程传递,不同线程的 JNIEnv 彼此独立。(重要!)

注意:
在 native 环境下创建的线程,要想和 java 通信,即需要获取一个 JNIEnv 对象。我们通过 AttachCurrentThreadDetachCurrentThread 方法将 native 的线程与 JavaVM 关联和解除关联。

JNI 中全局引用和局部引用的区别和使用

全局引用
通过 NewGlobalRefDeleteGlobalRef 方法创建和释放一个全局引用。

全局引用能在多个线程中被使用,且不会被 GC 回收,只能手动释放

局部引用
通过 NewLocalRefDeleteLocalRef 方法创建和释放一个局部引用。

局部引用只在创建它的 native 方法中有效,包括其调用的其它函数中有效。因此我们不能寄望于将一个局部引用直接保存在全局变量中下次使用(请使用全局引用实现该需求)。

我们可以不用删除局部引用,它们会在 native 方法返回时全部自动释放,但是建议对于不再使用的局部引用手动释放,避免内存过度使用。

弱全局引用
通过 NewWeakGlobalRefDeleteWeakGlobalRef 创建和释放一个弱全局引用。

弱全局引用类似于全局引用,唯一的区别是它不会阻止被 GC 回收

JNI 线程间数据怎么互相访问

线程本来就是共享内存区域的,因此我们需要使用 全局引用

JNI 线程间数据怎么互相访问

静态注册:
通过 JNIEXPORTJNICALL 两个宏定义声明,Java + 包名 + 类名 + 方法名 形式的函数名。不好的地方就是方法名太长了。

动态注册:
通常在 JNI_OnLoad 方法中通过 RegisterNatives 方法注册,可以不再遵从固定的命名写法

定位 NDK 中的问题和错误

通过 log 来定位和分析问题。

如果是上线状态(即关闭了基本的 log),我们可以借助 NDK 提供的 addr2line 工具和 objdump 工具来定位错误

内存泄漏

概念

内存泄漏(Memory Leak) 是指程序在申请内存后,无法释放已申请的内存空间。简单地说,发生内存泄漏是由于长周期对象持有对短周期对象的引用,使得短周期对象不能被及时回收。

内存泄漏例子及解决办法

  1. 单例模式导致的内存泄漏:单例传入参数this来自Activity,使得持有对Activity的引用。
    解决办法:传参context.getApplicationContext()

  2. Handler导致的内存泄漏:Message持有对Handler的引用,而非静态内部类的Handler又隐式持有对外部类Activity的引用,使得引用关系会保持至消息得到处理,从而阻止了Activity的回收。
    解决办法:使用静态内部类+WeakReference弱引用;当外部类结束生命周期时清空消息队列。

  3. 线程导致的内存泄漏:AsyncTask/Runnable以匿名内部类的方式存在,会隐式持有对所在Activity的引用。
    解决办法:将AsyncTask和Runnable设为静态内部类或独立出来;在线程内部采用弱引用保存Context引用

内存泄漏和内存溢出的区别

  • ==内存泄漏(Memory Leak)==是指程序在申请内存后,无法释放已申请的内存空间。是造成应用程序OOM的主要原因之一。
  • ==内存溢出(out of memory)==是指程序在申请内存时,没有足够的内存空间供其使用。

内存泄漏是导致内存溢出的主要原因;直接加载大图片也易造成内存溢出

Android项目目录

minSDK- 最小的API
Eclipse中的文件结构:
1)src :自己写的源文件;
2)gen:SDK帮助生成的R.java文件;
3)android.jar 文件:所引用的类文件都来源于此;
4)assets:可以放所有文件;但不会在R.java 生成唯一ID.
5)res:所有的文件都会在R.java中生成唯一ID。
每张图片都有三个不同的像素版本,layout定义界面控件的属性。values:也会在R.java中生成唯一ID。
6)AndroidManifest.xml:是整个程序的应用配置文件,

你可能感兴趣的:(Android,Android,开发)