知识宝典 - 1835434698/1835434698.github.io GitHub Wiki
3部分、5层。
1、activity
(1)一个Activity通常就是一个单独的屏幕(窗口)。
(2)Activity之间通过Intent进行通信。
(3)android应用中每一个Activity都必须要在AndroidManifest.xml配置文件中声明,否则系统将不识别也不执行该Activity。
2、service
(1)service用于在后台完成用户指定的操作。service分为两种:
(a)started(启动):当应用程序组件(如activity)‘「调用startService()方法启动服务时,服务处于started状态。(多个启动同一个service后者覆盖前者。)
(b)bound(绑定):当应用程序组件调用bindService()方法绑定到服务时,服务处于bound状态。(多个绑定一个service,后者只调用被绑定方法。)
(2)startService()与bindService()区别:
(a)started service(启动服务)是由其他组件调用startService()方法启动的,这导致服务的onStartCommand()方法被调用。当服务是started状态时,其生命周期与启动它的组件无关,并且可以在后台无限期运行,即使启动服务的组件已经被销毁。因此,服务需要在完成任务后调用stopSelf()方法停止,或者由其他组件调用stopService()方法停止。
(b)使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。
(3)开发人员需要在应用程序配置文件中声明全部的service,使用标签。
(4)Service通常位于后台运行,它一般不需要与用户交互,因此Service组件没有图形用户界面。Service组件需要继承Service基类。Service组件通常用于为其他组件提供后台服务或监控其他组件的运行状态。
3、content provider
(1)android平台提供了Content Provider使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据。
(2)只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中。它的好处是统一数据访问方式。
(3)ContentProvider实现数据共享。ContentProvider用于保存和获取数据,并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为android没有提供所有应用共同访问的公共存储区。
(4)开发人员不会直接使用ContentProvider类的对象,大多数是通过ContentResolver对象实现对ContentProvider的操作。
(5)ContentProvider使用URI来唯一标识其数据集,这里的URI以content://作为前缀,表示该数据由ContentProvider来管理。
4、broadcast receiver
(1)你的应用可以使用它对外部事件进行过滤,只对感兴趣的外部事件(如当电话呼入时,或者数据网络可用时)进行接收并做出响应。广播接收器没有用户界面。然而,它们可以启动一个activity或serice来响应它们收到的信息,或者用NotificationManager来通知用户。通知可以用很多种方式来吸引用户的注意力,例如闪动背灯、震动、播放声音等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。
(2)广播接收者的注册有两种方法,分别是程序动态注册和AndroidManifest文件中进行静态注册。
(3)动态注册广播接收器特点是当用来注册的Activity关掉后,广播也就失效了。静态注册无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器也是打开着的。
(4)频率比较高的事件无法使用静态广播,如电池电量。
5、谈谈你对ContentProvider的理解。
ContentProvider为存储和获取数据提供统一的接口,可以在不同应用程序之间共享数据。
ContentProvider主要有以下优点:
1.ContentProvider提供了对底层数据存储方式的抽象。如底层可以采用SQLite方式存储数据,使用ContentProvider封装之后,即便底层换成XML存储也不会对上层应用代码产生影响。
2.Android框架中的一些类需要ContentProvider类型数据。如想让数据可以使用在SyncAdapter,Loader,CursorAdapter等类上,那么就需要为数据做一层ContentProvider封装。
3.ContentProvider为应用间的数据交互提供了一个安全的环境。它准许你把自己的应用数据根据需求开放给其它应用进行增、删、改、查,而不用担心直接开放数据库权限而带来的安全问题。
6、说说ContentProvider、ContentResolver、ContentObserver 之间的关系
ontentProvider——内容提供者, 在android中的作用是对外共享数据,也就是说你可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider 对你应用中的数据进行添删改查。
ContentResolver——内容解析者, 其作用是按照一定规则访问内容提供者的数据(其实就是调用内容提供者自定义的接口来操作它的数据)。ContentResolver resolver = getContext().getContentResolver(); uri = resolver.insert(uri, values);
ContentObserver——内容观察者,目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理,它类似于数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。
Activity之间的通信方式。
Intent
借助类的静态变量
借助全局变量/Application
借助外部工具
– 借助SharedPreference
– 使用Android数据库SQLite
– 赤裸裸的使用File
– Android剪切板
借助Service Binder
进程间通讯
一、使用 Intent (三大组件)
二、使用文件共享
三、使用 Messenger 通过server类似aidl
四、使用 AIDL
五、使用 ContentProvider
六、使用 Socket (点对点 ip+端口)
aidl机制:
aidl跟service区别就是一个远程(跨进程)、一个本地。都是实现IBinder接口。
client、serviceManager、service、IBinder。
Bundle机制:
Bundle经常使用在Activity之间或者线程间传递数据,传递的数据可以是boolean、byte、int、
long、float、double、string等基本类型或它们对应的数组,也可以是对象或对象数组。
当Bundle传递的是对象或对象数组时,必须实现Serializable或Parcelable接口。
Bundle 实际就是内部是一个map来存放数据对象,然后通过序列化与反序列号写读数据对象。
1、standard模式
2、singleTop模式
3、singleTask模式
4、singleInstance模式
1). Thread:Thread 是程序执行的最小单元,它是分配CPU的基本单位。可以用 Thread 来执行一些异步的操作。
2). Service:Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的 Service 是运行在主进程的 main 线程上的。如:onCreate,onStart 这些函数在被系统调用的时候都是在主进程的 main 线程上运行的。如果是RemoteService,那么对应的 Service 则是运行在独立进程的 main 线程上。因此请不要把 Service 理解成线程,它跟线程半毛钱的关系都没有!
既然这样,那么我们为什么要用 Service 呢?其实这跟 android 的系统机制有关,我们先拿 Thread 来说。Thread 的运行是独立于 Activity 的,也就是说当一个 Activity 被 finish 之后,如果你没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:当 Activity 被 finish 之后,你不再持有该 Thread 的引用。另一方面,你没有办法在不同的 Activity 中对同一 Thread 进行控制。
举个例子:如果你的 Thread 需要不停地隔一段时间就要连接服务器做某种同步的话,该 Thread 需要在 Activity 没有start的时候也在运行。这个时候当你 start 一个 Activity 就没有办法在该 Activity 里面控制之前创建的 Thread。因此你便需要创建并启动一个 Service ,在 Service 里面创建、运行并控制该 Thread,这样便解决了该问题(因为任何 Activity 都可以控制同一 Service,而系统也只会创建一个对应 Service 的实例)。
因此你可以把 Service 想象成一种消息服务,而你可以在任何有 Context 的地方调用 Context.startService、Context.stopService、Context.bindService,Context.unbindService,来控制它,你也可以在 Service 里注册 BroadcastReceiver,在其他地方通过发送 broadcast 来控制它,当然这些都是 Thread 做不到的。
原理即 一个static的单例对象,成员变量是一个List等容器==> 注册时 用来存放观察者(List.add)。发送时 用来通知观察者。
通过 Android 系统的 Binder 机制实现通信。分有序广播与无序广播。
本地广播和全局广播有什么差别?
1、本地广播:发送的广播事件不被其他应用程序获取,也不能响应其他应用程序发送的广播事件。本地广播只能被动态注册,不能静态注册。动态注册或方法时需要用到LocalBroadcastManager。
2、全局广播:发送的广播事件可被其他应用程序获取,也能响应其他应用程序发送的广播事件(可以通过 exported–是否监听其他应用程序发送的广播 在清单文件中控制) 全局广播既可以动态注册,也可以静态注册。
AlertDialog是非阻塞式对话框:AlertDialog弹出时,后台还可以做事情;而PopupWindow是阻塞式对话框:PopupWindow弹出时,程序会等待,在PopupWindow退出前,程序一直等待,只有当我们调用了dismiss方法的后,PopupWindow退出,程序才会向下执行。
注意事项
(1、构造方法,2、onMeasure测量,3、layout布局自己,4、onLayout布局子view,5、onDraw绘画)
1.尽量不要在View中使用Handler。(View中已经提供了post系列方法,完全可以替代Handler的作用。)
2.View中如果有线程或者动画,需要及时停止。
3.View带有滑动嵌套时,需要处理好滑动冲突问题。
4.在View的onDraw方法中不要创建太多的临时对象,也就是new出来的对象。因为onDraw方法会被频繁调用,如果有大量的临时对象,就会引起内存抖动,影响View的效果。
- onDraw方法绘制其实是绘制到绑定的bitmap上面。
Canvas canvas = new Canvas(bitmap);
或者canvas.drawBitmap(bitmap,0,0,null);
View刷新机制
1.界面上任何一个 View 的刷新请求最终都会走到 ViewRootImpl 中的 scheduleTraversals() 里来安排一次遍历绘制 View 树的任务(标记出需要刷新的view)。
2.doTraversal() 方法会调用performTraversals()开始根据当前状态判断是否需要执行performMeasure()测量、perfromLayout() 布局、performDraw()绘制流程,在这几个流程中都会去遍历View树来刷新需要更新的View,最终完成View刷新
a、invalidate:方法会执行draw过程,重绘View树。invalidate(int l, int t, int r, int b) 可定制绘制区域。
b、requestLayout:View执行requestLayout方法,会向上递归到顶级父View中,再执行这个顶级父View的requestLayout,所以其他View的onMeasure,onLayout也可能会被调用。
c、postInvalidate:通过事件分发分发给handler。
d、requestFocus:请求获取焦点。可以传入事件类型(比如,down事件)
优化策略
1、为了加速你的view,对于频繁调用的方法,需要尽量减少不必要的代码。先从onDraw开始,需要特别注意不应该在这里做内存分配的事情,因为它会导致GC,从而导致卡顿。在初始化或者动画间隙期间做分配内存的动作。不要在动画正在执行的时候做内存分配的事情。
2、还需要尽可能的减少onDraw被调用的次数,大多数时候导致onDraw都是因为调用了invalidate().因此请尽量减少调用invaildate()的次数。如果可能的话,尽量调用含有4个参数的invalidate()方法而不是没有参数的invalidate()。没有参数的invalidate会强制重绘整个view。
3、另外一个非常耗时的操作是请求layout。任何时候执行requestLayout(),会使得Android UI系统去遍历整个View的层级来计算出每一个view的大小。如果找到有冲突的值,它会需要重新计算好几次。另外需要尽量保持View的层级是扁平化的,这样对提高效率很有帮助。
4、如果你有一个复杂的UI,你应该考虑写一个自定义的ViewGroup来执行他的layout操作。与内置的view不同,自定义的view可以使得程序仅仅测量这一部分,这避免了遍历整个view的层级结构来计算大小。这个PieChart
Requestlayout,onlayout,onDraw,DrawChild区别与联系。
RequestLayout()方法 :子View调用requestLayout方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,ViewRootImpl会调用三大流程,从measure开始,对于每一个含有标记位的view及其子View都会进行测量、布局、绘制。requestLayout如果没有改变l,t,r,b,那就不会触发onDraw;但是如果这次刷新是在动画里,mDirty非空,就会导致onDraw。
invalidate() 只执行自身draw方法。
onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局)。
调用onDraw()方法绘制视图本身。
drawChild()去重新回调每个子视图的draw()方法。
1. SurfaceView允许其他线程更新视图对象(执行绘制方法)而View不允许这么做,它只允许UI线程更新视图对象。
2. SurfaceView是放在其他最底层的视图层次中,所有其他视图层都在它上面,所以在它之上可以添加一些层,而且它不能是透明的。
3. 它执行动画的效率比View高,而且你可以控制帧数。
4. SurfaceView在绘图时使用了双缓冲机制,而View没有。
Parcelable和Serializable都是实现序列化并且都可以用于Intent间传递数据,Serializable是Java的实现方式,可能会频繁的IO操作,所以消耗比较大,但是实现方式简单 Parcelable是Android提供的方式,效率比较高,但是实现起来复杂一些 , 二者的选取规则是:内存序列化上选择Parcelable, 存储到设备或者网络传输上选择Serializable(当然Parcelable也可以但是稍显复杂)
通过反射读写bean可以优化复杂的实现方式。
补间动画只是改变了View的显示效果而已,并不会真正的改变View的属性。而属性动画可以改变View的显示效果和属性。举个例子:例如屏幕左上角有一个Button按钮,使用补间动画将其移动到右下角,此刻你去点击右下角的Button,它是绝对不会响应点击事件的,因此其作用区域依然还在左上角。只不过是补间动画将其绘制在右下角而已,而属性动画则不会。
1.启动类加载器:这个类加载器负责放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库。用户无法直接使用。
2.扩展类加载器:这个类加载器由sun.misc.Launcher$AppClassLoader实现。它负责<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。用户可以直接使用。
3.应用程序类加载器:这个类由sun.misc.Launcher$AppClassLoader实现。是ClassLoader中getSystemClassLoader()方法的返回值。它负责用户路径(ClassPath)所指定的类库。用户可以直接使用。如果用户没有自己定义类加载器,默认使用这个。
4.自定义加载器:用户自己定义的类加载器。
类加载过程
1、耗时操作本身并不会导致主线程卡死, 导致主线程卡死的真正原因是耗时操作之后的触屏操作, 没有在规定的时间内被分发。
2、Looper 中的 loop()方法, 他的作用就是从消息队列MessageQueue 中不断地取消息, 然后将事件分发出去
是指通过函数参数传递到其它代码的,某一块可执行代码的引用。这一设计允许了底层代码调用在高层定义的子程序。在Java里面,我们使用接口来实现回调。
HandlerThread:
HandlerThread继承于Thread,所以它本质就是个Thread。与普通Thread的差别就在于,然后在内部直接实现了Looper的实现,这是Handler消息机制必不可少的。有了自己的looper,可以让我们在自己的线程中分发和处理消息。如果不用HandlerThread的话,需要手动去调用Looper.prepare()和Looper.loop()这些方法。
ThreadLocal原理:
本质上就是一个变量。一个存放对象的变量有get()与set()方法可实现同步效果。
因为ThreadLocal在每个线程中对该变量会创建一个 副本(counterparts),即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间不会相互影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。
但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。
onTouch方法优先级比onTouchEvent高,会先触发。假如onTouch方法返回false,会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。
setOnTouchListener与dispatchTouchEvent关系
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
如果设置了setOnTouchListener,dispatchTouchEvent会将事件发给OnTouchListener。
击事件被拦截,但是想传到下面的View,如何操作?
requestDisallowInterceptTouchEvent()
当访问UI时,ViewRoot会调用checkThread方法检查当前访问UI的线程是哪个,如果不是UI线程则会抛出异常。ViewRootImpl的创建是在onResume方法回调之后,而我们一开篇是在onCreate方法中创建子线程并访问UI,在那个时刻,ViewRootImpl还没有来得及创建,无法检测当前线程是否是UI线程。
Android的UI访问是没有加锁的,多个线程可以同时访问更新操作同一个UI控件。也就是说访问UI的时候,android系统当中的控件都不是线程安全的,这将导致在多线程模式下,当多个线程共同访问更新操作同一个UI控件时容易发生不可控的错误,而这是致命的。所以Android中规定只能在UI线程中访问UI
什么是Java注解
注解,也叫元数据。一种代码级别的说明,在JDK1.5之后引入的特性,与类、接口、枚举同一层次。可以声明在包、类、字段、方法、局部变量、方法参数等前面,来对这些元素进行说明,注释等。
1、根据反射的测试的问题,引出@Retention元注解的讲解:其三种取值:RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME分别对应:Java源文件(.java文件)---->.class文件---->内存中的字节码
2、EventBus 见七。
class.isAnnotationPresent(Kevin.class)// 是否是Kevin.class注解
Kevin kevin = (Kevin) c.getAnnotation(Kevin.class);//得到注解类Kevin
3、注解枚举
public class FileType {
public final String fileType;
public static final String TYPE_MUSIC = "mp3";
public static final String TYPE_PHOTO = "png";
public static final String TYPE_TEXT = "txt";
//Retention 是元注解,简单地讲就是系统提供的,用于定义注解的“注解”
@Retention(RetentionPolicy.SOURCE)
@StringDef({TYPE_MUSIC, TYPE_PHOTO, TYPE_TEXT})
@interface FileTypeDef {
}
public FileType(@FileTypeDef String fileType) {
this.fileType = fileType;
}
}
什么是依赖注入
依赖注入是这样的一种行为,在类Car中不主动创建GasEnergy的对象,而是通过外部传入GasEnergy对象形式来设置依赖。(其实就是接口化后进一步接口化)
1、DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常.
<variable
name="mapData"<!-- 这里是变量名字 -->
type="HashMap<String,String>" /> <!-- 这里是类型<需要用<转义 -->
双向绑定
打包
1、打包资源文件,生成R.java文件。
2、处理aidl文件,生成相应的Java文件。
3、编译项目源代码,生成class文件。
4、转换所有的class文件,生成classes.dex文件。
5、打包生成APK文件。
6、对APK文件进行签名。
7、对签名后的APK文件进行对齐处理。
应用程序安装到手机上时发生了什么
1、拷贝文件到手机。2、编译为机器码。3、发送广播。4、桌面app更新。
安卓签名:
1.数据摘要。(分块摘要,分为1M大小,返回一个map)
2.对内容摘要进行签名,并生成签名块。(用签名文件和证书对摘要签名。)
3。将签名块添加到原APK文件内容中。
gradle打包流程:
-
aapt。 为 res 目录下的资源生成 R.java 文件,Manifest.java 文件 。
-
aidl 。 把项目中自定义的 aid!文件生成相应的 Java 代码文件 。
-
javac 。 把项目中所有的 Java 代码编译成 class 文件 。 包括三部分 Java 代码,自己写
的业务逻辑代码, aapt 生成的 Java 文件, aidl 生成的 Java 文件 。
-
proguard。 1昆淆同时生成 proguardMapping. txt 。 这一步是可选的 。
-
dex 。 把所有的 class 文件(包括第三方库的 class 文件)转换为 dex 文件 。
-
aapt。 还是使用 aapt,这里使用它的另 一个功能,即打包,把 res 目录下的资源、
assets 目录下的文件,打包成一个 .ap一文件 。
7 ) apkbuilder。 将所有的 dex 文件 、 ap 文件、 AndroidManifest且nl 打包为 .apk 文件,
这是一个未签名的 apk 包 。
8 ) jarsigner。 对 apk 进行签名
- zipalign 。 对要发布的 apk 文件进行对齐操作,以便在运行时节省内存 。
沙箱是为app提供隔离环境的一种安全机制,严格控制执行的程序所访问的资源,以确保系统的安全,让app在独立的进程中执行任务,让其不能访问外部进程的资源,这样一个应用出问题了,其他的应用进程能够保证不被影响。
应用程序在应用层的AndroidManifest.xml中所申请的权限将会在Android系统启动时,经过解析后,逐步映射到内核层的组ID和用户ID,最终由内核层的setgid()和setuid()函数设置后才能执行;在进行权限申请时,6.0以上需要动态代码申请,6.0以下直接在AndroidManifest.xml注册即可。
1.(模块化的特点是:模块之间解耦,可以独立管理。)模块化是将一个程序按照其功能做拆分,分成相互独立的模块。
2.(每个模块可独立编译,提高了编译速度;)将一个app的代码拆分成几份独立的组件,组件之间是低耦合的,可以独立编译打包;也可以将组件打包到一个apk中。
3.(形式上的区别,组件化的单位是module,插件化的单位是apk。关注点不同,插件化更关注动态加载、热更新、热修复等‘插拔’技术。)插件化是将一个apk根据业务功能拆分成不同的子apk(也就是不同的插件),每个子apk可以独立编译打包,最终发布上线的是集成后的apk。在apk使用时,每个插件是动态加载的,插件也可以进行热修复和热更新。
Tinker流程与原理:
- 新dex与旧dex通过dex差分算法生成差异包 patch.dex
- 将patch dex下发到客户端,客户端将patch dex与旧dex合成为新的全量dex
- 将合成后的全量dex 插入到dex elements前面(此部分和QQ空间机制类似),完成修复
插件化开发:
(包建强《任玉刚that和张勇》)(Apollo1.2资源冲突解决)
activity:定义一个代理activity;通过DexClassLoader获取到插件apk中的 DexClassLoader;通过反射获取到目标自定义Activity,付给代理activity;通过设置代理建立双向引用(也就是设置that与插件包);然后启动目标Activity。(1.5)
server:定义一个代理service;收到启动服务是获取目标类,通过反射在主线程中创建目标service对象;调用目标service相应方法。(ServiceHook4)
broadcast:定义一个代理broadcast;通过DexClassLoader获取到插件apk中的 ClassLoader;通过反射获取到目标自定义broadcast,付给代理broadcast;通过设置代理建立双向引用(也就是设置that);然后启动目标broadcast。(3.4)
provide:定义一个代理provide; ContentProvider2
1.命令 adb shell am start -W com.tangzy.tzymvp/com.tangzy.tzymvp.MainActivity
WaitTime 返回从 startActivity 到应用第一帧完全显示这段时间. 就是总的耗时,包括前一个应用 Activity pause 的时间和新应用启动的时间;
ThisTime 表示一连串启动 Activity 的最后一个 Activity 的启动耗时;
TotalTime 表示新应用启动的耗时,包括新进程的启动和 Activity 的启动,但不包括前一个应用Activity pause的耗时。
2. Application的attachBaseContext()与第一个activity的onWindowFocusChanged()时间差。
第一种方式更加准确。
1.性能分析工具
1.1Profile GPU Rendering 卡顿检测工具,手机自带(开发者模式下)
蓝色部分:代表创建和更新视图的显示列表的时间,如果这个条变高,这里可能有许多自定义View的绘制,或者在onDraw()方法中执行大量的工作;
紫色部分:Android4.0或者更高,代表花费传输资源到渲染线程所花费的时间;
红色部分:代表通过Android2D渲染器发出指令到OpenGL绘制和重绘显示所花费的时间。这个条的高度直接和每次显示列表执行花费的时间呈正比,更多的显示列表意味着更长的红条;
橙色部分:代表CPU等待GPU完成它的工作的时间,如果这个条变高,它以为着应用在GPU上执行了大量的工作;
2.布局优化(1.避免过渡绘制。2.优化布局层级。)
3.内存优化 (1.bitmap使用。2.io流,游标即使关闭。3.数据结构。)
对象引用。强引用、软引用、弱引用、虚引用四种引用类型,根据业务需求合理使用不同,选择不同的引用类型。
减少不必要的内存开销。注意自动装箱,增加内存复用,比如有效利用系统自带的资源、视图复用、对象池、Bitmap对象的复用。
使用最优的数据类型。比如针对数据类容器结构,可以使用ArrayMap数据结构,避免使用枚举类型,使用缓存Lrucache等等。
图片内存优化。可以设置位图规格,根据采样因子做压缩,用一些图片缓存方式对图片进行管理等等。
强引用置为null,会不会被回收
不会立即,因为有些对象是可恢复的,比如在 finalize方法中恢复引用 ,所以这个不用我们管,JVM处理
4.稳定性优化
提高代码质量。比如开发期间的代码审核,看些代码设计逻辑,业务合理性等。
代码静态扫描工具。常见工具有Android Lint、Findbugs、Checkstyle、PMD等等。
Crash监控。把一些崩溃的信息,异常信息及时地记录下来,以便后续分析解决。
Crash上传机制。在Crash后,尽量先保存日志到本地,然后等下一次网络正常时再上传日志信息。
5.安装包大小优化
代码混淆。使用ProGuard代码混淆器工具,它包括压缩、优化、混淆等功能。
资源优化。比如使用Android Lint删除冗余资源,资源文件最少化等。
图片优化。比如利用AAPT工具对PNG格式的图片做压缩处理,降低图片色彩位数等。
避免重复功能的库,使用WebP图片格式等。
插件化。比如功能模块放在服务器上,按需下载,可以减少安装包大小。
1.匿名内部类容易内存泄漏。
(由于匿名内部类会隐式持有外部类的引用,因此会导致外部类无法被回收,最终导致内存泄漏。只有匿名内部类和非静态内部类会隐式持有外部类的引用。)(联想:成员内部类、局部内部类、匿名内部类和静态内部类。)
public interface MyInterface {
void doSomething();
}
public class MyRunable implements Runnable {
private WeakReference<MyInterface> mInterface;
public MyRunnable(MyInterface mInterface) {
this.mInterface = new WeakReference<>(mInterface);
}
@Override
public void run() {
mInterface.get().doSomething();
}
}
public class MyActivity extends Activity {
...
new Thread(new MyRunnable(new MyInterface() {
@Override
public void doSomething() {
//具体实现
}
})).start();
}
2.成员内部类:Handler
及时handler.removeCallbacksAndMessages(null);
3.单例造成的内存泄露
单例因为是静态方法,所以此时单例就有持有该 Activity 的强引用
private AppManager(Context context) {
this.context = context;
}
解决方法1:使用弱引用。2:如下
private AppManager(Context context) {
this.context = context.getApplicationContext();// 使用Application 的context
}
4.集合类
集合类添加元素后,仍引用着集合元素对象,导致该集合中的元素对象无法被回收,从而导致内存泄露,举个例子
static List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object obj = new Object();
objectList.add(obj);
obj = null;
}
解决方法:在集合元素使用之后从集合中删除,等所有元素都使用完之后,将集合置空
objectList.clear();
objectList = null;
5.其他的情况
网络、文件等流忘记关闭;手动注册广播时,退出时忘记 unregisterReceiver();Service 执行完后忘记 stopSelf();EventBus 等观察者模式的框架忘记手动解除注册;static 关键字修饰的成员变量;ListView 的 Item 泄露(初始化时缓存中没有View对象则convertView是null);WebView造成的泄露(,应该调用它的destory()函数来销毁它)。
改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再调用上述方法将其设为ImageView的 source。decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。 尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource直接使用图片路径来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。
Glide的设计微妙在于:
- Glide的生命周期绑定:可以控制图片的加载状态与当前页面的生命周期同步,使整个加载过程随着页面的状态而启动/恢复,停止,销毁。
- Glide的缓存设计:通过(三级缓存,Lru算法,Bitmap复用)对Resource进行缓存设计(按照图片大小缓存)。
- Glide的完整加载过程:采用Engine引擎类暴露了一系列方法供Request操作。
- Glide默认Bitmap格式是 RGB_565,内存开销小。
- 获取网络图片宽高
Glide.with(context)
.load(pathUrl)
.asBitmap()//Glide返回一个Bitmap对象
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap bitmap, GlideAnimation<? super Bitmap> glideAnimation) {
bitmap.getWidth()
bitmap.getHeight()
}
});
glide通过builder初始化内容有:网络加载线程池、本地硬盘加载线程池、动画线程池、内存计算器、连接器工厂。
String有final修饰。StringBuffer可变是因为StringBuffer为什么是可变类。因为StringBuffer的append方法的底层实现就是创建一个新的目标对象,然后将各个字符引用串接起来,这就是一个新的对象了,本质上就是栈多了一个变量,而堆上面并没有变化。
1.互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放
2.请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
3.不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用
4.循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。
避免死锁。
1、加锁顺序。(如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。)
2、加锁时限。
对象锁:当使用synchronized修饰类普通方法、this、代码块时,那么当前加锁的级别就是实例对象,当多个线程并发访问该对象的同步方法、同步代码块时,会进行同步。
类锁:当使用synchronized修饰类静态方法、类时,那么当前加锁的级别就是类,当多个线程并发访问该类(所有实例对象)的同步方法以及同步代码块时,会进行同步。
1、同步代码块
synchronized(obj){
//同步代码块
}
2、同步方法
public synchronized void draw(double drawAmount){
//
}
3、同步锁
//加锁
lock.lock();
//
lock.unlock();
synchronized的应用方式
方法锁(synchronized修饰方法时)
对象锁(synchronized修饰方法或代码块)
类锁(synchronized修饰静态的方法或者代码块或者一个类的class对象上)
Lock
1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
addInterceptor()离线的时候的缓存与addNetworkInterceptor()读取缓存设置,cache(new Cache())缓存大小、路径。
通过添加到工作队列,检查工作队列是否满了,如果满了则进入等待队列,检查一系列设置,开启线程执行,是否使用了缓存,检查缓存设置,是读取缓存返回,否则请求网络数据。
什么是http协议
协议是指计算机通信网络中两台计算机之间进行通信所必须共同遵守的规定或规则,超文本传输协议(HTTP)是一种通信协议,它允许将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器
从下到上:物理层->数据链路层->网络层->传输层->应用层
HTTP工作原理
HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据
1、客户端连接到Web服务器
2、发送HTTP请求
3、服务器接受请求并返回HTTP响应
4、释放连接TCP连接
5、客户端浏览器解析HTML内容
https证书:
1.证书包含
- 证书颁发机构
- 证书颁发机构签名
- 证书绑定的服务器域名
- 证书版本、有效期
- 签名使用的加密算法(非对称算法,如RSA)
- 公钥
HTTP与HTTPS的区别以及如何实现安全性?
公钥加密、私钥解密。通过三次握手,服务器将公钥发给浏览器。浏览器用公钥加密自己生成的工作密钥,发送给服务器。服务器用自己的私钥解密浏览器发来的密钥,得到浏览器的工作密钥,用浏览器的工作密钥加密消息发送给浏览器。
**验证证书的合法性 : **客户端请求服务端通讯,服务端下发证书,客户端如果有公钥责用自己的公钥去验证证书签名(不会被中间人攻击),如果没有责使用证书中公钥(可能被代理攻击)。
1、允许连接到同一个主机地址的所有请求共享Socket。这必然会提高请求效率。
2、GZip透明压缩减少传输的数据包大小。
3、响应缓存,避免同一个重复的网络请求。
4、线程池技术。
5、反向代理模式。
6、okhttp 拦截器。(对server返回的response进行拦截,可打印日志、修改内容等)
默认配置:5个最多启动线程数,64个排队等候。0个核心线程数。
泛型是Java 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型变量、泛型接口、泛型方法。
1.泛型
//泛型类。
ArrayList<E>
//泛型变量;
private T x ;
//泛型接口
interface Info<T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
public void setVar(T x);
}
//泛型方法
public class StaticFans {
//静态函数
public static <T> void StaticMethod(T a){
Log.d("harvic","StaticMethod: "+a.toString());
}
//普通函数
public <T> void OtherMethod(T a){
Log.d("harvic","OtherMethod: "+a.toString());
}
}
//进阶:返回值中存在泛型
public <T> List<T> parseArray(String response,Class<T> object){
List<T> modelList = JSON.parseArray(response, object);
return modelList;
}
class Point<T>{// 此处可以随便写标识符号
private T x ;
private T y ;
public void setX(T x){//作为参数
this.x = x ;
}
public void setY(T y){
this.y = y ;
}
public T getX(){//作为返回值
return this.x ;
}
public T getY(){
return this.y ;
}
};
//IntegerPoint使用
Point<Integer> p = new Point<Integer>() ;
p.setX(new Integer(100)) ;
System.out.println(p.getX());
//FloatPoint使用
Point<Float> p = new Point<Float>() ;
p.setX(new Float(100.12f)) ;
System.out.println(p.getX());
2.类型擦除:泛型这种语法糖,编译器会在编译期间「擦除」泛型语法并相应的做出一些类型转换动作(使用边界类替换)。
public class Caculate { private T num; }
编译后
public class Caculate{ public Caculate(){} private Object num; }
2.泛型中extends和super的区别:
**extends** T> 只能用于方法返回,告诉编译器此**返参**的类型的**最小继承边界**为T,T和T的父类都能接收,但是入参类型无法确定,只能接受null的传入 **super** T>只能用于限定方法入参,告诉编译器**入参**只能是T或**其子类型**,而返参只能用Object类接收? 既不能用于入参也不能用于返参 ### 38、Java——run()方法和start()方法的区别 线程的run()方法是由java虚拟机直接调用的,如果我们没有启动线程(没有调用线程的start()方法)而是在应用代码中直接调用run()方法不会开启线程(就是一个普通的方法)。 ### 39、ListView **1、ListView的View重用机制**: ListView根据每页显示数量来决定缓存多少个View(n+1)。当手指滑动的时候根据layout来处理View的显示位置。 **2、ListView错位**:每当有新的元素进入界面时就会回调getView()方法,而在getView()方法中会开启异步请求从网络上获取图片,注意网络操作都是比较耗时的,也就是说当我们快速滑动ListView的时候就很有可能出现这样一种情况,某一个位置上的元素进入屏幕后开始从网络上请求图片,但是还没等图片下载完成,它就又被移出了屏幕 ### 40、RecycleView原理。 Adapter工作原理:首先是适配器,适配器的作用都是类似的,用于提供每个 item 视图,并返回给 RecyclerView 作为其子布局添加到内部。但是,与 ListView 不同的是,ListView 的适配器是直接返回一个 View,将这个 View 加入到 ListView 内部。而 RecyclerView 是返回一个 ViewHolder 并且不是直接将这个 holder 加入到视图内部,而是加入到一个缓存区域,在视图需要的时候去缓存区域找到 holder 再间接的找到 holder 包裹的 View。 ViewHolder:每个 ViewHolder 的内部是一个 View,并且 ViewHolder 必须继承自RecyclerView.ViewHolder类。 这主要是因为 RecyclerView 内部的缓存结构并不是像 ListView 那样去缓存一个 View,而是直接缓存一个 ViewHolder ,在 ViewHolder 的内部又持有了一个 View。既然是缓存一个 ViewHolder,那么当然就必须所有的 ViewHolder 都继承同一个类才能做到了。 cache :RecyclerView 的内部维护了一个二级缓存,滑出界面的 ViewHolder 会暂时放到 cache 结构中,而从 cache 结构中移除的 ViewHolder,则会放到一个叫做 RecycledViewPool 的循环缓存池中。顺带一说,RecycledView 的性能并不比 ListView 要好多少,它最大的优势在于其扩展性。但是有一点,在 RecycledView 内部的这个第二级缓存池 RecycledViewPool 是可以被多个 RecyclerView 共用的,这一点比起直接缓存 View 的 ListView 就要高明了很多,但也正是因为需要被多个 RecyclerView 公用,所以我们的 ViewHolder 必须继承自同一个基类(即RecyclerView.ViewHolder)。默认的情况下,cache 缓存 2 个 holder,RecycledViewPool 缓存 5 个 holder。对于二级缓存池中的 holder 对象,会根据 viewType 进行分类,不同类型的 viewType 之间互不影响。 **1、mChangedScrap**:存放发生改变的ViewHolder对象。 **2、mCachedViews**:存放remove调的视图的VIewHolder对象(保存的信息、绑定的数据未删除),容量有限制默认是2。 **3、mRecyclerPool**:存放remove调的视图的VIewHolder对象(完全删除信息、数据等)所有recycleview共享。 4、存放自定义缓存。 **RecyclerView 如何添加头、脚以及间隔线。** ![img](data\qq432337FEDCB6EB5443353EB50AE37219\e82a48a88742436ba984c1a3d665c516\clipboard.png) ![img](data\qq432337FEDCB6EB5443353EB50AE37219\91caccb1e85b4a14a9cb4071424a4153\clipboard.png) ### 41、启动页白屏及黑屏解决? 给SplashActivity设置主题 ```java true true @drawable/layer_splash2。主题设置背景图片,背景图片
<bitmap android:gravity="top|center_horizontal" android:scaleType="center" android:src="@drawable/splash_top" />
<bitmap android:gravity="bottom|center_horizontal" android:scaleType="center" android:src="@drawable/splash_bottom" />
### 42、Fragment 懒加载
![img](data\qq432337FEDCB6EB5443353EB50AE37219\caa3a59492ab497f937de53a8001b18f\clipboard.png)
### 43、viewPager 数据刷新
POSITION_NONE :意思是如果item的位置如果没有发生变化,则返回POSITION_UNCHANGED。如果返回了POSITION_NONE,表示该位置的item已经不存在了。默认的实现是假设item的位置永远不会发生变化,而返回POSITION_UNCHANGED
### 44、**双亲委派模型**
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。
双亲委派模型的工作过程是:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。
每一个层次的类加载器都是如此。因此,所有的加载请求最终都应该传送到顶层的启动类加载器中。
只有当父加载器反馈自己无法完成这个加载请求时(搜索范围中没有找到所需的类),子加载器才会尝试自己去加载。
### 45、说说你对Java反射的理解
简单的说,反射机制就是在程序的运行过程中被允许对程序本身进行操作,比如自我检查,进行装载,还可以获取类本身,类的所有成员变量和方法,类的对象,还可以在运行过程中动态的创建类的实例,通过实例来调用类的方法,这就是反射机制一个比较重要的功能了
### 46、Java内存模型
Java内存模型定义了一种多线程访问Java内存的规范。Java内存模型要完整讲不是这里几句话能说清楚的,我简单总结一下Java内存模型的几部分内容:
![image-20191217164659210](data\typora-user-images\image-20191217164659210.png)
(1)Java内存模型将内存分为了**主内存和工作内存**。类的状态,也就是类之间共享的变量,是存储在主内存中的,每次Java线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在自己的工作内存中有一份拷贝,运行自己线程代码的时候,用到这些变量,操作的都是自己工作内存中的那一份。在线程代码执行完毕之后,会将最新的值更新到主内存中去
(2)定义了几个原子操作,用于操作主内存和工作内存中的变量
(3)定义了volatile变量的使用规则
(4)happens-before,即先行发生原则,定义了操作A必然先行发生于操作B的一些规则,比如在同一个线程内控制流前面的代码一定先行发生于控制流后面的代码、一个释放锁unlock的动作一定先行发生于后面对于同一个锁进行锁定lock的动作等等,只要符合这些规则,则不需要额外做同步措施,如果某段代码不符合所有的happens-before规则,则这段代码一定是线程非安全的
**原子性:**由Java内存模型来直接保证原子性的变量操作包括read、load、use、assign(赋值)、store、write这6个动作。
**可见性:valatile**类型的变量保证对所有线程的可见性(立即同步给主内存)。
**有序性:**保证有序性的关键字有volatile和synchronized,volatile禁止了指令重排序,而synchronized则由“一个变量在同一时刻只能被一个线程对其进行lock操作”来保证。
### 47、 Java内存结构(java运行时数据区域)
![image-20191219105648132](data\typora-user-images\image-20191219105648132.png)
![](data\typora-user-images\image-20191217141932232.png)
### 48、jvm程序运行时,有六个地方都可以保存数据:
1、 **寄存器**:这是最快的保存区域,因为它位于和其他所有保存方式不同的地方:处理器内部。然而,寄存器的数量十分有限,所以寄存器是根据需要由编译器分配。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。
2、 **栈**:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中(new 出来的对象)。驻留于常规RAM(随机访问存储器)区域。但可通过它的“堆栈指针”获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。创建程序时,java编译器必须准确地知道堆栈内保存的所有数据的“长度”以及“存在时间”。这是由于它必须生成相应的代码,以便向上和向下移动指针。这一限制无疑影响了程序的灵活性,所以尽管有些java数据要保存在堆栈里——特别是对象句柄,但java对象并不放到其中。(**由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。**)
3、 **堆**:存放用new产生的数据。一种常规用途的内存池(也在RAM区域),其中保存了java对象。和堆栈不同:“内存堆”或“堆”最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间。因此,用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需用new命令编制相碰的代码即可。执行这些代码时,会在堆里自动进行数据的保存。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间。(**一般由程序员分配释放, 若程序员不释放,JVM不定时观察发现没有引用指向时会GC回收 。(所有线程共享的一块内存区域)**)
4、**方法区**(静态区): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等
数据。(**所有线程共享的一块内存区域**)
5、 **非RAM存储**:硬盘等永久存储空间。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是“流式对象”和“固定对象”。对于流式对象,对象会变成字节流,通常会发给另一台机器,而对于固定对象,对象保存在磁盘中。即使程序中止运行,它们仍可保持自己的状态不变。对于这些类型的数据存储,一个特别有用的技艺就是它们能存在于其他媒体中,一旦需要,甚至能将它们恢复成普通的、基于RAM的对象。
![image-20191217142937710](data\typora-user-images\image-20191217142937710.png)
### 49、JVM的垃圾回收器介绍
分代回收算法
在我们的经验之中,大部分的对象在生成之后就会马上变成垃圾,很少有对象能够或很久。这个就是我们将对象分成新生代和老年代的依据。在不同的代采用不同的收集是算法,从而提高内存的利用率。
我们的分区一般如下图所示,一个生成空间,两个大小相等的幸存空间,一个老年代空间。而针对它们的回收,我们的叫法也不相同。
**新生代GC(占1/3)**:指发生在新生代的垃圾回收动作,因为Java对象大多都具备朝生夕灭的特点,所以minor GC发生得非常频繁,一般回收速度也比较块。(**使用 复制算法**)
**老年代GC(占2/3)**:指发生在老年代的GC,它的速度会比minor GC慢很多。(**标记整理算法**)
![image-20191217172607355](data\typora-user-images\image-20191217172607355.png)
### 50、设计模式
##### 1、工厂
简单工厂
![img](data\qq432337FEDCB6EB5443353EB50AE37219\d9a05b2252784c60883a0aa47e1bf429\001.jpg)
工厂方法模式(抽象工厂只有一个抽象产品方法)
![img](data\qq432337FEDCB6EB5443353EB50AE37219\56a0403d61db48a6b7707ef7ad037e83\002.jpg)
抽象工厂(抽象工厂有多个抽象产品方法)
![img](data\qq432337FEDCB6EB5443353EB50AE37219\61915a129fc04e87b0155f92b30b0136\003.jpg)
##### 2、单例
懒汉
```java
class Singleton{
private static Singleton single;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(single==null)
single =new Singleton();
return single;
}
}
枚举
enum Singleton{
INSTANCE;
}
有点类似代理模式和状态模式(对一些工具的封装,比如加解密框架)
//容器
public class Context {
Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
//上下文接口
public void contextInterface() {
strategy.algorithmInterface();
}
}
//2. 策略角色
public abstract class Strategy {
//算法方法
public abstract void algorithmInterface();
}
//3. 具体策略角色
public class ConcreteStrategyA extends Strategy {
@Override
public void algorithmInterface() {
System.out.println("算法A实现");
}
}
//实现
public class Client {
public static void main(String[] args) {
Context context;
context = new Context(new ConcreteStrategyA());
context.contextInterface();
context = new Context(new ConcreteStrategyB());
context.contextInterface();
context = new Context(new ConcreteStrategyC());
context.contextInterface();
}
}
处理人员判断是否是自己负责的事情,不是就丢给下一个人去处理。(拦截器)
public static void main(String[] args){
//组装责任链
Handler handler1=new ConcreteHandler1();
Handler handler2=new ConcreteHandler2();
handler1.setNext(handler2);
//提交请求
handler1.handleRequest("two");
}
//抽象处理者角色
abstract class Handler{
private Handler next;
public void setNext(Handler next){
this.next=next;
}
public Handler getNext(){
return next;
}
//处理请求的方法
public abstract void handleRequest(String request);
}
//具体处理者角色1
class ConcreteHandler1 extends Handler{
public void handleRequest(String request){
if(request.equals("one")) {
System.out.println("具体处理者1负责处理该请求!");
}else{
if(getNext()!=null){
getNext().handleRequest(request);
}else{
System.out.println("没有人处理该请求!");
}
}
}
}
//具体处理者角色2
class ConcreteHandler2 extends Handler{
public void handleRequest(String request){
if(request.equals("two")){
System.out.println("具体处理者2负责处理该请求!");
}else{
if(getNext()!=null){
getNext().handleRequest(request);
}else{
System.out.println("没有人处理该请求!");
}
}
}
}
不同的状态去做不同的事情。(状态机)
就是将一个接口或类转换成其他的接口或类
//目标接口
interface Target{
public void request();
}
//适配者接口(源角色)
class Adaptee{
public void specificRequest(){
System.out.println("适配者中的业务代码被调用!");
}
}
//类适配器类
class ClassAdapter extends Adaptee implements Target{
public void request(){
specificRequest();
}
}
//客户端代码
public class ClassAdapterTest{
public static void main(String[] args){
System.out.println("类适配器模式测试:");
Target target = new ClassAdapter();
target.request();
}
}
起到中介隔离作用,开闭原则。
就是实现Cloneable接口复写clone方法。
//具体原型类
class Realizetype implements Cloneable{
Realizetype(){
System.out.println("具体原型创建成功!");
}
public Object clone() throws CloneNotSupportedException{
System.out.println("具体原型复制成功!");
return (Realizetype)super.clone();
}
}
//原型模式的测试类
public class PrototypeTest{
public static void main(String[] args)throws CloneNotSupportedException{
Realizetype obj1=new Realizetype();
Realizetype obj2=(Realizetype)obj1.clone();
System.out.println("obj1==obj2?"+(obj1==obj2));
}
}
类似工厂模式,只是不是相同的方法。
public abstract class SmartDevice {
//相关设备打开之后 使其进入准备状态
public abstract void readyState(String instruction);
//操作该设备
public abstract void operateDevice(String instruction, SmartMediator mediator);
}
//具体同事类1(窗帘设备)
public class CurtainDevice extends SmartDevice{
public void operateDevice(String instruction,SmartMediator mediator) {
System.out.println("窗帘已"+instruction);//通过传入指令,打开或关闭窗帘
mediator.curtain(instruction);//窗帘通过中介者唤醒音乐设备和洗浴设备
}
public void readyState(String instruction) {
//如果其他设备开启则调用此方法,唤醒窗帘
System.out.println("窗帘设备准备"+instruction);
}
}
//具体同事类2(音响设备)
public class MusicDevice extends SmartDevice{
public void operateDevice(String instruction,SmartMediator mediator) {
System.out.println("音乐设备已"+instruction);
mediator.music(instruction);
}
public void readyState(String instruction) {
System.out.println("音乐设备准备"+instruction);
}
}
//具体同事类3(洗浴设备)
public class BathDevice extends SmartDevice{
public void operateDevice(String instruction, SmartMediator mediator) {
System.out.println("洗浴设备"+instruction);
mediator.bath(instruction);
}
public void readyState(String instruction) {
System.out.println("洗浴设备正在准备"+instruction);
}
}
//抽象中介者(中介设备)
public abstract class SmartMediator {
//保留所有设备的引用是为了当接收指令时可以唤醒其他设备的操作
SmartDevice bd;
SmartDevice md;
SmartDevice cd;
public SmartMediator(SmartDevice bd, SmartDevice md, SmartDevice cd) {
super();
this.bd = bd;
this.md = md;
this.cd = cd;
}
public abstract void music(String instruction);
public abstract void curtain(String instruction);
public abstract void bath(String instruction);
}
//具体中介者
public class ConcreteMediator extends SmartMediator{
public ConcreteMediator(SmartDevice bd, SmartDevice cd, SmartDevice md) {
super(bd, cd, md);
}
public void music(String instruction) {//音乐被唤醒后,使其他设备进入准备状态
cd.readyState(instruction);//调用窗帘的准备方法
bd.readyState(instruction);//调用洗浴设备的准备方法
}
public void curtain(String instruction) {
md.readyState(instruction);
bd.readyState(instruction);
}
public void bath(String instruction) {
cd.readyState(instruction);
md.readyState(instruction);
}
}
// 客户端
public class Client {
public static void main(String[] args) {
SmartDevice bd=new BathDevice();
SmartDevice cd=new CurtainDevice();
SmartDevice md=new MusicDevice();
SmartMediator sm=new ConcreteMediator(bd, cd, md);//把设备引用都保存在调停者中
cd.operateDevice("open",sm); //开启窗帘
md.operateDevice("close",sm);//关闭音乐
}
}
1、代理:通过代理方法修改原方法的参数和返回值,从而实现某种不可告人的目的
/**
*接口类
**/
public interface Iuser {
void eat(String s);
void eat2(String s);
void eat2(String s, String s1);
}
/**
*实现类
**/
public class UserImpl implements Iuser {
@Override
public void eat(String s) {
System.out.println("我要吃"+s);
}
@Override
public void eat2(String s) {
System.out.println("我要吃2"+s);
}
@Override
public void eat2(String s, String s1) {
System.out.println("我要吃2"+s+" and "+s1);
}
}
/**
*代理类
**/
public class DynamicProxy implements InvocationHandler {
private Object object;//用于接收具体实现类的实例对象
//使用带参数的构造器来传递具体实现类的对象
public DynamicProxy(Object obj){
this.object = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
System.out.println("前置内容");
method.invoke(object, args);
System.out.println("args[0] = "+args[0]);//修改参数值
System.out.println("后置内容");
return null;
}
}
/**
*调用方法
**/
public void proxy(View view) {
Iuser user = new UserImpl();
InvocationHandler h = new DynamicProxy(user);
Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);
proxy.eat("苹果");
proxy.eat2("juzi");
proxy.eat2("123", "456");
}
2、Hook:HOOK模式可以在动态代理模式基础上实现;因为代理模式拦截所有。
/**
* hook的核心代码这个方法的唯一目的:用自己的点击事件,替换掉 View原来的点击事件
*
* @param v hook的范围仅限于这个view
*/
public static void hook(Context context, final View v) {//
try {
// 反射执行View类的getListenerInfo()方法,拿到v的mListenerInfo对象,这个对象就是点击事件的持有者
Method method = View.class.getDeclaredMethod("getListenerInfo");
method.setAccessible(true);//由于getListenerInfo()方法并不是public的,所以要加这个代码来保证访问权限
Object mListenerInfo = method.invoke(v);//这里拿到的就是mListenerInfo对象,也就是点击事件的持有者
//要从这里面拿到当前的点击事件对象
Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");// 这是内部类的表示方法
Field field = listenerInfoClz.getDeclaredField("mOnClickListener");
final View.OnClickListener onClickListenerInstance = (View.OnClickListener) field.get(mListenerInfo);//取得真实的mOnClickListener对象
//2. 创建我们自己的点击事件代理类
// 方式1:自己创建代理类
// ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
// 方式2:由于View.OnClickListener是一个接口,所以可以直接用动态代理模式
Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{View.OnClickListener.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d("HookSetOnClickListener", "点击事件被hook到了");//加入自己的逻辑
return method.invoke(onClickListenerInstance, args);//执行被代理的对象的逻辑
}
});
//3. 用我们自己的点击事件代理类,设置到"持有者"中
field.set(mListenerInfo, proxyOnClickListener);
//完成
} catch (Exception e) {
e.printStackTrace();
}
}
// 还真是这样,自定义代理类
static class ProxyOnClickListener implements View.OnClickListener {
View.OnClickListener oriLis;
public ProxyOnClickListener(View.OnClickListener oriLis) {
this.oriLis = oriLis;
}
@Override
public void onClick(View v) {
Log.d("HookSetOnClickListener", "点击事件被hook到了");
if (oriLis != null) {
oriLis.onClick(v);
}
}
}
1、ActivityManagerService
1)用户不可直接访问,管理四大组件;
2)用户通过ActivityManager获取运行中的应用信息,可以获取到servcie,process,app,memory,Task信息、清除用户数据、内存状态、应用是否在前台运行等。
2、WindowManagerService
1)用户不可直接访问,负责窗口的启动,添加,删除等。
2)用户通过WindowManager与之交互。
3、PackageManagerService
1)用户不可直接访问,管理着所有跟 package 相关的工作,常见的比如安装、卸载应用、权限等。
2)用户通过PackageManager与之交互。
HashMap:包含Node<K,V>[] table、int size、Set<Map.Entry<K,V>> entrySet。节点为Node的数组,Node节点包含hash, key, value, Node<K,V> next。哈希算法计算key的哈希值存放到相应位置。扩容系数是0.75,每次扩容到2倍(扩容后重新排列)。数据结构是数组与链表即ArrayList与LinkedList。只能有一个null。
HashSet:本质就是HashMap,将元素值作为HashMap的key。不能重复只能有一个null。
LinkedList:包含Node first、 Node last、int size。节点为Node的单项链表,Node阶段包含当前element、下一个节点、上一个节点。链表无需扩容
ArrayList:包含Object[] 、int size。每次扩容到1.5倍。
**SparseArray:**占用空间小,效率低下,GC成本巨大。
原理 : 链式的存储结构,存放观察者与被观察者。
内存泄漏: (AutoDispose、RxLifecycle解决内存泄漏)
.as(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(TzyActivity.this))) 使用简单。(AutoDispose原理就是的到调用者的生命周期)
**如何切换线程:**通过handle实现,主线程通过Looper.getMainLooper()得到。
操作符:
1、create:创建一个 Obserable 被观察者对象
2、map:map(new Function<Integer, String>() {}类型转换(将int转化为String)。
3、zip:zip(getStringObservable(), getInterObservable(), new BiFunction<String, Integer, String>() {}合并操作,将两个Observable(观察者)转换合并为一个。(先处理完前面的观察者的subscribe(),然后一个在调用一个后面的观察者的subscribe()去处理合并操作)
4、Concat:合并操作符。与zip不同的是合成后前面的在前,后面的跟着。
5、merge:并,注意它和 concat 的区别在于,不用等到 发射器 A 发送完所有的事件再进行发射器 B 的发送
6、ConcatMap:与flatMap一样,只是对第一步操作是有序的。
7、FlatMap:将数据转化类型,然后存入集合种,foreach取出处理。
8、Filter:过滤掉不符合条件的。
9、buffer:将大list分割成多个小list。
10、timer:延迟时间执行
11、interval:延迟执行,并可以设置时间间隔。
12、doOnNext:订阅者在接收到数据之前做一些操作
13、skip:代表跳过 count 个数目开始接收
14、take:指定订阅者最多收到多少数据
15、just:依次调用 onNext() 方法
16、Single:只接收一个参数。
17、debounce:小于多少毫秒的时间不处理。
18、last:仅仅取列表中的最后一个值
19、distinct:去重判断的是内容是否相等。
20、reduce:依次取出两个做作处理
21、scan:与reduce一样,只是每次处理都有结果输出。
22、window:按照时间划分窗口,将数据发送给不同的Observable
RxJava的功能与原理实现
首先,我们需要明确,RxJava是Reactive Programming在Java中的一种实现。什么是响应式编程? 用一个字来概括就是流(Stream)。Stream 就是一个按时间排序的 Events 序列,它可以放射三种不同的 Events:(某种类型的)Value、Error 或者一个” Completed” Signal。通过分别为 Value、Error、”Completed”定义事件处理函数,我们将会异步地捕获这些 Events。基于观察者模式,事件流将从上往下,从订阅源传递到观察者。
RxJava与平时使用的异步操作来比的优缺点
至于使用Rx框架的优点,它可以避免回调嵌套,更优雅地切换线程实现异步处理数据。配合一些操作符,可以让处理事件流的代码更加简洁,逻辑更加清晰。解耦了各个模块操作,单一化,不嵌套。
原理: 使用注解、反射接口化网络层与RXJava结合,链式化异步,泛型化参数 T create(Class service)。
- 通过WebView的addJavascriptInterface()进行对象映射 (有安全问题,效率低下)
- 通过evaluateJavascript() (4.4之后可用) 高效、可直接返回值。
- 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url
- 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息
1、冒泡算法
private void maopao(int[] array) {
int length = array.length;
int temp;
for (int i =0; i < length-1; i++){
for (int j = i+1; j < length; j++){
if (array[i] > array[j]){
temp = array[j];
array[j] = array[i];
array[i] = temp;
}
}
}
}
2、折半查找
private int zheban(int[] array, int i) {
int ritht = array.length;
int left = 0;
int middle = (ritht+left)/2;
Logger.d(TAG, "left = "+left+";ritht = "+ritht);
while (left < ritht){
middle = (ritht+left)/2;
if (array[middle] > i){
ritht = middle-1;
}else if (array[middle] < i){
left = middle +1;
}else {
return array[middle];
}
}
return 0;
}
3、快速排序
public class quickSort {
int a[]={49,38,65,97,76,13,27,49,78,34,12,64,5,4,62,99,98,54,56,17,18,23,34,15,35,25,53,51};
publicquickSort(){
quick(a);
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}
}
public int getMiddle(int[] list, int low, int high) {
int tmp =list[low]; //数组的第一个作为中轴
while (low < high){
while (low < high&& list[high] >= tmp) {
high--;
}
list[low] =list[high]; //比中轴小的记录移到低端
while (low < high&& list[low] <= tmp) {
low++;
}
list[high] =list[low]; //比中轴大的记录移到高端
}
list[low] = tmp; //中轴记录到尾
return low; //返回中轴的位置
}
public void _quickSort(int[] list, int low, int high) {
if (low < high){
int middle =getMiddle(list, low, high); //将list数组进行一分为二
_quickSort(list, low, middle - 1); //对低字表进行递归排序
_quickSort(list,middle + 1, high); //对高字表进行递归排序
}
}
public void quick(int[] a2) {
if (a2.length > 0) { //查看数组是否为空
_quickSort(a2,0, a2.length - 1);
}
}
}
1、定义declare-styleable
<resources>
<declare-styleable name="MyTextView">
<attr name=“text" format="string" />
</declare-styleable>
</resources>
1、首先设置setContentView,然后调用到AppCompatDelegateImpl的setContentView()方法,
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
2、获取xml控件转换为对象。
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
读取名字,属性等。然后反射创建一个对象(取名字),设置属性。
1、Launcher通知AMS启动目标Activity。
2、AMS得到Launcher的通知,就需要响应这个通知,主要就是新建一个Task去准备启动Activity,并且告诉Launcher你可以休息了(Paused)
3、Launcher得到AMS让自己“休息”的消息,那么就直接挂起,并告诉AMS我已经Paused了;
4、AMS知道了Launcher已经挂起之后,创建一个新进程(AMS通过Socket去和Zygote协商),新的进程会导入ActivityThread类,
5、进程创建好了,通过调用上述的ActivityThread的main方法,主线程默认绑定Looper。
6、App还没有启动完,要永远记住,四大组建的启动都需要AMS去启动,将上述的应用进程信息注册到AMS中,AMS再在堆栈顶部取得要启动的Activity,通过一系列链式调用去完成App启动;
1、加密apk文件:byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));//以二进制形式读出apk,并进行加密处理//对源Apk进行加密操作
2、合并文件:将加密之后的Apk和原脱壳Dex进行合并
3、在文件的末尾追加源程序Apk的长度:
4、修改新Dex文件的文件头信息:
主要步骤:
我们拿到需要加密的Apk和自己的壳程序Apk,然后用加密算法对源Apk进行加密在将壳Apk进行合并得到新的Dex文件,最后替换壳程序中的dex文件即可,得到新的Apk,那么这个新的Apk我们也叫作脱壳程序Apk.他已经不是一个完整意义上的Apk程序了,他的主要工作是:负责解密源Apk.然后加载Apk,让其正常运行起来。
HandlerThread;
object equals() 源码
webview子线程内存泄漏。
hashmap线程不安全原理
intentService 多个activity都屌用
布局优化框架。(检测)
okhttp拦截器原理
apm ActivityThread
jetpack
Aspect AOP
———-java类的初始化顺序———– 1、静态变量 2、静态代码块 3、main方法 4、类的属性 5、代码块 6、构造方法
——如果有父类则是:——— 1、父类–静态变量 2、父类–静态代码块 3、子类–静态变量 4、子类–静态代码块 5、父类–属性 6、父类–代码块 7、父类–构造方法 8、子类–属性 9、子类–代码块 10、子类–构造方法
注意: 1、如果Test类有静态代码块或静态属性,只有Test类所有静态变量与静态代码块都已经装载结束,才开始执行main()方法体 2、静态代码段只在类第一次加载时才执行
/* ————-final:最终——————-
- 1、用final修饰的变量是常量
- 2、用final修饰的引用类型(如对象)的值可变,但不可再new
- 3、用final修饰的方法不可再重写
- 4、用final修饰的类不可被继承 */ ———————————————— 版权声明:本文为CSDN博主「窗边冷月光」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/sinat_27403673/article/details/77444177
handler 的post与sendmessage区别
IdleHandler Toast原理
RxJava合并请求。
Dagger2
依赖注入
https://www.cnblogs.com/zdz8207/p/android-learning-2018.html
https://juejin.im/post/5de5176c6fb9a071b9711290
android插件化开发指南
热修复(基本都用微信的和美团的)
19.3增量更新
知道哪些混合开发的方式?说出它们的优缺点和各自使用场景?(解答:比如:RN,weex,H5,小程序,WPA等。做Android的了解一些前端js等还是很有好处的);
RxJava编程,拉姆达表达式。
实现一个Json解析器(可以通过正则提高速度)
自定义View如何提供获取View属性的接口?
绘制正玄余玄曲线。
(四)算法
排序算法有哪些?
最快的排序算法是哪个?
手写一个冒泡排序
手写快速排序代码
快速排序的过程、时间复杂度、空间复杂度
手写堆排序
堆排序过程、时间复杂度及空间复杂度
写出你所知道的排序算法及时空复杂度,稳定性
二叉树给出根节点和目标节点,找出从根节点到目标节点的路径
给阿里2万多名员工按年龄排序应该选择哪个算法?
GC算法(各种算法的优缺点以及应用场景)
蚁群算法与蒙特卡洛算法
子串包含问题(KMP 算法)写代码实现
一个无序,不重复数组,输出N个元素,使得N个元素的和相加为M,给出时间复杂度、空间复杂度。手写算法
万亿级别的两个URL文件A和B,如何求出A和B的差集C(提示:Bit映射->hash分组->多文件读写效率->磁盘寻址以及应用层面对寻址的优化)
百度POI中如何试下查找最近的商家功能(提示:坐标镜像+R树)。
两个不重复的数组集合中,求共同的元素。
两个不重复的数组集合中,这两个集合都是海量数据,内存中放不下,怎么求共同的元素?
一个文件中有100万个整数,由空格分开,在程序中判断用户输入的整数是否在此文件中。说出最优的方法
一张Bitmap所占内存以及内存占用的计算
2000万个整数,找出第五十大的数字?
烧一根不均匀的绳,从头烧到尾总共需要1个小时。现在有若干条材质相同的绳子,问如何用烧绳的方法来计时一个小时十五分钟呢?
求1000以内的水仙花数以及40亿以内的水仙花数
5枚硬币,2正3反如何划分为两堆然后通过翻转让两堆中正面向上的硬8币和反面向上的硬币个数相同
时针走一圈,时针分针重合几次
N*N的方格纸,里面有多少个正方形
x个苹果,一天只能吃一个、两个、或者三个,问多少天可以吃完?
-
翻转一个单项链表
-
合并多个单有序链表(假设都是递增的)
-
堆排序过程,时间复杂度,空间复杂度
-
快速排序的时间复杂度,空间复杂度
-
HashSet与HashMap怎么判断集合元素重复
-
逻辑地址与物理地址,为什么使用逻辑地址
-
一个无序,不重复数组,输出N个元素,使得N个元素的和相加为M,给出时间复杂度、空间复杂度。手写算法
-
二叉树,给出根节点和目标节点,找出从根节点到目标节点的路径
-
数据结构中堆的概念,堆排序
-
图:有向无环图的解释
-
LRUCache原理
-
ThreadLocal 原理
-
HashMap源码,SpareArray原理 (为什么是头插法)
-
Https请求慢的解决办法,DNS,携带数据,直接访问IP
-
https相关,如何验证证书的合法性,https中哪里用了对称加密,哪里用了非对称加密,对加密算法(如RSA)等是否有了解
-
是否熟悉Android jni开发,jni如何调用java层代码
-
Android系统为什么会设计ContentProvider,进程共享和线程安全问题
-
RxJava的功能与原理实现
-
适配器模式,装饰者模式,外观模式的异同?
-
静态内部类的设计意图。
-
多线程:怎么用、有什么问题要注意;Android线程有没有上限,然后提到线程池的上限
-
点击事件被拦截,但是想传到下面的view,如何操作?
-
Android中进程内存的分配,能不能自己分配定额内存
-
序列化的作用,以及 Android 两种序列化的区别。
-
前台切换到后台,然后再回到前台,Activity生命周期回调方法。弹出Dialog,生命值周期回调方法。
https://blog.csdn.net/LovelyProgrammer/article/details/79813353
MeasureSpec UNSPECIFIED、AT_MOST、AT_MOST https://blog.csdn.net/xiahao86/article/details/43372753
动画(插值器与估值器)
图片库对比
图片库的源码分析
视频开发