Android常见问题及答案收集三

android常见问题及答案收集三。
不断收集中


安卓apk构建的流程

  • aapt 为res 目录下资源生成R.java 文件,同时为AndroidMainfest.xml 生成Mainfeset.java文件
  • aidl 将项目中自定义的aidl 生成相应的Java文件
  • javac 将项目中所有java 代码编译成class 文件,包括 业务逻辑代码, aapt 生成 java文件,aidl 生成java 文件
  • proguard , 混淆同时生成mapping.txt ,这步可选
  • 将所有class 文件(包括第三方class 文件) 转换为dex 文件
  • aapt 打包, 将res资源,assets资源,打包成一个.ap_的文件
  • apkbuilder ,将所有dex 文件,.ap_文件, AndroidMainfest.xml打包为.apk 文件,这是一个未签名的apk包
  • jarsigner 对apk 进行签名
  • ziplign 对apk 进行对齐操作,以便运行时节约内存。

自定义 Handler 时如何有效地避免内存泄漏问题

  • 自定义的静态handler
  • 使用弱引用持有外部activity等
  • 还有一个主意的就是当你activity被销毁的时候如果还有消息没有发出去就remove【removecallbacksandmessages清除Message】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class CustomActivity extends Activity{

private CustomHandler mCustomHandler;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCustomHandler = new CustomHandler(this);
}

static class CustomHandler extends Handler {

private final WeakReference<Activity> reference;

CustomHandler(Activity activity) {
reference = new WeakReference<>(activity);
}

@Override
public void handleMessage(Message msg) {
Activity activity = reference.get();
if (activity != null && !activity.isFinishing()) {

}
}
}
}

@Override
protected void onDestroy() {
super.onDestroy();
if(mCustomHandler!=null){
mCustomHandler.removeCallbacksAndMessages(null);
mCustomHandler = null;
}
}
}

什么情况下会导致内存泄漏问题

  • 资源对象没关闭造成的内存泄漏(如: Cursor、File等)
  • Bitmap 对象不在使用时调用recycle()释放内存【可以不调用recycle,但是要及时把对bitmap的引用置为null】
  • 集合中对象没清理造成的内存泄漏(特别是 static 修饰的集合)
  • 接收器、监听器注册没取消造成的内存泄漏
  • Activity 的 Context 造成的泄漏,可以使用 ApplicationContext
  • Handler 造成的内存泄漏问题(一般由于 Handler 生命周期比其外部类的生命周期长引起的)非静态内部类
  • 异步操作 还有AsyncTask 等
  • 静态类持有非静态类 比如工具类莫名持有context

如何避免内存泄漏

  • 资源对象用完一定要关闭,最好加finally
  • 静态集合对象用完要清理
  • 接收器、监听器使用时候注册和取消成对出现
  • context使用注意生命周期,如果是静态类引用直接用ApplicationContext
  • 使用静态内部类
  • 结合业务场景,设置软引用,弱引用,确保对象可以在合适的时机回收
  • 使用LeakCannery自动化内存泄漏分析,再针对分析

LaunchMode 的应用场景

  • Standard:
    Standard 模式是系统默认的启动模式,一般我们 app 中大部分页面都是由该模式的页面构成的,比较常见的场景是:社交应用中,点击查看用户A信息->查看用户A粉丝->在粉丝中挑选查看用户B信息->查看用户A粉丝… 这种情况下一般我们需要保留用户操作 Activity 栈的页面所有执行顺序。

  • SingleTop:
    SingleTop 模式一般常见于社交应用中的通知栏行为功能,例如:App 用户收到几条好友请求的推送消息,需要用户点击推送通知进入到请求者个人信息页,将信息页设置为 SingleTop 模式就可以增强复用性。

  • SingleTask:
    SingleTask 模式一般用作应用的首页,例如浏览器主页,用户可能从多个应用启动浏览器,但主界面仅仅启动一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。

  • SingleInstance:
    SingleInstance 模式常应用于独立栈操作的应用,如闹钟的提醒页面,当你在A应用中看视频时,闹钟响了,你点击闹钟提醒通知后进入提醒详情页面,然后点击返回就再次回到A的视频页面,这样就不会过多干扰到用户先前的操作了。

BroadcastReceiver 与 LocalBroadcastReceiver 有什么区别

  • BroadcastReceiver 使用

    • 制作intent(可以携带参数)
    • 使用sendBroadcast()传入intent;
    • 制作广播接收器类继承BroadcastReceiver重写onReceive方法(或者可以匿名内部类啥的)
    • 在java中(动态注册)或者直接在Manifest中注册广播接收器(静态注册)使用registerReceiver()传入接收器和intentFilter
    • 取消注册可以在OnDestroy()函数中,unregisterReceiver()传入接收器
  • LocalBroadcastReceiver 使用

    • LocalBroadcastReceiver不能静态注册,只能采用动态注册的方式。在发送和注册的时候采用,LocalBroadcastManager的sendBroadcast方法和registerReceiver方法
  • 区别
    • BroadcastReceiver
      • 有序广播
        • 调用sendOrderedBroadcast()发送
        • 优先级相同的广播,动态注册的广播优先处理
        • 广播接收者还能对广播进行截断和修改
      • 普通广播
        • 调用sendBroadcast()发送
      • BroadcastReceiver不推荐使用静态注册了,8.0之后限制了绝大部分广播只能使用动态注册
    • LocalBroadcastReceiver
      • 效率更高。
      • 发送的广播不会离开我们的应用,不会泄露关键数据。
      • 其他程序无法将广播发送到我们程序内部,不会有安全漏洞。

SharedPreferences线程安全么?

context.getSharedPreferences()开始追踪的话,可以去到ContextImpl的getSharedPreferences(),最终发现SharedPreferencesImpl这个SharedPreferences的实现类,在代码中可以看到读写操作时都有大量的synchronized,因此它是线程安全的。但是,进程不安全。【 commit 是同步写入有返回值,apply是异步写入】

HashMap 实际上是一个“链表散列”的数据结构,即数组和链表的结合体。它是基于哈希表的 Map 接口的非同步实现。

  • 数组:存储区间连续,占用内存严重,寻址容易,插入删除困难;
  • 链表:存储区间离散,占用内存比较宽松,寻址困难,插入删除容易;
  • Hashmap 综合应用了这两种数据结构,实现了寻址容易,插入删除也容易。

Android 中 UI 的刷新机制

  • 界面刷新的本质流程

    • 通过ViewRootImpl的scheduleTraversals()进行界面的三大流程。
    • 调用到scheduleTraversals()时不会立即执行,而是将该操作保存到待执行队列中。并给底层的刷新信号注册监听。
    • 当VSYNC信号到来时,会从待执行队列中取出对应的scheduleTraversals()操作,并将其加入到主线程的消息队列中。
    • 主线程从消息队列中取出并执行三大流程: onMeasure()-onLayout()-onDraw()
  • 同步屏障的作用

    • 同步屏障用于阻塞住所有的同步消息(底层VSYNC的回调onVsync方法提交的消息是异步消息)
    • 用于保证界面刷新功能的performTraversals()的优先执行。
  • 同步屏障的原理?

    • 主线程的Looper会一直循环调用MessageQueue的next方法并且取出队列头部的Message执行,遇到同步屏障(一种特殊消息)后会去寻找异步消息执行。如果没有找到异步消息就会一直阻塞下去,除非将同步屏障取出,否则永远不会执行同步消息。
    • 界面刷新操作是异步消息,具有最高优先级
    • 我们发送的消息是同步消息,再多耗时操作也不会影响UI的刷新操作

Android中内存优化的方式

内存泄漏优化

  • 单例模式引发的内存泄漏:
    原因:单例模式里的静态实例持有对象的引用,导致对象无法被回收,常见为持有Activity的引用
    优化:改为持有Application的引用,或者不持有使用的时候传递。
  • 集合操作不当引发的内存泄漏:
    原因:集合只增不减
    优化:有对应的删除或卸载操作
  • 线程的操作不当引发的内存泄漏:
    原因:线程持有对象的引用在后台执行,与对象的生命周期不一致
    优化:静态实例+弱引用(WeakReference)方式,使其生命周期一致
  • 匿名内部类/非静态内部类操作不当引发的内存泄漏:
    原因:内部类持有对象引用,导致无法释放,比如各种回调
    优化:保持生命周期一致,改为静态实例+对象的弱引用方式(WeakReference)
  • 常用的资源未关闭回收引发的内存泄漏:
    原因:BroadcastReceiver,File,Cursor,IO流,Bitmap等资源使用未关闭
    优化:使用后有对应的关闭和卸载机制
  • Handler使用不当造成的内存泄漏:
    原因:Handler持有Activity的引用,其发送的Message中持有Handler的引用,当队列处理Message的时间过长会导致Handler无法被回收
    优化:静态实例+弱引用(WeakReference)方式
  • 内存溢出:
    原因:

1.内存泄漏长时间的积累
2.业务操作使用超大内存
优化:
1.调整图像大小后再放入内存、及时回收
2.不要过多的创建静态变量

开发中内存优化细节

  • 循环中尽可能不要创建较大的对象,列入bitmap。这会引起内存抖动。
  • 自定义View避免在onDraw中创建对象,因为自定义Viewon的onDraw方法会被频繁调用。创建对象,甚至较大的对象,会导致内存增加,甚至内存抖动。
  • cursor和流的及时关闭,特别是异常处理。一定要写finally里关闭流。
  • 避免静态内部类的引用。比如A类中有静态变量B。这是只要B被应用,就会导致A也不能回收。
  • 图片尽量使用软引用。较大的图片可以通过bitmapFactory缩放后再使用。并及时recycler.另外加载较大的图片尽可能不要使用setImageResourse,BitmapFactory.decordeResource和setImageBitmap方法。这些方法返回的是Bitmap对象。占用内存较大。可以使用BitmapFactory.decodeStream配合BitampFactory.Options对图片进行缩放,然后显示。

Serializable和Parcelable的区别

都是安卓常用到的序列化对象,Parcelable要手动写构造函数和writeToParcel,不过现在as都是自动生成的,Serializable是声明一下接口就行了。Parcelable比Serializable性能强,Serializable在使用是会产生大量临时变量,增加GC回收频率。但是Serializable的数据是以IO流在磁盘,而Parcelable是写在内存,所以Parcelable无法将数据更好的持久化。

点击图标开始app的启动流程

  • 点击app图标,Launcher进程使用Binder IPC向system__server进程发起startActivity请求;
  • system__server进程收到1中的请求后,向zygote进程发送创建新进程的请求;
  • zygote进程fork出新的App进程
  • App进程通过Binder IPC向system__server进程发起attachApplication请求;
  • system__server进程收到4中的请求后,通过Binder IPC向App进程发送scheduleLauncherActivity请求;
  • App进程的ApplicationThread线程收到5的请求后,通过handler向主线程发送LAUNCHER_ACTIVITY消息;
  • 主线程收到6中发送来的Message后,反射创建目标Activity,回调oncreate等方法,开始执行生命周期方法,我们就可以看到应用页面了。

面向对象的六大基本原则

  • 单一职责原则(Single Responsibility Principle):
    对于一个类来说,它里面包含的应该是相关性很高的函数或者变量。比如,两个不相关的业务功能不应该放在一个类中处理,我们应该尽可能的针对于具体的业务功能对类进行拆分,以达到“每个类只负责自己需要关心的内容,其他的与我无关”的目的。

  • 开闭原则(Open Close Principle):
    开闭原则是我们程序设计过程中最基本的原则之一。在我们的程序里,我们所熟知的对象,对于扩展应该是开放的,而对于修改应该是封闭的。什么意思呢?其实很好理解。我们平常在开发的时候可能会经常碰到一个问题:今天写了一个类,里面封装了很多功能方法,但是过了没多久,业务功能改动了,我们又不得不修改这个类里面已存在的代码,以满足当前业务需求。然而,修改已存在的代码会带来很大问题,倘若这个类在很多其他类中用到,而且耦合度较高,那么即使我们对该类内部代码做很小的改动,可能都会导致与之相关(引用到该类)的部分的改动,如果我们不注意这个问题,可能就会一直陷入无止境修改和烦躁当中,这就违背了开闭原则。推荐的做法是:为了防止与之相关类(引用到该类)的改动,我们对于该类的修改应该是封闭的,我们可以提供对于该类功能的扩展途径。那么该如何扩展呢?可以通过接口或者抽象类来实现,我们只需要暴露公共的方法(接口方法),然后由具体业务决定的类来实现这方法,并在里面处理具体的功能代码,至于对外具体怎么用,用户无需关心。其实,开闭原则旨在指导用户,当我们业务功能需要变化时,应该尽量通过扩展的方式来实现,而不是通过修改已有代码来达到目的。只有这样,我们才能避免代码的冗余和腐化,才能使系统更加的稳定和灵活。

  • 里氏替换原则(Liskov Substitution Principle):
    对于一个系统来说,所有引用基类的地方必须同样能够透明地替换为其子类的对象。

  • 依赖倒置原则(Dependence Inversion Principle):
    在Java中可以这样理解:模块之间应该通过接口或者抽象来产生依赖关系,而实现类之间不应该发生直接的依赖关系。这也就是我们常说的面向接口/抽象编程。

  • 接口隔离原则(Interface Segregation Principle):
    接口隔离原则提倡类之间的依赖关系应该建立在最小接口上,提倡将复杂、臃肿的接口拆分为更加具体和细小的接口,以达到解耦的目的。这里的”最小接口”指的也是抽象的概念,将具体实现细节隔离,降低类的耦合性。

  • 迪米特原则(Law of Demeter):
    一个对象应该尽可能的对其他对象有最少的了解。即类与类之间应该尽可能的减小耦合度,否则一个类变化,它对另一个类的影响越大。

Android的事件分发机制

1.触发过程:Activity->Window->DocerView->ViewGroup->View,View不触发再返回由父级处理依次向上推。
2.在每个阶段都要经过三个方法 dispatchTouchEvent(分发)、onInterceptTouchEvent(拦截)、onTouch(处理)

大体流程:
Activity中走了Window 的 dispatch,Window 的 dispatch 方法直接走了 DocerView 的 dispatch 方法,DocerView 又直接分发给了 ViewGroup,ViewGroup 中走的是 onInterce 判断是否拦截,拦截的话会走 onTouch 来处理,不拦截则继续下发给 View。到 View 这里已经是最底层了,View 若继续不处理,那就调用上层的 onTouch 处理,上层不处理继续往上推。

ArrayList和LinkedList的区别

  • ArrayList 为 数组结构,具有数组的基本特性, 查询快,增删除非是最后一个 否则需要更改其余数据的角标
  • LinkedList为双链表 具有链表的基本特性 , 但访问元素需要把指针移动到相应位置慢

startService和bindService的区别,生命周期以及使用场景

  • 生命周期上的区别

执行startService时,Service会经历onCreate->onStartCommand。当执行stopService时,直接调用onDestroy方法。调用者如果没有stopService,Service会一直在后台运行,下次调用者再起来仍然可以stopService。

执行bindService时,Service会经历onCreate->onBind。这个时候调用者和Service绑定在一起。调用者调用unbindService方法或者调用者Context不存在了(如Activity被finish了),Service就会调用onUnbind->onDestroy。这里所谓的绑定在一起就是说两者共存亡了。

多次调用startService,该Service只能被创建一次,即该Service的onCreate方法只会被调用一次。但是每次调用startService,onStartCommand方法都会被调用。Service的onStart方法在API 5时被废弃,替代它的是onStartCommand方法。

第一次执行bindService时,onCreate和onBind方法会被调用,但是多次执行bindService时,onCreate和onBind方法并不会被多次调用,即并不会多次创建服务和绑定服务。

  • 调用者如何获取绑定后的Service的方法

onBind回调方法将返回给客户端一个IBinder接口实例,IBinder允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。我们需要IBinder对象返回具体的Service对象才能操作,所以说具体的Service对象必须首先实现Binder对象。

  • 既使用startService又使用bindService的情况

如果一个Service又被启动又被绑定,则该Service会一直在后台运行。首先不管如何调用,onCreate始终只会调用一次。对应startService调用多少次,Service的onStart方法便会调用多少次。Service的终止,需要unbindService和stopService同时调用才行。不管startService与bindService的调用顺序,如果先调用unbindService,此时服务不会自动终止,再调用stopService之后,服务才会终止;如果先调用stopService,此时服务也不会终止,而再调用unbindService或者之前调用bindService的Context不存在了(如Activity被finish的时候)之后,服务才会自动停止。

那么,什么情况下既使用startService,又使用bindService呢?

如果你只是想要启动一个后台服务长期进行某项任务,那么使用startService便可以了。如果你还想要与正在运行的Service取得联系,那么有两种方法:一种是使用broadcast,另一种是使用bindService。前者的缺点是如果交流较为频繁,容易造成性能上的问题,而后者则没有这些问题。因此,这种情况就需要startService和bindService一起使用了。

另外,如果你的服务只是公开一个远程接口,供连接上的客户端(Android的Service是C/S架构)远程调用执行方法,这个时候你可以不让服务一开始就运行,而只是bindService,这样在第一次bindService的时候才会创建服务的实例运行它,这会节约很多系统资源,特别是如果你的服务是远程服务,那么效果会越明显(当然在Servcie创建的是偶会花去一定时间,这点需要注意)。

  • 本地服务与远程服务

本地服务依附在主进程上,在一定程度上节约了资源。本地服务因为是在同一进程,因此不需要IPC,也不需要AIDL。相应bindService会方便很多。缺点是主进程被kill后,服务变会终止。

远程服务是独立的进程,对应进程名格式为所在包名加上你指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被kill的是偶,该服务依然在运行。缺点是该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。

对于startService来说,不管是本地服务还是远程服务,我们需要做的工作都一样简单。

JVM、Dalvik、ART三者的原理和区别

JVM java虚拟机、Dalvik 早期的Android虚拟机、ART 现行的Android虚拟机

  • JVM:是Java Virtual Machine的缩写,其并不是指某个特定的虚拟机实现,而指任何能够运行Java字节码(class文件)的虚拟机实现,比如oracle的Hotspot VM

  • Dalvik:是Google写的一个用于Android的虚拟机,但其严格来说并不算JVM(没有遵守Java虚拟机规范,比如其字节码格式是dex而非class)
    该虚拟机在5.0时被ART替代

  • ART:是Android Runtime的缩写,严格来说并不是一个虚拟机,在4.4~6.0时采用安装时全部编译为机器码的方式实现,7.0时默认不全部编译,采用解释执行+JIT+空闲时AOT以改善安装耗时
    ART在安卓4.4时加入,5.0取代dalvik作为唯一实现直到现在。

如何保证线程安全

  • 线程安全的本质?
    在多个线程访问共同资源时,在某一个线程对资源进行写操作中途(写入已经开始,还没结束),其他线程对这个写了一般资源进行了读操作,或者基于这个写了一半操作进行写操作,导致数据问题
    所以:保证线程安全有以下操作

  • synchronized
    保证方法内部或代码块内部资源互斥访问,同一时间,由同一Monitor监视代码,最终只能有一个线程在访问

  • volatile
    保证加了 volatile 关键字的字段的操作具有同步性,以及对 long 和 double的操作原子性(long double 原子性这个简单说一下就行).因此 volatile 可以看做简单版本的 synchronized,但是volatile只能保证基本数据类型有效果,无法修改类似 User.name 或 ++ 原子性问题

  • java.util.concurrent.atomic 包:

下面有 AtomicInteger AtomicBoolean 等类,作用和 volatile 基本一致,可以看做通用版本的 volatile

1
2
3
AtomicInteger atomicInteger = new AtomicInteger(0);
...
atomicInteger.getAndIncrement();
  • Lock / ReentrantReadWriteLock
    加锁机制,但是比 synchronized 麻烦,不推荐直接使用,最好用更复杂的锁 ReadWriteLock
    1
    2
    3
    4
    5
    6
    7
    Lock lock = new ReentrantLock();
    ...
    lock.lock(); try {
    x++;
    } finally {
    lock.unlock();
    }

TCP/IP三次握手,四次挥手

一切都是为了数据安全的传输。
拟人化,想象一下两个人在一片漆黑的世界里,如何用声音交流。

A:你好吗? ,B听见 (第一次握手,B知道A会说话,会听吗?A知道B会听,会说吗?)
B:谢谢!我很好,你呢?,A听见(第二次握手,A知道B能听见,会说话。B知道A会说,但是会听吗?)
A:我也很好,你准备干什么?,B听见(第三次握手,A,B双方均确认对方会说会听,然后开始交流)。

……

A:我要走了,你还有事吗?,A等待(第一次挥手)
B:嗯!稍等我想想 A:收到,等待中(第二次挥手)
B:没事了,走吧?B等待中(第三次)
A:好的,那走吧!B走了,A等了一会B没声了,A也走了(第四次)