升级gradle之后发现 打包出来的apk体积突然大了将近一倍。
在主工程的AndroidManifest.xml配置中,在<application>标签,添加android:extractNativeLibs=true属性,打包APK时,是否对so库进行压缩的控制属性为 android:extractNativeLibs。
handler的原理
简单来说Handler中只有一个Looper,Handler将Message发送到Looper的消息队列中,即MessageQueue,等待Looper的循环读取Message,处理Message,然后调用Message的target,即附属的Handler的dispatchMessage()方法,将该消息回调到handleMessage()方法中,然后完成更新UI操作。
Android中为什么Looper.loop()不会卡死主线程
Looper.loop()方法会循环调用消息队列messageQueue.next()方法来读取消息,当messageQueue中没有消息时主线程会阻塞在message.next()内部的messageQueue.nativePollOnce()函数调用中,类似于Object.wait(),但会让出cpu执行权,让cpu休眠直到下次有消息进入
谈谈List,Set,Map的区别
List中存储的数据是有顺序的,并且值允许重复;Map中存储的数据是无序的,它的键是不允许重复的,但是值是允许重复的;Set中存储的数据是无顺序的,并且不允许重复,但元素在集合中的位置是由元素的hashcode决定,即位置是固定的(Set集合是根据hashcode来进行数据存储的,所以位置是固定的,但是这个位置不是用户可以控制的,所以对于用户来说set中的元素还是无序的)
谈谈ArrayList和LinkedList的区别?
ArrayList是基于数组的数据结构,LinkedList是基于链表的数据结构。
ArrayList适用于查询操作,LinkedList适用于插入和删除操作
请简述 LinkedHashMap 的工作原理和使用方式?
查看LinkedHashMap源码发现是继承HashMap实现Map接口。也就是HashMap的方法LinkedMap都有。
LinkHashMap与HashMap的主要区别是:LinkedHashMap是有序的,hashmap是无序的。LinkedHashMap通过维护一个双向链表实现有序,也正是因为要维护这个链表,内存上有更大的开销。
补充下有序和无序:我们说的无序是插入顺序和输出顺序不一致。 补充一下链表结构和顺序结构:线性结构分为顺序结构,和链表结构。
顺序结构:在内存中是一块完整有序内存。所以我们在查询的时候时候直接索引index,便可找到要查询的数据,速度非常快,缺点是插入删除慢。有点类似班级排队时(一列纵队),每个人都知道自己在第几个位置。老师只要说第三个位置,那这个同学立马知道老师要找的是自己。这时候要插入一个同学到第二个位置,所以之前第二个位置开始往后的每个同学的位置都要+1。所以比较慢。
链表结构:通过结点头记录该结点的上一个结点和下一个下一个结点(就是传统的双链表,单链表就是只记录下一个结点,循环链表就是最后一个结点的下一个结点指向第一个结点)。正是因为这种关系,所以链表结构不需要一块完整的内存,而且插入删除相对快,但是查询相对慢。但是因为要维护结点头,所以内存开销相对大一点。有点类似于班级排队时,每个人虽然不知道自己的位置,但是知道自己前面是谁和后面是谁。当要插入一个同学b时到c前面时,只要c 同学记住自己之前是a,现在换成b.b记住自己前面是a,后面是c。所以想对来说插入很快。删除类似。但是当老师按位置查询时,就要先从第一个开始计数,知道找到老师要找的数字。所以查询慢。
如何实现多线程中的同步?
多线程同步和异步不是一回事。几种情况,
就是大家说的synchronized 他可以保证原子性,保证多个线程在操作同一方法时只有一个线程可以持有锁,并且操作该方法,
就是手动调用读写锁,
手动操作线程的wait和notify
volatile我记得是没有原子性的,他可以保证内存可见性,在多线程的情况下保证每个线程的数据都是最新的
synchronized和volatile关键字的区别?
1.volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
2.volatile仅能使用在变量级别;synchronized则可以使用 在变量、方法、和类级别的volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
3.volatile不会造成线程的阻塞;synchronized可能会造成 线程的阻塞。
4.volatile标记的变量不会被编译器优化;synchronized标 记的变量可以被编译器优化
请谈谈 Thread 中 run() 与 start()的区别?
run() 和普通的成员方法一样,可以被重复调用。但是如果单独调用 run 方法,则不是在子线程中执行。start()这个方法只能被调用一次。调用这个方法后 程序会启动一个 新的线程来 执行 run 方法。注意 :调用start 后,线程处于可运行状态(并没有运行),一旦得到 cup 时间片,就开始执行run 方法,run 方法结束后,线程则立即终止。
谈谈线程阻塞的原因?
1、线程执行了Thread.sleep(int n)方法,线程放弃CPU,睡眠n毫秒然后恢复运行。
2、线程要执行一段同步代码,由于无法获得相关的同步锁,只好进入阻塞状态,等到获得了同步锁,才能恢复运行。
3、线程执行了一个对象的wait()方法,进入阻塞状态,只有等到其他线程执行了该对象的notify()或notifyAll()方法,才可能将其唤醒。
4、线程执行I/O操作或进行远程通信时,会因为等待相关的资源而进入阻塞状态。例如,当线程执行System.in.read()方法时,如果用户没有向控制台输入数据,则该线程会一直等读到了用户的输入数据才从read()方法返回。进行远程通信时,在客户程序中,线程在以下情况可能进入阻塞状态。
5、请求与服务器建立连接时,即当线程执行Socket的带参数的构造方法,或执行Socket的connect()方法时,会进入阻塞状态,直到连接成功,此线程才从Socket的构造方法或connect()方法返回。
6、线程从Socket的输入流读取数据时,如果没有足够的数据,就会进入阻塞状态,直到读到了足够的数据,或者到 达输入流的末尾,或者出现了异常,才从输入流的read()方 法返回或异常中断。输入流中有多少数据才算足够呢?这要看线程执行的read()方法的类型。int read(); 只要输入流中有一个字节,就算足够。int read(byte[] buff); 只要输入流中的字节数目与参数buff数组的长度相同,就算足够。String readLine(); 只要输入流中邮一行字符串,就算足够。值得注意的是,InputStream类并没有readLine方法,在过滤流BufferedReader类中才有此方法。
7、线程向Socket的输出流写一批数据时,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输出流的write()方法返回或异常中断。
8、调用Socket的setSoLinger()方法设置了关闭Socket的延迟时间,那么当线程执行Socket的close方法时,会进入阻塞状态,直到底层Socket发送完所有剩余数据,或者超过了setSoLinger()方法设置的延迟时间,才从close()方法返回。
Service如何进行保活?
1、利用系统广播拉活 2、利用系统service拉活 3、利用Native进程拉活<Android5.0以后失效> fork进行监控主进程 4、利用native拉活 5、利用JobScheduler机制拉活<Android5.0以后> 6、利用账号同步机制拉活
简单介绍下ContentProvider是如何实现数据共享的?
ContentProvider(内容提供者):对外提供了统一的访问数据的接口。 ContentResolver(内容解析者):通过URI的不同来操作不同的ContentProvider中的数据。 ContentObserver(内容观察者):观察特定URI引起的数据库的变化。通过ContentResolver进行注册,观察数据是否发生变化及时通知刷新页面(通过Handler通知主线程更新UI)。
请简述 Http 与 Https 的区别?
HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数 据能加密传输,于是网景公司设计了SSL(Secure SocketsLayer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。 1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。 2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。 3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。 4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。 最后一点在Android 9.0 如果用http进行传输,需要在application节点下设置android:usesCleartextTraffic="true"
TCP、UDP、socket理解
tcp需要三次握手,四次挥手才能将一串消息发送出去。udp是无连接的,线程不安全的,发送消息效率更高,不管消息是否到达
而socket一般用于即时通讯
view的绘制原理
主要是三个方法,OnMeasure(绘制视图大小. 精确模式(MeasureSpec.EXACTLY,MeasureSpec.AT_MOST) onLayout(视图展示的位置)ondraw(绘制)
简述view的事件分发机制
主要三个方法去处理;是否消费(方法名太长)-是否拦截 -是否处理
android主要有几种动画形式?
属性动画,帧动画,补间动画(平移、渐变、旋转、缩放)
android中activity启动模式有几种?
默认模式(new一个创建一次)
栈顶复用(这个activity是在栈顶,再次启动这个activity就直接复用,不创建新的activity)
栈内复用(任务栈中存在这个activity,拉出来置顶复用)
全局单例模式(升级版栈内复用)
RxJava常用操作符
创建型操作符:
create操作符已经用到就是创建被观察者
just是不断地将事件添加到任务队列中
fromArray:跟just基本一样就是可以直接接收数组
interval:创建一个按固定时间间隔发射整数序列的 Observable,相当于定时器
range:创建发射指定范围的整数序列的 Observable,可以拿来替代 for 循环,发射一个范围内的有
序整数序列
repeat:创建一个 N 次重复发射特定数据的 Observable
变换操作符:
map:将 Observable 转换为一个新的 Observable 对象并发射,观察者将收到新的 Observable 处理
flatMap:该操作符将 Observable 发射的数据集合变换为 Observable 集合,然后将这些 Observable发射的数据平坦化地放进一个单独的 Observable
cast:强制将数据转换成另外一个类型
concatMap:该操作符功能与 flatMap 操作符一致;不过,它解决了 flatMap 交叉问题,提供了一种能够把发射的值连续在一起的函数,而不是合并它们
buffer:buffer 操作符将源 Observable 变换为一个新的 Observable,这个新的 Observable 每次发射一组列表值而不是一个一个发射,和 buffer 操作符类似的还有 window 操作符,只不过 window操作符发射的是 Observable 而不是数据列表。
groupBy:该操作符用于分组元素,将源 Observable 变换成一个发射 Observables 的新 Observable,它们中的每一个新 Observable 都发射一组指定的数据。
过滤操作符:
filter:filter 操作符是对源 Observable 产生的结果自定义规则进行过滤,只有满足条件的结果才会
提交给订阅者
elementAt:elementAt 操作符用来返回指定位置的数据
distinct:distinct 操 作 符 用 来 去 重 , 其 只 允 许 还 没 有 发 射 过 的 数 据 项 通 过
skip、take:skip 操作符将源 Observable 发射的数据过滤掉前 n 项;而 take 操作符则只取前 n 项;
ignoreElements:ignoreElements 操作符忽略所有源 Observable 产生的结果,只把 Observable 的 onSubscribe和 onComplete 事件通知给订阅者
组合操作符:
startWith:startWith 操作符会在源 Observable 发射的数据前面插上一些数据
merge:merge 操作符将多个 Observable 合并到一个 Observable 中进行发射,merge 可能会让合并的Observable 发射的数据交错。
concat:将多个 Obserbavle 发射的数据进行合并发射。concat 严格按照顺序发射数据,前一个
Observable 没发射完成是不会发射后一个 Observable 的数据的
zip:zip 操作符合并两个或者多个 Observable 发射出的数据项,根据指定的函数变换它们,并发
射一个新值。
combineLastest:当两个 Observable 中的任何一个发射了数据时,使用一个函数结合每个 Observable 发射的最近数据项,并且基于这个函数的结果发射数据。
辅助操作符:
delay:delay 操作符让原始 Observable 在发射每项数据之前都暂停一段指定的时间段。
这个测试需要在Android环境下,不能在java环境下测试,否则没有效果。
Do: Do 系列操作符就是为原始 Observable 的生命周期事件注册一个回调,当 Observable 的某个
事件发生时就会调用这些回调
subscribeOn、observeOn:
subscribeOn 操作符用于指定 Observable 自身在哪个线程上运行,如果 Observable 需要执行耗时操作,一般可以让其在新开的一个子线程上运行。
observerOn 用来指定 Observer 所运行的线程,也就是发射出的数据在哪个线程上使用。一般情况下会指定在主线程中运行,这样就可以修改UI。
timeout:如果原始 Observable 过了指定的一段时长没有发射任何数据,timeout 操作符会以一个
onError 通知终止这个 Observable,或者继续执行一个备用的 Observable。
错误处理操作符:
catch:catch 操作符拦截原始 Observable 的 onError 通知,将它替换为其他数据项或数据序列,让
产生的 Observable 能够正常终止或者根本不终止。RxJava 将 catch 实现为以下 3 个不同的操作符:
retry:retry 操作符不会将原始 Observable 的 onError 通知传递给观察者,它会订阅这个 Observable,再给它一次机会无错误地完成其数据序列。 retry 总是传递 onNext 通知给观察者,由于重新订阅,这可能会造成数据项重复。RxJava 中的实现为 retry 和 retryWhen。
布尔操作符:
all:操作符根据一个函数对源 Observable 发射的所有数据进行判断,最终返回的结果就是这
个判断结果。这个函数使用发射的数据作为参数,内部判断所有的数据是否满足我们定义好的
判断条件。如果全部都满足则返回 true,否则就返回 false。
contains、isEmpty:contains 操作符用来判断源 Observable 所发射的数据是否包含某一个数据。如果包含该数据,会返回 true;如果源 Observable 已经结束了却还没有发射这个数据,则返回 false。isEmpty操作符用来判断源 Observable 是否发射过数据。
条件操作符:
amb:amb 操作符对于给 定两个 或多个 Observable,它只 发射首先发射数 据或通知 的那个
Observable 的所有数据。
defaultIfEmpty:发射来自原始 Observable 的数据。如果原始 Observable 没有发射数据,就发射一个默认数据
转换操作符:
toList:toList 操作符将发射多项数据且为每一项数据调用 onNext 方法的 Observable 发射的多项数
据组合成一个 List,然后调用一次 onNext 方法传递整个列表。
toSortedList:toSortedList 操作符类似于 toList 操作符;不同的是,它会对产生的列表排序,默认是自然升序。如果发射的数据项没有实现 Comparable 接口,会抛出一个异常。当然,若发射的数据项
没有实现 Comparable 接口,可以使用 toSortedList(Func2)变体,其传递的函数参数 Func2 会作
用于比较两个数据项。
toMap:toMap 操作符收集原始 Observable 发射的所有数据项到一个 Map(默认是 HashMap),然
后发射这个 Map。
如何排查OOM?
简单来说,就是程序的内存不够了,挂掉了,Android Studio 中集成的 Profiler 可以分析 APP 内存。可以使用 Dump 功能方便的查看当前的内存快照,一般来说bitmap被view持有,造成OOM的情况较多
内存泄漏
什么是内存泄漏? 通常我们认为,在运行的程序中,如果一个无法访问的对象却仍然占用着内存空间,即为此对象造成了内存泄漏
ANR的排查方法
1.当您的 Activity 位于前台时,您的应用在 5 秒钟内未响应输入事件或 BroadcastReceiver(如按键或屏幕轻触事件)。
2.虽然前台没有 Activity,但您的 BroadcastReceiver 用了相当长的时间仍未执行完毕。
一般可以通过监控日志查找分析
Intent传输数据大小限制
intent传输数据的大小受Binder的限制,理论上上限是1M,实际512K以上就会报错,
解决办法
减少传输数据量
Intent通过绑定一个Bundle来传输,这个可以超过1M,不过也不能过大
通过内存共享,使用静态变量或者使用EventBus等类似的通信工具
通过文件共享
深拷贝和浅拷贝的区别
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
LRUCache 原理
LruCache 中 Lru 算法的实现就是通过 LinkedHashMap 来实现的。LruCache 中将 LinkedHashMap 的顺序设置为 LRU 顺序来实现 LRU 缓存,通过调用get/put等方法控制缓存大小,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除。
什么是MVVM
Model代表数据模型,数据和业务逻辑都在Model层中定义,View代表UI视图,负责数据的展示,ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作。实现了数据双向绑定
从ActivityA 启动 ActivityB的生命周期
A.onCreate —> A.onStart —> A.onResume —>A.onPause —>B.onCreate —> B.onStart —> B.onResume—> A.onSaveInstanceState —> A.onStop
EventBus原理
主要解决多线程和多activity、fragment之间的通信问题,原理是通过注解和反射实现,将方法保存在公共队列中供其调用,首先在register()的时候,通过反射把当前类的所有方法遍历,然后把带有@Subscribe注解的方法保存在队列中,在调用的时候发送post方法,与队列中的方法进行匹配,这里只匹配方法的参数,如果一样的话就掉起该方法
gradle中minsdkversion、compilesdkversion、targetsdkversion的区别
compileSdkVersion 告诉 Gradle 用哪个 Android SDK 版本编译你的应用。minSdkVersion 则是应用可以运行的最低要求 targetSdkVersion 是 Android 提供向前兼容的主要依据,在应用的 targetSdkVersion 没有更新之前系统不会应用最新的行为变化。
内存优化技巧
1.谨慎使用 Service。IntentService 是 Service 的一个子类,在它的内部有一个工作线程来处理耗时任务,当任务执行完后,IntentService 就会自动停止
2.选择优化后的数据容器。Android 提供了几个优化后的数据容器,包括 SparseArray、SparseBooleanArray 以及 LongSparseArray。
3.小心代码抽象。抽象会导致更多的代码需要被执行,也就是需要更多的时间和把更多的代码映射到内存中
4.使用 protobuf 作为序列化数据。Protocol buffers 是 Google 设计的,它可以对结构化的数据序列化,与 XML 类似,不过比 XML 更小,更快,而且更简单
5.Apk 瘦身。有些资源和第三方库会在我们不知情的情况下大量消耗内存,Android Studio 提供了 R8 和 ProGuard 帮助我们缩小 Apk,去掉不必要的资源
6.使用 Dagger2 进行依赖注入。Dagger2 是在编译期生成代码,而不是用反射实现的,这样就避免了反射带来的内存开销,而是在编译期生成代码
7.谨慎使用第三方库。这些第三方库包括日志、分析、图片加载、缓存以及其他框架,都有可能带来性能问题
Android轻量级数据SparseArray详解
由于SparseArray中Key存储的是数组形式,因此可以直接以int作为Key。避免了HashMap的装箱拆箱操作,性能更高且int的存储开销远远小于Integer,SparseArray采用了延迟删除的机制,通过将删除KEY的Value设置DELETED,方便之后对该下标的存储进行复用,使用二分查找,时间复杂度为O(LogN),如果遇到频繁删除,不会触发gc机制,导致mSize 远大于有效数组长度,造成性能损耗
一般使用场景:key为整型;不需要频繁的删除;元素个数相对较少
DataBinding原理分析
使用了观察者设计模式,可以看到单项绑定和双向绑定的区别就是“@”和“{}”之间多了个“=”,简化Activity/Fragment中部分操作View的代码,让我们更加关注业务逻辑的实现
Android蓝牙通信
有两种常见的方式,一种是socket方式,另一种是通过GATT Server(Android 5.0以后)通信。通过Bluetooth API执行扫描、配对、连接、关闭等操作
wait和sleep的区别
wait会释放锁、sleep不会
wait是Object的方法,sleep是Thread的方法
线程池的工作机制
如果当前运行的线程少于corePoolSize(核心线程数),则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。
如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。
如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。
注意:wait()、notify()、notifyAll()必须在synchronized中使用,否则会报异常:java.lang.IllegalMonitorStateException: object not locked by thread before notifyAll()
实现线程的方式
继承Thread、实现Runable、实现Callable
Android常用动画
属性动画、补间动画(平移、渐变、旋转、缩放)、帧动画
Android常用存储方式
SharePreferences、SQLite、Contert Provider、File、网络存储
mmkv存储原理
基于 mmap 的高性能通用 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强
Android依赖版本统一管理
通过自定义config.build文件可以实现依赖的统一管理,对于组件化开发的工程提供维护便利
Android本地广播和全局广播的区别
1、本地广播:发送的广播事件不被其他应用程序获取,也不能响应其他应用程序发送的广播事件。本地广播只能被动态注册,不能静态注册。动态注册或方法时需要用到LocalBroadcastManager。
2、全局广播:发送的广播事件可被其他应用程序获取,也能响应其他应用程序发送的广播事件(可以通过 exported–是否监听其他应用程序发送的广播 在清单文件中控制) 全局广播既可以动态注册,也可以静态注册
Gradle中混淆的使用
一个是 Java 代码的混淆,另外一个是资源的压缩,如keep 命令指的是一系列以 -keep 开头的命令,它主要用来保留 Java 中不需要进行混淆的元素
为什么onCreate和onResume方法中不可以获取View宽高
是因为还没执行View的绘制流程
view.post之所以能够拿到宽高,是因为在绘制之前,会将获取宽高的任务放到Handler的消息队列,等到View的绘制结束之后,便会执行
子线程发消息到主线程进行更新 UI,有几种方式
handler 、AsyncTask、EventBus,广播,view.post, runinUiThread,其实本质上就2种:handler机制 + 广播
子线程中能不能 new handler?为什么
必须可以。子线程 可以new 一个mainHandler,然后发送消息到UI Thread。
AsyncTask(异步任务)的工作原理
AsyncTask是对Handler和线程池的封装,使用它可以更新用户界面,当然,这里的更新操作还是在主线程中完成的,但由于AsyncTask内部包含了一个Handler,所以可以发送消息给主线程,让他更新UI,另外AsyncTask内还包含了一个线程池,避免了不必要的创建和销毁线程的开销
谈谈对Android NDK的理解
NDK是一系列工具的················集合.NDK提供了一系列的工具,帮助开发者快速开发C或C++的动态库,并能自动将so和java应用一起打包成apk.这些工具对开发者的帮助是巨大的.NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU,平台,ABI等差异,开发人员只需要简单修改 mk文件(指出"哪些文件需要编译","编译特性要求"等),就可以创建出so.NDK可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作.NDK提供了一份稳定,功能有限的API头文件声明.Google明确声明该API是稳定的,在后续所有版本中都稳定支持当前发布的API.从该版本的NDK中看出,这些 API支持的功能非常有限,包含有:C标准库(libc),标准数学库(libm ),压缩库(libz),Log库(liblog).
我只是启动一个应用程序,为什么Application的onCreate执行了多次?
在启动应用程序的时候,linux中调用fork创建的子进程,将共享父进程的代码空间,复制父进程数据空间,此时子进程会获得父进程的所有变量的一份拷贝。如果这个时候第三方框架会启动新的进程,那么也会执行接下来的Application的代码,所以会执行多次了
为什么ArrayMap比HashMap更适合Android开发
HashMap在存放数据的时候,无论存放的量是多少,首先是会生成一个Entry对象,这个就比较浪费内存空间,而ArrayMap只是把数据插入到数组中,不用生成新的对象,存放大量数据的时候,ArrayMap性能上就不如HashMap,因为ArrayMap使用的是二分查找法找的下标,当数据多了下标值找起来时间就花的久,此外还需要将所有数据往后移再插入数据,而HashMap只要插入到链表或者树后面即可
为什么Arrays.asList后往里add数据会报错
java.util.ArrayList里面,我们看下里面的代码是存在add方法的,我们再回头再去看看asList生成的List是在java.util.Arrays包里面的,而这里面的ArrayList我们看到了,并没有去实现List接口,所以也就没有add,get等方法,另外在kotlin里面,我们会看到一个细节,当你敲完Arrays.asList的时候,编译器会提示你,可以转换成listof函数,而这个还是我们知道生成的list都是只能读取,不能往里写数据
Thread.sleep(0)到底“睡没睡”
其实在Android操作系统中,每个线程使用cpu资源都是有优先级的,优先级高的才有资格使用,而操作系统则是在一个线程释放cpu资源以后,重新计算所有线程的优先级来重新分配cpu资源,所以sleep真正的意义不是暂停,而是在接下去的时间内不参与cpu的竞争,等到cpu重新分配完资源以后,如果优先级没变,那么继续执行,所以sleep(0)秒的真正含义是触发cpu资源重新分配
Android 中 Intent 采用了什么设计模式?
采用了原型模式
原型模式的好处在于方便地拷贝某个实例的属性进行使用、又不会对原实例造成影响,其逻辑在于对 Cloneable 接口的实现
Android中对象的内存回收机制
对象的内存回收机制由垃圾回收器来实现。垃圾回收器是一个系统级别的服务,它会定期扫描应用的内存空间,检查哪些对象已经不再被引用,然后将这些对象标记为垃圾,并回收它们所占用的内存空间。 对象的内存回收机制主要涉及以下几个方面:
1.引用计数
2.标记清除
3.复制算法
4.标记整理
Android中如何实现插件动态加载
实现插件化主要有两种方式:动态加载和静态加载,其中动态加载是指在应用程序运行时加载插件,而静态加载是指在编译时加载插件。
Andorid中如何实现数据加密
1. 对称加密(如 AES、DES、3DES)
2.非对称加密(如 RSA、DSA)
3.消息摘要(如 MD5、SHA)
Java中实现线程同步的方式
synchronized关键字:使用synchronized关键字可以对共享资源进行同步,使得在同一时间只有一个线程可以访问该资源。synchronized可以修饰方法或代码块,保证了线程的互斥访问和可见性。
Lock接口:Lock是Java5中引入的一个新的同步机制,通过Lock和Unlock方法来对共享资源进行加锁和释放操作,与synchronized相比,可以更加灵活地控制同步范围。
ReentrantLock类:ReentrantLock是Lock接口的一个实现类,功能比synchronized更加强大,包括可重入锁、可中断锁、公平性、实现Condition等功能。
volatile关键字:使用volatile关键字修饰的变量在多个线程之间可见,即一个线程对该变量做出的修改,会被其他线程立即看到,并更新自己的缓存。volatile关键字可以保证线程之间的可见性。
AtomicInteger类:AtomicInteger是Java中提供的一个原子性变量类,它可以保证多个线程同时对一个整数变量进行加减操作时的原子性。
synchronized块和Lock接口实现读写锁:在读写分离的场景中,可以使用synchronized块或Lock接口的实现类ReadWriteLock来实现读写锁,从而提高程序的并发性和性能。
Movie原生实现gif图动态显示
Android 方法超过64K
当项目越做越大,方法数越来越多的时候,会不可避免的遇到方法数超过64K的错误,这是因为单个dex文件索引的方法是由short类型来保存的。而short的长度范围是(-32768~32767),即总共65536个。那么如何解决?
1.精简方法数量,删除无用的类、方法、第三方库。(治标不治本,业务代码增加,始终会达到临界值) 2.使用ProGuard去掉一些未使用的代码 (同解决方法1) 3.插件化方案 4.分割Dex
多进程android webview,Android上多进程中使用webview的问题
在Applicationd类的onCreate或者onBaseContextAttached方法中加入
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
initWebViewDataDirectory(this);
}
/**
* 得到进程名称
* @param context
* @return
*/
public static String getProcessName(Context context) {
try {
if (context == null)
return null;
ActivityManager manager = (ActivityManager)
context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningAppProcessInfo processInfo :
manager.getRunningAppProcesses()) {
if (processInfo.pid == android.os.Process.myPid()) {
return processInfo.processName;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 为webView设置目录后缀
* @param context
*/
@RequiresApi(api = Build.VERSION_CODES.P)
public static void initWebViewDataDirectory(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
String processName = getProcessName(context);
if (!context.getPackageName().equals(processName)) {//判断是否是默认进程名称
WebView.setDataDirectorySuffix(processName);
}
}