Android平台手机 5大优点:html
开放性:Android平台首先就是其开放性,开发的平台容许任何移动终端厂商加入到Android联盟中来。显著的开放性能够使其拥有更多的开发者;java
挣脱运营商的束缚:在过去很长的一段时间,手机应用每每受到运营商制约,使用什么功能接入什么网络,几乎都受到运营商的控制,而Android用户能够更加方便地链接网络,运营商的制约减小;android
丰富的硬件选择:因为Android的开放性,众多的厂商会推出千奇百怪,功能特点各具的多种产品。功能上的差别和特点,却不会影响到数据同步、甚至软件的兼容;git
开发商不受任何限制:Android平台提供给第三方开发商一个十分宽泛、自由的环境,不会受到各类条条框框的阻扰;github
无缝结合的Google应用: Android平台手机将无缝结合这些优秀的Google服务如地图、邮件、搜索等;web
Android平台手机几大不足:算法
安全和隐私:因为手机与互联网的紧密联系,我的隐私很可贵到保守。除了上网过程当中经意或不经意留下的我的足迹,Google这个巨人也时时站在你的身后,洞穿一切;数据库
过度依赖开发商缺乏标准配置:在Android平台中,因为其开放性,软件更多依赖第三方厂商,好比Android系统的SDK中就没有内置音乐播放器,所有依赖第三方开发,缺乏了产品的统一性;数组
同类机型用户不多:在很多手机论坛都会有针对某一型号的子论坛,对一款手机的使用心得交流,并分享软件资源。而对于Android平台手机,因为厂商丰富,产品类型多样,这样使用同一款机型的用户愈来愈少,缺乏统一机型的程序强化。浏览器
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算法的构造方法。
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启动通常分为两种:
这里主要介绍冷启动,大体分为5个步骤:
补充知识: 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()方法里调用并建立,这个类主要作了这几个事:
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的启动。
什么是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中的字节码将被编译成本地机器码,以后每次打开应用,执行的都是本地机器码。 去除了运行时的解释执行,效率更高,启动更快。
区别:
参考官方文档: source.android.com/devices/tec…
Serializable(Java自带): Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象能够在网络上进行传输,也能够存储到本地。
Parcelable(android 专用): 除了Serializable以外,使用Parcelable也能够实现相同的效果, 不过不一样于将对象进行序列化,Parcelable方式的实现原理是将一个完整的对象进行分解, 而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能了。
区别:
实现:
参考自简书:www.jianshu.com/p/a60b609ec…
From Noble_JIE
从出生来来讲,Serializable 是java的方法,Parcelable 是android独有的序列化反序列化方法 从用法上来讲,Serializable 比 Parcelable 简单,全部类实现Serializable便可,Parcelable须要对对全部属性及成员变量进行Creator 。 从性能上来讲,Parcelable 性能要高于Serializable。
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广播,注册方式主要有两种.
第一种是静态注册,也可成为常驻型广播,这种广播须要在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 的方法。
一、使用分页加载,不要一次性加载全部数据。
二、复用convertView。在getItemView中,判断converView是否为空,若是不为空,可复用。
三、异步加载图片。Item中若是包含有webimage,那么最好异步加载。
四、快速滑动时,不显示图片。当快速滑动列表(SCROLL_STATE_FLING),item中的图片或获取须要消耗资源的view,能够不显示出来;而处于其余两种状态(SCROLL_STATE_IDLE和SCROLL_STATE_TOUCH_SCROLL),则将那些view显示出来
ANR是什么? ANR全称:Application Not Responding,也就是应用程序无响应. 简单来讲,就是应用跑着跑着,忽然duang,界面卡住了,没法响应用户的操做如触摸事件等.
缘由 Android系统中,ActivityManagerService(简称AMS)和WindowManagerService(简称WMS)会检测App的响应时间. 若是App在特定时间没法相应屏幕触摸或键盘输入时间,或者特定事件没有处理完毕,就会出现ANR.
ANR的产生须要同时知足三个条件
解决方案: 总结为一句话,即不要在主线程(UI线程)里面作繁重的操做
数据处理和视图加载分离
从远端拉取数据确定是要放在异步的,在咱们拉取下来数据以后可能就匆匆把数据丢给了 VH 处理,其实,数据的处理逻辑咱们也应该放在异步处理,这样 Adapter 在 notify change 后,ViewHolder 就能够简单无压力地作数据与视图的绑定逻辑,好比:
mTextView.setText(Html.fromHtml(data).toString());
复制代码
这里的 Html.fromHtml(data)
方法可能就是比较耗时的,存在多个 TextView
的话耗时会更为严重,这样便会引起掉帧、卡顿,而若是把这一步与网络异步线程放在一块儿,站在用户角度,最多就是网络刷新时间稍长一点。
数据优化
分页拉取远端数据,对拉取下来的远端数据进行缓存,提高二次加载速度;对于新增或者删除数据经过 DiffUtil
来进行局部刷新数据,而不是一味地全局刷新数据。
布局优化
减小过渡绘制
减小布局层级,能够考虑使用自定义 View 来减小层级,或者更合理地设置布局来减小层级,不推荐在 RecyclerView 中使用 ConstraintLayout
,有不少开发者已经反映了使用它效果更差,相关连接有:Is ConstraintLayout that slow?、constraintlayout 1.1.1 not work well in listview。
减小 xml 文件 inflate 时间
这里的 xml 文件不只包括 layout 的 xml,还包括 drawable 的 xml,xml 文件 inflate 出 ItemView 是经过耗时的 IO 操做,尤为当 Item 的复用概率很低的状况下,随着 Type 的增多,这种 inflate 带来的损耗是至关大的,此时咱们能够用代码去生成布局,即 new View()
的方式,只要搞清楚 xml 中每一个节点的属性对应的 API 便可。
减小 View 对象的建立
一个稍微复杂的 Item 会包含大量的 View,而大量的 View 的建立也会消耗大量时间,因此要尽量简化 ItemView;设计 ItemType 时,对多 ViewType 可以共用的部分尽可能设计成自定义 View,减小 View 的构造和嵌套。
其余
升级 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
的缓存,用空间换时间来提升滚动的流畅性。
若是多个 RecycledView
的 Adapter
是同样的,好比嵌套的 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;
}
};
复制代码
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
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提供了5中存储数据的方式,分别是如下几种:
一、使用Shared Preferences存储数据,用来存储key-value,pairs格式的数据,它是一个轻量级的键值存储机制,只能够存储基本数据类型。
二、使用文件存储数据,经过FileInputStream和FileOutputStream对文件进行操做。在Android中,文件是一个应用程序私有的,一个应用程序没法读写其余应用程序的文件。
三、使用SQLite数据库存储数据,Android提供的一个标准数据库,支持SQL语句。
四、使用Content Provider存储数据,是全部应用程序之间数据存储和检索的一个桥梁,它的做用就是使得各个应用程序之间实现数据共享。它是一个特殊的存储数据的类型,它提供了一套标准的接口用来获取数据,操做数据。系统也提供了音频、视频、图像和我的信息等几个经常使用的Content Provider。若是你想公开本身的私有数据,能够建立本身的Content Provider类,或者当你对这些数据拥有控制写入的权限时,将这些数据添加到Content Provider中实现共享。外部访问经过Content Resolver去访问并操做这些被暴露的数据。
五、使用网络存储数据
进程间通讯即IPC,英文全称Inter-Process Communication,是指进程间数据交互的过程. Android底层是基于Linux,而Linux基于安全考虑,是不容许两个进程间直接操做对方的数据,这就是进程隔离. 六种经常使用姿式:
参考:Android开发艺术探索 第2章 2.4节
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中 SP 的底层是由Xml来实现的,操做SP的过程就是Xml的序列化和解析的过程。Xml是存储在磁盘上的,所以当咱们频繁进行SP操做时,就是频繁进行序列化与解析,这就频繁进行I/O的操做,因此确定会致使性能消耗。同时序列化Xml是就是将内存中的数据写到Xml文件中,因为DVM的内存是颇有限的,所以单个SP文件不建议太大,具体多大是没有一个具体的要求的,可是咱们知道DVM 堆内存也就是16M,所以数据大小确定不能超过这个数字的。其实 SP 设置的目的就是为了保存用户的偏好和配置信息的,所以不要保存太多的数据。
最经常使用的布局方式为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里面能够具体定义每个控件。
From Taonce
经过 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)
}
复制代码
经过 runOnUiThread()
thread {
val text = "runOnUiThread"
runOnUiThread {
tv.text = text
}
}
复制代码
经过 View.post()
thread {
val text = "post"
tv.post {
tv.text = text
}
}
复制代码
经过 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")
}
}
复制代码
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)
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对指定应用中的数据进行操做。
这边介绍三种: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按顺序一个个的取出并执行,执行完成后自动结束本身,不须要开发者手动关闭
ListView采用的是RecyclerBin的回收机制,在一些轻量级的List显示时效率更高.
自定义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的工做是依赖于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");
}
}
复制代码
事件的传递流程: Activity(PhoneWindow)->DecorView->ViewGroup->View。 事件分发过程当中三个重要的方法: dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent(); 事件传递规则 通常一次点击会有一系列的MotionEvent,能够简单分为:down->move->….->move->up,当一次event分发到ViewGroup时,ViewGroup收到事件后调用dispatchTouchEvent,在dispatchTouchEvent中先检查是否要拦截,若拦截则ViewGroup处理事件,不然交给有处理能力的子容器处理。
内存泄漏指对象再也不使用,本该被回收,却由于有其余正在使用的对象持有该对象的引用,而没法被JVM回收
内存泄漏的影响:
Android开发中常见内存泄漏及解决办法
内存泄漏排查工具: AS Monitor,MAT,LeakCanary
扩展: Java内存管理,GC
Handler引发的内存泄漏 缘由:该线程持有Handler的引用,而Handler也持有Activity的引用,这就致使了Activity再也不使用时,GC回收不了Activity 解决:Handler持有的引用最好使用弱引用,在Activity被释放的时候要记得清空Message,取消Handler对象的Runnable
单例模式引发的内存泄漏 缘由:构建该单例的一个实例时须要传入一个Context,若是此时传入的是Activity,因为Context会被建立的实例一直持有,当Activity进入后台或者开启设置里面的不保留活动时,Activity会被销毁,可是单例持有它的Context引用,Activity无法销毁 解决:对于生命周期比Activity长的对象,要避免直接引用Activity的context,能够考虑使用ApplicationContext
非静态内部类建立静态实例引发的内存泄漏 缘由:非静态的内部类会自动持有外部类的引用,建立的静态实例就会一直持有的引用 解决:能够考虑把内部类声明为静态的
非静态匿名内部类引发的内存泄漏 缘由:若是匿名内部类被异步线程使用,可能会引发内存泄漏 解决:能够考虑把内部类声明为静态的
资源对象没有关闭引发的内存泄漏 缘由:资源性对象好比Cursor、File、Bitmap、视频等,系统都用了一些缓冲技术,在使用这些资源以后没有关闭 解决:处理完资源对象的逻辑记得关闭,最好是造成习惯现写一开一关
集合对象没有及时清理引发的内存泄漏 缘由:若是集合是static、不断的往里面添加东西、又忘记去清理,确定会引发内存泄漏 解决:集合里面的东西、有加入就应该对应有相应的删除
不能经过 GC 来解决内存泄漏问题