Android知识点汇总

Android 进阶

Android平台的优点和不足

Android平台手机 5大优点:html

开放性:Android平台首先就是其开放性,开发的平台容许任何移动终端厂商加入到Android联盟中来。显著的开放性能够使其拥有更多的开发者;java

挣脱运营商的束缚:在过去很长的一段时间,手机应用每每受到运营商制约,使用什么功能接入什么网络,几乎都受到运营商的控制,而Android用户能够更加方便地链接网络,运营商的制约减小;android

丰富的硬件选择:因为Android的开放性,众多的厂商会推出千奇百怪,功能特点各具的多种产品。功能上的差别和特点,却不会影响到数据同步、甚至软件的兼容;git

开发商不受任何限制:Android平台提供给第三方开发商一个十分宽泛、自由的环境,不会受到各类条条框框的阻扰;github

无缝结合的Google应用: Android平台手机将无缝结合这些优秀的Google服务如地图、邮件、搜索等;web

Android平台手机几大不足:算法

安全和隐私:因为手机与互联网的紧密联系,我的隐私很可贵到保守。除了上网过程当中经意或不经意留下的我的足迹,Google这个巨人也时时站在你的身后,洞穿一切;数据库

过度依赖开发商缺乏标准配置:在Android平台中,因为其开放性,软件更多依赖第三方厂商,好比Android系统的SDK中就没有内置音乐播放器,所有依赖第三方开发,缺乏了产品的统一性;数组

同类机型用户不多:在很多手机论坛都会有针对某一型号的子论坛,对一款手机的使用心得交流,并分享软件资源。而对于Android平台手机,因为厂商丰富,产品类型多样,这样使用同一款机型的用户愈来愈少,缺乏统一机型的程序强化。浏览器

简述apk打包过程

1:打包资源文件,生成R.java文件 输入:res文件,Assets文件,AndroidManifest.xml文件,Android基础类库(Android.jar文件) 输出:R.java,resources.arsc 工具:aapt 工具位置:SDK\build-tools\29.0.0\aapt.exe

2:处理aidl文件,生成相应java文件 输入:源码文件,aidl文件,framework.aidl文件 输出:对应的.java文件 工具:aidl工具 工具位置:SDK\build-tools\29.0.0\aidl.exe

3:编译工程源代码,生成相应class文件 输入:源码文件(包括R.java和AIDL生成的.java文件),库文件(jar文件) 输出:.class文件 工具:javac工具 工具位置:Java\jdk1.8.0_201\bin\javac.exe

4:转换全部class文件,生成classes.dex文件 输入:.class文件(包括Aidl生成.class文件,R生成的.class文件,源文件生成的.class文件),库文件(.jar文件) 输出:.dex文件 工具:javac工具 工具位置:Java\jdk1.8.0_201\bin\javac.exe

5:打包生成apk文件 输入:打包后的资源文件,打包后类文件(.dex文件),libs文件(包括.so文件) 输出:未签名的.apk文件 工具:apkbuilder.bat工具已废弃,改成sdklib.jar工具 工具位置:E:\SDK\tools\lib\sdklib.jar

6:对apk文件进行签名 输入:未签名的.apk文件 输出:签名的.apk文件 工具: jarsigner工具 apksigner工具 工具位置: Java\jdk1.8.0_201\bin\jarsigner.exe SDK\build-tools\29.0.0\lib\apksigner.jar

7:对签名后的apk文件进行对齐处理 输入:签名后的.apk文件 输出:对齐后的.apk文件 工具:zipalign工具 工具位置:SDK\build-tools\29.0.0\zipalign.exe

注:工具位置基于win平台. 参考链接: developer.android.com/studio/buil… blog.csdn.net/jiangwei091…

LruCache 算法源码解析

咱们先看下LruCache算法的构造方法。

public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }
复制代码

从构造方法的源码咱们能够看到,在这段代码中咱们主要作了两件事。第一是判断下传递来的最大分配内存大小是否小于零,若是小于零则抛出异常,由于咱们若是传入一个小于零的内存大小就没有意义了。以后在构造方法内存就new了一个LinkHashMap集合,从而得知LruCache内部实现原理果真是基于LinkHashMap来实现的。

以后咱们再来看下存储缓存的put()方法。

public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }
 
        V previous;
        synchronized (this) {
            putCount++;
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }
 
        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }
 
        trimToSize(maxSize);
        return previous;
    }
复制代码

从代码中咱们能够看到,这个put方法内部其实没有作什么很特别的操做,就是对数据进行了一次插入操做。可是咱们注意到最后的倒数第三行有一个trimToSize()方法,那么这个方法是作什么用的呐?咱们点进去看下。

public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }
 
                if (size <= maxSize) {
                    break;
                }
 
                Map.Entry<K, V> toEvict = map.eldest();
                if (toEvict == null) {
                    break;
                }
 
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }
 
            entryRemoved(true, key, value, null);
        }
    }
复制代码

咱们能够看到,这个方法原来就是对内存作了一次判断,若是发现内存已经满了,那么就调用map.eldest()方法获取到最后的数据,以后调用map.remove(key)方法,将这个最近最少使用的数据给剔除掉,从而达到咱们内存不炸掉的目的。

咱们再来看看get()方法。

public final V get(K key) {
        //key为空抛出异常
        if (key == null) {
            throw new NullPointerException("key == null");
        }
 
        V mapValue;
        synchronized (this) {
            //获取对应的缓存对象
            //get()方法会实现将访问的元素更新到队列头部的功能
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            missCount++;
        }
复制代码

get方法看起来就是很常规的操做了,就是经过key来查找value的操做,咱们再来看看LinkHashMap的中get方法。

public V get(Object key) {
        LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key);
        if (e == null)
            return null;
        //实现排序的关键方法
        e.recordAccess(this);
        return e.value;
    }
复制代码

调用recordAccess()方法以下:

void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }
复制代码

因而可知LruCache中维护了一个集合LinkedHashMap,该LinkedHashMap是以访问顺序排序的。当调用put()方法时,就会在结合中添加元素,并调用trimToSize()判断缓存是否已满,若是满了就用LinkedHashMap的迭代器删除队尾元素,即最近最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法得到对应集合元素,同时会更新该元素到队头。

谈谈热修复的原理

咱们知道Java虚拟机 —— JVM 是加载类的class文件的,而Android虚拟机——Dalvik/ART VM 是加载类的dex文件, 而他们加载类的时候都须要ClassLoader,ClassLoader有一个子类BaseDexClassLoader,而BaseDexClassLoader下有一个数组——DexPathList,是用来存放dex文件,当BaseDexClassLoader经过调用findClass方法时,实际上就是遍历数组,找到相应的dex文件,找到,则直接将它return。而热修复的解决方法就是将新的dex添加到该集合中,而且是在旧的dex的前面,因此就会优先被取出来而且return返回。

简述app启动过程

App启动通常分为两种:

  1. 冷启动:当应用启动的时候,后台没有当前应用的进程,这时系统会建立一个新的进程分配给应用。
  2. 热启动:当前应用已经打开,可是被按下返回键或者Home键退出到桌面或者去到其余App,当再次回到应用时就是热启动。

这里主要介绍冷启动,大体分为5个步骤:

  1. 当点击桌面图标,就会利用Launcher经过Binder进程间通讯机制通知ActivityManagerService(AMS),它要启动一个Activity;
  2. AMS获得Launcher的通知,就会新建一个Task去准备启动Activity,并经过Binder机制通知Launcher进入Paused状态;
  3. Launcher获得消息,就会直接挂起,并经过Binder告诉AMS我已经Paused了;AMS知道了Launcher已经挂起以后,就能够放心的为新的Activity准备启动工做了,首先,APP确定须要一个新的进程去进行运行,因此须要建立一个新进程,这个过程是须要Zygote参与的,AMS经过Socket去和Zygote协商,而后利用Zygote.fork()建立一个新的进程,在这个进程里启动ActivityThread类,这就是每个应用程序都有一个ActivityThread与之对应的缘由;
  4. 进程建立好了,经过调用上述的ActivityThread的main方法,这是应用程序的入口,在这里开启Looper消息循环队列,这也是主线程默认绑定Looper的缘由;(另外,ActivityThread经过Binder将一个ApplicationThread类型的Binder对象传递给AMS,以便之后AMS可以经过这个Binder对象和它进行通讯);
  5. 这时候,App尚未启动完,要永远记住,四大组件的启动都须要AMS去启动,将上述的应用进程信息注册到AMS中,因此AMS在经过BinderActivityThread,如今一切准备就绪,它能够真正执行Activity的启动操做了。

补充知识: Zygote zygote名字翻译叫受精卵,zygote进程的建立是由Linux系统中init进程建立的,Android中全部的进程都是直接或者间接的由init进程fork出来的,Zygote进程负责其余进程的建立和启动,好比建立SystemServer进程。当须要启动一个新的android应用程序的时候,ActivityManagerService就会经过Socket通知Zygote进程为这个应用建立一个新的进程。

Launcher 咱们要知道手机的桌面也是一个App咱们叫它launcher,每个手机应用都是在Launcher上显示,而Launcher的加载是在手机启动的时候加载Zygote,而后Zygote启动SystenServer,SystenServer会启动各类ManageService, 包括ActivityManagerService,并将这些ManageService注册到ServiceManage 容器中,而后ActivityManagerService就会启动Home应用程序Launcher.

ActivityManagerService ActivityManagerService咱们简称AMS,四大组件都归它管,四大组件的跨进程通讯都要和它合做。

Binder Binder是Android跨进程通讯(IPC)的一种方式,也是Android系统中最重要的特性之一,android 四大组件以及不一样的App都运行在不一样的进程,它则是各个进程的桥梁将不一样的进程粘合在一块儿。

ActivityThread 首先ActivityThread并非一个Thread,其做用就是在main方法内作消息循环。那咱们常说的主线程是什么?主线程就是承载ActivityThread的Zygote fork而建立的进程。 ActivityThread的调用是在ActivityManageService.startProcessLocked()方法里调用并建立,这个类主要作了这几个事:

  1. 建立Looper,开启Looper循环
  2. 建立内部类 H,H继承于Handler 用于跨进程通讯切换线程
  3. 建立ApplicationThread跨进程Binder对象mAppThread。 这里要说一点,ActivityThread经过ApplicationThread与AMS进行通讯,ApplicationThread经过H与ActivityThread进行通讯(handler机制),处理Activity的事务。

From GavinCui12

一、点击桌面应用图标,Launcher进程将启动Activity(MainActivity)的请求以Binder的方式发送给了AMS。

二、AMS接收到启动请求后,交付ActivityStarter处理Intent和Flag等信息,而后再交给ActivityStackSupervisior/ActivityStack 处理Activity进栈相关流程。同时以Socket方式请求Zygote进程fork新进程。

三、Zygote接收到新进程建立请求后fork出新进程。

四、在新进程里建立ActivityThread对象,新建立的进程就是应用的主线程,在主线程里开启Looper消息循环,开始处理建立Activity。

五、ActivityThread利用ClassLoader去加载Activity、建立Activity实例,并回调Activity的onCreate()方法,这样便完成了Activity的启动。

Android虚拟机

ART和DVM(Dalvik)区别

什么是Dalvik: Dalvik是Google公司本身设计用于Android平台的Java虚拟机。 它能够支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行。 .dex格式是专为Dalvik应用设计的一种压缩格式,适合内存和处理器速度有限的系统。 Dalvik通过优化,容许在有限的内存中同时运行多个虚拟机的实例,而且每个Dalvik应用做为独立的Linux进程执行。 独立的进程能够防止在虚拟机崩溃的时候全部程序都被关闭。

什么是ART: 与Dalvik不一样,ART使用预编译(AOT,Ahead-Of-Time)。 也就是在APK运行以前,就对其包含的Dex字节码进行翻译,获得对应的本地机器指令,因而就能够在运行时直接执行了。 ART应用安装的时候把dex中的字节码将被编译成本地机器码,以后每次打开应用,执行的都是本地机器码。 去除了运行时的解释执行,效率更高,启动更快。

区别:

  1. Dalvik每次都要编译再运行,Art只会首次启动编译
  2. Art占用空间比Dalvik大(原生代码占用的存储空间更大),就是用“空间换时间”
  3. Art减小编译,减小了CPU使用频率,使用明显改善电池续航
  4. Art应用启动更快、运行更快、体验更流畅、触感反馈更及时

参考官方文档: source.android.com/devices/tec…

Android基础

github-sample

Serializable和Parcelable的区别

Serializable(Java自带): Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象能够在网络上进行传输,也能够存储到本地。

Parcelable(android 专用): 除了Serializable以外,使用Parcelable也能够实现相同的效果, 不过不一样于将对象进行序列化,Parcelable方式的实现原理是将一个完整的对象进行分解, 而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能了。

区别:

  1. 在使用内存的时候,Parcelable 类比Serializable性能高,因此推荐使用Parcelable类。
  2. Serializable在序列化的时候会产生大量的临时变量,从而引发频繁的 GC。
  3. Parcelable 不能使用在要将数据存储在磁盘上的状况。尽管 Serializable 效率低点,但在这种状况下,仍是建议你用Serializable 。

实现:

  1. Serializable 的实现,只须要继承Serializable 便可。这只是给对象打了一个标记,系统会自动将其序列化。
  2. Parcelabel 的实现,须要在类中添加一个静态成员变量 CREATOR,这个变量须要继承Parcelable.Creator 接口,(通常利用编译器能够自动生成)。

参考自简书:www.jianshu.com/p/a60b609ec…

From Noble_JIE

从出生来来讲,Serializable 是java的方法,Parcelable 是android独有的序列化反序列化方法 从用法上来讲,Serializable 比 Parcelable 简单,全部类实现Serializable便可,Parcelable须要对对全部属性及成员变量进行Creator 。 从性能上来讲,Parcelable 性能要高于Serializable。

怎样避免和解决ANR

Application Not Responding,即应用无响应。避免ANR最核心的一点就是在主线程减小耗时操做。一般须要从那个如下几个方案下手:

a)使用子线程处理耗时IO操做

b)下降子线程优先级,使用Thread或者HandlerThread时,调用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置优先级,不然仍然会下降程序响应,由于默认Thread的优先级和主线程相同

c)使用Handler处理子线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程

d)Activity的onCreate和onResume回调中尽可能避免耗时的代码

e)BroadcastReceiver中onReceiver代码也要尽可能减小耗时操做,建议使用intentService处理。intentService是一个异步的,会自动中止的服务,很好解决了传统的Service中处理完耗时操做忘记中止并销毁Service的问题

Broadcast 注册方式与区别

Broadcast广播,注册方式主要有两种.

第一种是静态注册,也可成为常驻型广播,这种广播须要在Androidmanifest.xml中进行注册,这中方式注册的广播,不受页面生命周期的影响,即便退出了页面,也能够收到广播这种广播通常用于想开机自启动啊等等,因为这种注册的方式的广播是常驻型广播,因此会占用CPU的资源。

第二种是动态注册,而动态注册的话,是在代码中注册的,这种注册方式也叫很是驻型广播,收到生命周期的影响,退出页面后,就不会收到广播,咱们一般运用在更新UI方面。这种注册方式优先级较高。最后须要解绑,否会会内存泄露

广播是分为有序广播和无序广播。

From chenwentong

1、Brodcast注册方式:

一、静态注册:

二、动态注册:
复制代码

2、静态注册:在清单文件manifest中注册,当程序退出以后还能够收到该广播。不能控制具体某个时间点接收和不接收广播。

3、动态注册:经过代码的方式注册context.registerReceiver(broadcastReceiver),注册的同时注意在不须要接受的时候进行反注册context.unregisterReceiver(broadcastReceiver);避免内存泄漏, 动态注册能够很好的控制广播接受。

4、从Android 8.0(API 26)开始,对于大部分隐式广播(广播的对象不是针对你开发的APP),不能在manifest中声明receiver,若是须要使用隐式广播,须要使用context.registerReceiver 的方法。

谈谈Android的安全机制

  1. Android 是基于Linux内核的,所以 Linux 对文件权限的控制一样适用于 Android。在 Android 中每一个应用都有本身的/data/data/包名 文件夹,该文件夹只能该应用访问,而其余应用则无权访问。
  2. Android 的权限机制保护了用户的合法权益。若是咱们的代码想拨打电话、发送短信、访问通讯录、定位、访问、sdcard 等全部可能侵犯用于权益的行为都是必需要在 AndroidManifest.xml 中进行声明的,这样就给了用户一个知情权。
  3. Android 的代码混淆保护了开发者的劳动成果。

ListView如何提升效率

一、使用分页加载,不要一次性加载全部数据。

二、复用convertView。在getItemView中,判断converView是否为空,若是不为空,可复用。

三、异步加载图片。Item中若是包含有webimage,那么最好异步加载。

四、快速滑动时,不显示图片。当快速滑动列表(SCROLL_STATE_FLING),item中的图片或获取须要消耗资源的view,能够不显示出来;而处于其余两种状态(SCROLL_STATE_IDLE和SCROLL_STATE_TOUCH_SCROLL),则将那些view显示出来

ANR异常的产生条件及解决方案

ANR是什么? ANR全称:Application Not Responding,也就是应用程序无响应. 简单来讲,就是应用跑着跑着,忽然duang,界面卡住了,没法响应用户的操做如触摸事件等.

缘由 Android系统中,ActivityManagerService(简称AMS)和WindowManagerService(简称WMS)会检测App的响应时间. 若是App在特定时间没法相应屏幕触摸或键盘输入时间,或者特定事件没有处理完毕,就会出现ANR.

ANR的产生须要同时知足三个条件

  1. 主线程:只有应用程序进程的主线程(UI线程)响应超时才会产生ANR
  2. 超时时间:产生ANR的上下文不一样,超时时间也不一样,但只要超过这个时间上限没有响应就会产生ANR
  3. 输入事件/特定操做:输入事件是指按键、触屏等设备输入事件,特定操做是指BroadcastReceiver和Service的生命周期中的各个函数调用

解决方案: 总结为一句话,即不要在主线程(UI线程)里面作繁重的操做

谈谈 RecyclerView 的性能优化

  1. 数据处理和视图加载分离

    从远端拉取数据确定是要放在异步的,在咱们拉取下来数据以后可能就匆匆把数据丢给了 VH 处理,其实,数据的处理逻辑咱们也应该放在异步处理,这样 Adapter 在 notify change 后,ViewHolder 就能够简单无压力地作数据与视图的绑定逻辑,好比:

    mTextView.setText(Html.fromHtml(data).toString());
    复制代码

    这里的 Html.fromHtml(data) 方法可能就是比较耗时的,存在多个 TextView 的话耗时会更为严重,这样便会引起掉帧、卡顿,而若是把这一步与网络异步线程放在一块儿,站在用户角度,最多就是网络刷新时间稍长一点。

  2. 数据优化

    分页拉取远端数据,对拉取下来的远端数据进行缓存,提高二次加载速度;对于新增或者删除数据经过 DiffUtil 来进行局部刷新数据,而不是一味地全局刷新数据。

  3. 布局优化

    1. 减小过渡绘制

      减小布局层级,能够考虑使用自定义 View 来减小层级,或者更合理地设置布局来减小层级,不推荐在 RecyclerView 中使用 ConstraintLayout,有不少开发者已经反映了使用它效果更差,相关连接有:Is ConstraintLayout that slow?、constraintlayout 1.1.1 not work well in listview。

    2. 减小 xml 文件 inflate 时间

      这里的 xml 文件不只包括 layout 的 xml,还包括 drawable 的 xml,xml 文件 inflate 出 ItemView 是经过耗时的 IO 操做,尤为当 Item 的复用概率很低的状况下,随着 Type 的增多,这种 inflate 带来的损耗是至关大的,此时咱们能够用代码去生成布局,即 new View() 的方式,只要搞清楚 xml 中每一个节点的属性对应的 API 便可。

    3. 减小 View 对象的建立

      一个稍微复杂的 Item 会包含大量的 View,而大量的 View 的建立也会消耗大量时间,因此要尽量简化 ItemView;设计 ItemType 时,对多 ViewType 可以共用的部分尽可能设计成自定义 View,减小 View 的构造和嵌套。

  4. 其余

    • 升级 RecycleView 版本到 25.1.0 及以上使用 Prefetch 功能,可参考 RecyclerView 数据预取。

    • 若是 Item 高度是固定的话,能够使用 RecyclerView.setHasFixedSize(true); 来避免 requestLayout 浪费资源;

    • 设置 RecyclerView.addOnScrollListener(listener); 来对滑动过程当中中止加载的操做。

    • 若是不要求动画,能够经过 ((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(false); 把默认动画关闭来提神效率。

    • TextView 使用 String.toUpperCase 来替代 android:textAllCaps="true"

    • TextView 使用 StaticLayout 或者 DynamicLayout 的自定义 View 来代替它。

    • 经过重写 RecyclerView.onViewRecycled(holder) 来回收资源。

    • 经过 RecycleView.setItemViewCacheSize(size); 来加大 RecyclerView 的缓存,用空间换时间来提升滚动的流畅性。

    • 若是多个 RecycledViewAdapter 是同样的,好比嵌套的 RecyclerView 中存在同样的 Adapter,能够经过设置 RecyclerView.setRecycledViewPool(pool); 来共用一个 RecycledViewPool

    • ItemView 设置监听器,不要对每一个 Item 都调用 addXxListener,应该你们公用一个 XxListener,根据 ID 来进行不一样的操做,优化了对象的频繁建立带来的资源消耗。

    • 经过 getExtraLayoutSpace 来增长 RecyclerView 预留的额外空间(显示范围以外,应该额外缓存的空间),以下所示:

      new LinearLayoutManager(this) {
          @Override
          protected int getExtraLayoutSpace(RecyclerView.State state) {
              return size;
          }
      };
      复制代码

如何减少apk安装包体积

1.代码混淆 minifyEnabled true

2.资源压缩 1)shrinkResources true 2)微信的AndResGuard

3.图片压缩 1)tinypng 2)svg 3)webp

4.so库配置 只保留两个abi平台,即armeabi和armeabi-v7a

5.dex优化 Facebook的redex

四种LaunchMode及其使用场景

standard 模式 这是默认模式,每次激活Activity时都会建立Activity实例,并放入任务栈中。使用场景:大多数Activity。

singleTop 模式 若是在任务的栈顶正好存在该Activity的实例,就重用该实例( 会调用实例的 onNewIntent() ),不然就会建立新的实例并放入栈顶,即便栈中已经存在该Activity的实例,只要不在栈顶,都会建立新的实例。使用场景如新闻类或者阅读类App的内容页面。

singleTask 模式 若是在栈中已经有该Activity的实例,就重用该实例(会调用实例的 onNewIntent() )。重用时,会让该实例回到栈顶,所以在它上面的实例将会被移出栈。若是栈中不存在该实例,将会建立新的实例放入栈中。使用场景如浏览器的主界面。无论从多少个应用启动浏览器,只会启动主界面一次,其他状况都会走onNewIntent,而且会清空主界面上面的其余页面。

singleInstance 模式 在一个新栈中建立该Activity的实例,并让多个应用共享该栈中的该Activity实例。一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例( 会调用实例的 onNewIntent() )。其效果至关于多个应用共享一个应用,无论谁激活该 Activity 都会进入同一个应用中。使用场景如闹铃提醒,将闹铃提醒与闹铃设置分离。singleInstance不要用于中间页面,若是用于中间页面,跳转会有问题,好比:A -> B (singleInstance) -> C,彻底退出后,在此启动,首先打开的是B。

Android的数据存储方式

Android提供了5中存储数据的方式,分别是如下几种:

一、使用Shared Preferences存储数据,用来存储key-value,pairs格式的数据,它是一个轻量级的键值存储机制,只能够存储基本数据类型。

二、使用文件存储数据,经过FileInputStream和FileOutputStream对文件进行操做。在Android中,文件是一个应用程序私有的,一个应用程序没法读写其余应用程序的文件。

三、使用SQLite数据库存储数据,Android提供的一个标准数据库,支持SQL语句。

四、使用Content Provider存储数据,是全部应用程序之间数据存储和检索的一个桥梁,它的做用就是使得各个应用程序之间实现数据共享。它是一个特殊的存储数据的类型,它提供了一套标准的接口用来获取数据,操做数据。系统也提供了音频、视频、图像和我的信息等几个经常使用的Content Provider。若是你想公开本身的私有数据,能够建立本身的Content Provider类,或者当你对这些数据拥有控制写入的权限时,将这些数据添加到Content Provider中实现共享。外部访问经过Content Resolver去访问并操做这些被暴露的数据。

五、使用网络存储数据

Android进程间通讯的几种姿式

进程间通讯即IPC,英文全称Inter-Process Communication,是指进程间数据交互的过程. Android底层是基于Linux,而Linux基于安全考虑,是不容许两个进程间直接操做对方的数据,这就是进程隔离. 六种经常使用姿式:

  1. Bundle
  2. 文件共享
  3. AIDL
  4. Messenger
  5. ContentProvider
  6. Socket

参考:Android开发艺术探索 第2章 2.4节

From lydlovexyz

ipc

理解Activity,View,Window三者关系

Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图)LayoutInflater像剪刀,Xml配置像窗花图纸。 1:Activity构造的时候会初始化一个Window,准确的说是PhoneWindow。 2:这个PhoneWindow有一个“ViewRoot”,这个“ViewRoot”是一个View或者说ViewGroup,是最初始的根视图。 3:“ViewRoot”经过addView方法来一个个的添加View。好比TextView,Button等 4:这些View的事件监听,是由WindowManagerService来接受消息,而且回调Activity函数。好比onClickListener,onKeyDown等

Android SharedPreference频繁操做有什么后果?能存多少数据

Android中 SP 的底层是由Xml来实现的,操做SP的过程就是Xml的序列化和解析的过程。Xml是存储在磁盘上的,所以当咱们频繁进行SP操做时,就是频繁进行序列化与解析,这就频繁进行I/O的操做,因此确定会致使性能消耗。同时序列化Xml是就是将内存中的数据写到Xml文件中,因为DVM的内存是颇有限的,所以单个SP文件不建议太大,具体多大是没有一个具体的要求的,可是咱们知道DVM 堆内存也就是16M,所以数据大小确定不能超过这个数字的。其实 SP 设置的目的就是为了保存用户的偏好和配置信息的,所以不要保存太多的数据。

请介绍下Android中经常使用的五种布局

最经常使用的布局方式为Absolute Layout、Relative Layout、Linear Layout、FrameLayout、TableLayout。其中Linear Layout和Relative Layout是最经常使用的方式,他们能够经过在xml配置文件或者代码中进行布局。

一、Frame Layout是最简单的布局方式,放置的控件都只能罗列到左上角,控件会有重叠,不能进行复杂的布局。

二、Linear Layout能够经过orientation属性设置线性排列的方向是垂直仍是纵向的,每行或每列只有一个元素,能够进行复杂的布局。

三、Absolute Layout可让子元素指定准确的x、y坐标值,并显示在屏幕上。Absolute Layout没有页边框,容许元素之间相互重叠。它是绝对坐标,因此在实际中不提倡使用。

四、Relative Layout容许子元素制定他们相对于其余元素或父元素的位置(经过ID制定)。所以,你能够以右对齐,或上下,或置于屏幕中央的形式来排列两个元素。元素按顺序排列,所以若是第一个元素在屏幕的中央,那么相对于这个元素的其余元素将以屏幕中央的相对位置来排列。这个是相对于Absolute Layout的,采用相对坐标,因此在实际中比较经常使用。

五、Table Layout将以子元素的位置分配到行或列。一个Table Layout由许多的Table Row组成,每一个Table Row都会定义一个row。Table Layout容器不会显示row、column或者cell的边线框。每一个row拥有0个或多个的cell; 和html中的table差很少。在实际中也常用。

一、线性布局 (LinearLayout):是一种很是经常使用的布局,次布局会将它包含的控件在线性方向上依次排列。经过android:orientation属性来肯定排列的方向是vertical(垂直)仍是horizontal(水平)。 二、相对布局(RelativeLayout):也是一种很是经常使用的布局,经过相对定位的方式让控件出如今布局的任何位置。 三、帧布局(FrameLayout):因为定位方式的欠缺,全部的控件都会默认摆放在布局的左上角,应用场景比较少。 四、绝对布局(AbsoluteLayout):用x、y坐标来肯定控件的位置。 五、表格布局(TableLayout):每个TableLayout里面有表格行TableRow,TableRow里面能够具体定义每个控件。

Android线程间通讯有几种方法?

  1. Handler机制
  2. runOnUiThread(Runnable action)
  3. View.post(Runnable action)
  4. AsyncTask
  5. 广播
  6. 使用EventBus、RxJava等框架

From Taonce

  1. 经过 Handler 来通讯

    val handler = @SuppressLint("HandlerLeak")
    object : Handler(){
        override fun handleMessage(msg: Message?) {
            Log.d("taonce","msg arg1: ${msg?.arg1}")
        }
    }
    thread {
        val msg: Message = handler.obtainMessage()
        msg.arg1 = 1
        handler.sendMessage(msg)
    }
    复制代码
  2. 经过 runOnUiThread()

    thread {
        val text = "runOnUiThread"
        runOnUiThread {
            tv.text = text
        }
    }
    复制代码
  3. 经过 View.post()

    thread {
        val text = "post"
        tv.post {
            tv.text = text
        }
    }
    复制代码
  4. 经过 AsyncTask

    class MyAsyncTask(val name: String) : AsyncTask<String, Int, Any>() {
    
        // 执行任务以前的准备工做,好比将进度条设置为Visible,工做在主线程
        override fun onPreExecute() {
            Log.d("async", "onPreExecute")
        }
    
        // 在onPreExecute()执行完以后当即在后台线程中调用
        override fun doInBackground(vararg params: String?): Any? {
            Log.d("async", "$name execute")
            Thread.sleep(1000)
            publishProgress(1)
            return null
        }
    
        // 调用了publishProgress()以后,会在主线程中被调用,用于更新总体进度
        override fun onProgressUpdate(vararg values: Int?) {
            Log.d("async", "progress is: $values")
        }
    
        // 后台线程执行结束后,会把结果回调到这个方法中,并在主线程中被调用
        override fun onPostExecute(result: Any?) {
            Log.d("async", "onPostExecute")
        }
    }
    复制代码

谈一谈对Android中Context理解

Context是一个抽象基类。在翻译为上下文,也能够理解为环境,是提供一些程序的运行环境基础信息。Context下有两个子类,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。而ContextWrapper又有三个直接的子类, ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity,因此Activity和Service以及Application的Context是不同的,只有Activity须要主题,Service不须要主题。Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各类承担着不一样的做用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的,所以在绝大多数场景下,Activity、Service和Application这三种类型的Context都是能够通用的。不过有几种场景比较特殊,好比启动Activity,还有弹出Dialog。出于安全缘由的考虑,Android是不容许Activity或Dialog凭空出现的,一个Activity的启动必需要创建在另外一个Activity的基础之上,也就是以此造成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),所以在这种场景下,咱们只能使用Activity类型的Context,不然将会出错。 getApplicationContext()和getApplication()方法获得的对象都是同一个application对象,只是对象的类型不同。 Context数量 = Activity数量 + Service数量 + 1 (1为Application)

Android 的四大组件都须要在清单文件中注册吗?并简述四大组件

Activity、Service、ContentProvider 如 果 要 使 用 则 必 须 在AndroidManifest.xml 中 进 行 注 册 , 而BroadcastReceiver则有两种注册方式,静态注册和动态注册。其中静态注册就是指在AndroidManifest.xml中进行注册,而动态注册时经过代码注册。

Activity:一般展示为一个用户操做的可视化界面。它为用户提供了一个完成操做指令的窗口。 (mp.weixin.qq.com/s/CgfeMT9Yt…) (Activity的来由)

Service:Android系统的服务(不是一个线程,是主程序的一部分),与Activity不一样,它是不能与用户交互的,不能本身启动的,需要调用Context.startService()来启动,执行后台,假设咱们退出应用时,Service进程并无结束,它仍然在后台行。

BroadcastReceiver:广播接收器是一个专一于接收广播通知信息,并作出相应处理的组件。

ContentProvider:(内容提供者)主要用于对外共享数据,也就是经过ContentProvider把应用中的数据共享给其它应用訪问,其它应用可以经过ContentProvider对指定应用中的数据进行操做。

Android 实现异步的几种方式,原理与各自特色

这边介绍三种:AsyncTask,HandlerThread和IntentService

AsyncTask原理:内部是Handler和两个线程池实现的,Handler用于将线程切换到主线程,两个线程池一个用于任务的排队,一个用于执行任务,当AsyncTask执行execute方法时会封装出一个FutureTask对象,将这个对象加入队列中,若是此时没有正在执行的任务,就执行它,执行完成以后继续执行队列中下一个任务,执行完成经过Handler将事件发送到主线程。AsyncTask必须在主线程初始化,由于内部的Handler是一个静态对象,在AsyncTask类加载的时候他就已经被初始化了。在Android3.0开始,execute方法串行执行任务的,一个一个来,3.0以前是并行执行的。若是要在3.0上执行并行任务,能够调用executeOnExecutor方法

HandlerThread原理:继承自Thread,start开启线程后,会在其run方法中会经过Looper建立消息队列并开启消息循环,这个消息队列运行在子线程中,因此能够将HandlerThread中的Looper实例传递给一个Handler,从而保证这个Handler的handleMessage方法运行在子线程中,Android中使用HandlerThread的一个场景就是IntentService

IntentService原理:继承自Service,它的内部封装了HandlerThread和Handler,能够执行耗时任务,同时由于它是一个服务,优先级比普通线程高不少,因此更适合执行一些高优先级的后台任务,HandlerThread底层经过Looper消息队列实现的,因此它是顺序的执行每个任务。能够经过Intent的方式开启IntentService,IntentService经过handler将每个intent加入HandlerThread子线程中的消息队列,经过looper按顺序一个个的取出并执行,执行完成后自动结束本身,不须要开发者手动关闭

既然RecyclerView在不少方面能取代ListView,Google为何没把ListView划上一条过期的横线?

ListView采用的是RecyclerBin的回收机制,在一些轻量级的List显示时效率更高.

  1. ListView采用的是RecyclerBin的回收机制在一些轻量级的List时效率更高。
  • 在处理少许数据使用 ListView
  • 在处理大量数据的时候使用 RecyclerView

ScrollView嵌套ListView的解决方案及其原理

自定义ListView 解决:重写其中的onMeasure()方法

缘由: ScrollView默认把Childview设置为UNSPEFEIED模式,而该模式下的ListView给本身的测量的高度就是第一个item的高度.原理: int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); 这个方法的做用是根据大小和模式来生成一个int值,这个int值封装了模式和大小信息. 首先MeasureSpec类是View的一个静态内部类,MeasureSpec类封装了从父布局到子布局传递的布局需求. 每一个MeasureSpec对象表明了宽度和高度的要求. MeasureSpec用int类型表示,前2位表明模式,后30位表明大小. 第一个参数Integer.MAX_VALUE >> 2:Integer.MAX_VALUE获取到int的最大值,可是表示大小的值size是int数值的底30位,因此把这个值右移两位,留出高两位表示布局模式. 此时这个值仍旧是一个30位所能表示的最大数,用该数做为控件的size,应该足够知足控件大小的需求. 第二个参数MeasureSpec.AT_MOST:表示这个控件适配父控件的最大空间.

(如下三种仅供参考,不推荐使用) 2.手动设置ListView高度 3.使用单个ListView取代ScrollView中全部内容 4.使用LinearLayout取代ListView

参考资料:
juejin.im/entry/5979a…
juejin.im/post/5a322c…

为何在子线程建立Handler会抛异常?如何正确使用

Handler的工做是依赖于Looper的,而Looper(与消息队列)又是属于某一个线程(ThreadLocal是线程内部的数据存储类,经过它能够在指定线程中存储数据,其余线程则没法获取到),其余线程不能访问。所以Handler就是间接跟线程是绑定在一块儿了。所以要使用Handler必需要保证Handler所建立的线程中有Looper对象而且启动循环。由于子线程中默认是没有Looper的,因此会报错。正确的使用方法是:

private final class WorkThread extends Thread {

        private Handler mHandler;

        public Handler getHandler() {
            return mHandler;
        }
          public void quit() {
            mHandler.getLooper().quit();
        }
        @Override
        public void run() {
            super.run();
            //建立该线程对应的Looper,
            // 内部实现
            // 1。new Looper()
            // 2。将1步中的lopper 放在ThreadLocal里,ThreadLocal是保存数据的,主要应用场景是:线程间数据互不影响的状况
            // 3。在1步中的Looper的构造函数中new MessageQueue();
            //其实就是建立了该线程对用的Looper,Looper里建立MessageQueue来实现消息机制
            //对消息机制不懂得同窗能够查阅资料,网上不少也讲的很不错。
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    Log.d("WorkThread", (Looper.getMainLooper() == Looper.myLooper()) + "," + msg.what);
                }
            };
            //开启消息的死循环处理即:dispatchMessage
            Looper.loop();
            //注意这3个的顺序不能颠倒
            Log.d("WorkThread", "end");
        }
    }
复制代码

自定义控件优化方案

  1. 为了加速你的view,对于频繁调用的方法,须要尽可能减小没必要要的代码。先从onDraw开始,须要特别注意不该该在这里作内存分配的事情,由于它会致使GC,从而致使卡顿。在初始化或者动画间隙期间作分配内存的动做。不要在动画正在执行的时候作内存分配的事情。
  2. 你还须要尽量的减小onDraw被调用的次数,大多数时候致使onDraw都是由于调用了invalidate().所以请尽可能减小调用invaildate()的次数。若是可能的话,尽可能调用含有4个参数的invalidate()方法而不是没有参数的invalidate()。没有参数的invalidate会强制重绘整个view。
  3. 另一个很是耗时的操做是请求layout。任什么时候候执行requestLayout(),会使得Android UI系统去遍历整个View的层级来计算出每个view的大小。若是找到有冲突的值,它会须要从新计算好几回。另外须要尽可能保持View的层级是扁平化的,这样对提升效率颇有帮助。 若是你有一个复杂的UI,你应该考虑写一个自定义的ViewGroup来执行他的layout操做。与内置的view不一样,自定义的view能够使得程序仅仅测量这一部分,这避免了遍历整个view的层级结构来计算大小。这个PieChart 例子展现了如何继承ViewGroup做为自定义view的一部分。PieChart 有子views,可是它历来不测量它们。而是根据他自身的layout法则,直接设置它们的大小。

谈谈Android的事件分发机制

事件的传递流程: Activity(PhoneWindow)->DecorView->ViewGroup->View。 事件分发过程当中三个重要的方法: dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent(); 事件传递规则 通常一次点击会有一系列的MotionEvent,能够简单分为:down->move->….->move->up,当一次event分发到ViewGroup时,ViewGroup收到事件后调用dispatchTouchEvent,在dispatchTouchEvent中先检查是否要拦截,若拦截则ViewGroup处理事件,不然交给有处理能力的子容器处理。

Android动画有几种,对其理解

  1. 视图动画。视图移动、view真真的位置并未移动。
  2. 帧动画。就和放电影同样,一帧一帧的播
  3. 属性动画。视图移动、其位置也会随着移动。
  4. 触摸返回动画。发生触摸事件时有反馈效果。好比波纹效果
  5. 揭露动画。从某一个点向四周展开或者从四周向某一点聚合起来。
  6. 转场动画 & 共享元素。好比切换activity。共享元素通常咱们使用在转换的先后两个页面有共同元素时。
  7. 视图状态动画。就是 View 在状态改变时执行的动画效果
  8. 矢量图动画。在图片的基础上作动画。
  9. 约束布局实现的关键帧动画。就是给须要动画效果的属性,准备一组与时间相关的值。关键的几个值。

Android 内存泄漏的缘由以及解决方案

  1. 内存泄漏指对象再也不使用,本该被回收,却由于有其余正在使用的对象持有该对象的引用,而没法被JVM回收

  2. 内存泄漏的影响:

    1. 应用可用内存减小,增长堆内存压力
    2. 频繁触发GC,会下降了应用的性能
    3. 到必定程序会致使内存溢出错误
  3. Android开发中常见内存泄漏及解决办法

    1. 静态变量生命周期与应用的生命周期同样,若是静态变量持有某个Activity的上下文,则对应Activity没法释放,致使内存泄漏(单例模式) 解决办法:使用Application的上下文
    2. 匿名内部类与非静态内部类由于都会持有外部类引用,当执行异步操做易致使内存泄漏 解决办法:将非静态内部类转为静态内部类+WeakReferenct的方式
    3. Handler消息队列存在延时消息致使内存泄漏 在onDestroy方法中调用Handler相应的方法移除回调和删除消息
    4. 各类注册的监听器忘记移除致使内存泄漏 解决办法:在onDestroy方法中取消注册
    5. 资源对象未关闭致使内存泄漏,如(IO,数据库,Bitmap等) 解决办法:及时关闭资源
    6. 属性动画未取消致使内存泄漏(如无限轮播图效果) 解决办法:onDestroy方法中取消动画
    7. 其余解决办法:使用AAC框架
  4. 内存泄漏排查工具: AS Monitor,MAT,LeakCanary

  5. 扩展: Java内存管理,GC

  6. Handler引发的内存泄漏 缘由:该线程持有Handler的引用,而Handler也持有Activity的引用,这就致使了Activity再也不使用时,GC回收不了Activity 解决:Handler持有的引用最好使用弱引用,在Activity被释放的时候要记得清空Message,取消Handler对象的Runnable

  7. 单例模式引发的内存泄漏 缘由:构建该单例的一个实例时须要传入一个Context,若是此时传入的是Activity,因为Context会被建立的实例一直持有,当Activity进入后台或者开启设置里面的不保留活动时,Activity会被销毁,可是单例持有它的Context引用,Activity无法销毁 解决:对于生命周期比Activity长的对象,要避免直接引用Activity的context,能够考虑使用ApplicationContext

  8. 非静态内部类建立静态实例引发的内存泄漏 缘由:非静态的内部类会自动持有外部类的引用,建立的静态实例就会一直持有的引用 解决:能够考虑把内部类声明为静态的

  9. 非静态匿名内部类引发的内存泄漏 缘由:若是匿名内部类被异步线程使用,可能会引发内存泄漏 解决:能够考虑把内部类声明为静态的

  10. 资源对象没有关闭引发的内存泄漏 缘由:资源性对象好比Cursor、File、Bitmap、视频等,系统都用了一些缓冲技术,在使用这些资源以后没有关闭 解决:处理完资源对象的逻辑记得关闭,最好是造成习惯现写一开一关

  11. 集合对象没有及时清理引发的内存泄漏 缘由:若是集合是static、不断的往里面添加东西、又忘记去清理,确定会引发内存泄漏 解决:集合里面的东西、有加入就应该对应有相应的删除

不能经过 GC 来解决内存泄漏问题

相关文章
相关标签/搜索