简述ARouter原理
ARouter是阿里巴巴开源的一款用于Android应用进行组件化改造的路由框架,它可以实现在同一个项目中各个模块之间的Activity跳转。ARouter的路由、参数传递和拦截器都是通过注解来进行标注的[1]。以下是ARouter原理的概述:
ARouter的原理主要包括以下几个方面:
- 注解处理器(Annotation Processor):ARouter使用注解处理器在编译时扫描和解析被注解标记的代码,并生成辅助类用于实现路由功能。注解处理器会解析被
@Route注解标记的类和方法,提取出相关的信息,例如路由路径、参数等。 - 路由表(Route Table):注解处理器会生成路由表,将被
@Route注解标记的类和方法信息存储在路由表中。路由表是ARouter在运行时进行路由查找和跳转的重要数据结构,它可以根据指定的路径找到对应的目标类和方法。 - 路由跳转:在应用运行时,通过ARouter的API接口,可以根据指定的路由路径进行跳转。ARouter会根据路由路径在路由表中查找目标类和方法,并使用反射机制创建实例或调用相应的方法。
- 参数传递:ARouter支持在路由跳转时传递参数,可以使用
withString()、withInt()等方法设置参数,并在目标类中通过注解@Autowired来接收参数。 - 拦截器(Interceptor):ARouter还支持拦截器的功能,可以在路由跳转前后进行拦截处理。拦截器可以用于权限验证、日志记录等操作。
总之,ARouter通过注解处理器生成路由表,在运行时实现路由跳转和参数传递的功能。它的设计思想是简化Android应用中组件间的通信和解耦,提供了方便、灵活的路由功能
请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系?
简单的说,Handler获取当前线程中的looper对象,looper用来从存放Message的MessageQueue中取出Message,再有Handler进行Message的分发和处理.
Message Queue(消息队列):用来存放通过Handler发布的消息,通常附属于某一个创建它的线程,可以通过Looper.myQueue()得到当前线程的消息队列
Handler:可以发布或者处理一个消息或者操作一个Runnable,通过Handler发布消息,消息将只会发送到与它关联的消息队列,然也只能处理该消息队列中的消息
Looper:是Handler和消息队列之间通讯桥梁,程序组件首先通过Handler把消息传递给Looper,Looper把消息放入队列。Looper也把消息队列里的消息广播给所有的
Handler:Handler接受到消息后调用handleMessage进行处理
Message:消息的类型,在Handler类中的handleMessage方法中得到单个的消息进行处理
在单线程模型下,为了线程通信问题,Android设计了一个Message Queue(消息队列), 线程间可以通过该Message Queue并结合Handler和Looper组件进行信息交换。下面将对它们进行分别介绍:
-
Message
Message消息,理解为线程间交流的信息,处理数据后台线程需要更新UI,则发送Message内含一些数据给UI线程。 -
Handler
Handler处理者,是Message的主要处理者,负责Message的发送,Message内容的执行处理。后台线程就是通过传进来的 Handler对象引用来sendMessage(Message)。而使用Handler,需要implement 该类的 handleMessage(Message)方法,它是处理这些Message的操作内容,例如Update UI。通常需要子类化Handler来实现handleMessage方法。 -
Message Queue
Message Queue消息队列,用来存放通过Handler发布的消息,按照先进先出执行。
每个message queue都会有一个对应的Handler。Handler会向message queue通过两种方法发送消息:sendMessage或post。这两种消息都会插在message queue队尾并按先进先出执行。但通过这两种方法发送的消息执行的方式略有不同:通过sendMessage发送的是一个message对象,会被 Handler的handleMessage()函数处理;而通过post方法发送的是一个runnable对象,则会自己执行。 -
Looper
Looper是每条线程里的Message Queue的管家。Android没有Global的Message Queue,而Android会自动替主线程(UI线程)建立Message Queue,但在子线程里并没有建立Message Queue。所以调用Looper.getMainLooper()得到的主线程的Looper不为NULL,但调用Looper.myLooper() 得到当前线程的Looper就有可能为NULL。对于子线程使用Looper,API Doc提供了正确的使用方法:
这个Message机制的大概流程:
- 在Looper.loop()方法运行开始后,循环地按照接收顺序取出Message Queue里面的非NULL的Message。
- 一开始Message Queue里面的Message都是NULL的。当Handler.sendMessage(Message)到Message Queue,该函数里面设置了那个Message对象的target属性是当前的Handler对象。随后Looper取出了那个Message,则调用 该Message的target指向的Hander的dispatchMessage函数对Message进行处理。在dispatchMessage方法里,如何处理Message则由用户指定,三个判断,优先级从高到低:
- Message里面的Callback,一个实现了Runnable接口的对象,其中run函数做处理工作;
- Handler里面的mCallback指向的一个实现了Callback接口的对象,由其handleMessage进行处理;
- 处理消息Handler对象对应的类继承并实现了其中handleMessage函数,通过这个实现的handleMessage函数处理消息。
由此可见,我们实现的handleMessage方法是优先级最低的!
- Handler处理完该Message (update UI) 后,Looper则设置该Message为NULL,以便回收!
在网上有很多文章讲述主线程和其他子线程如何交互,传送信息,最终谁来执行处理信息之类的,个人理解是最简单的方法——判断Handler对象里面的Looper对象是属于哪条线程的,则由该线程来执行! - 当Handler对象的构造函数的参数为空,则为当前所在线程的Looper;
- Looper.getMainLooper()得到的是主线程的Looper对象,Looper.myLooper()得到的是当前线程的Looper对象。
简述什么是观察者模式?
观察者模式又叫发布者订阅者模式,它定义了一对多的关系,让多个观察者对象同时监听某一个主体对象,这个主体对象发生变化时就会通知所有的观察者对象,使得他们能够自己更新自己
如:你订阅游戏主播,当主播开播的时候他就会给你推送开播消息;
使用观察者模式的好处:
1.1支持简单的广播通信,自动通知所有已订阅的对象。
1.2页面载入后目标元素容易和观察者者存在一种动态关联,增加了灵活性。
1.3目标对象与观察者之间的抽象耦合关系能够单独扩展及运用
请列举观察者模式应用场景 ?
1.当一个对象状态的改变需要改变其他对象, 或实际对象是事先未知的或动态变化的时, 可使用观察者模式。
2.当应用中的一些对象必须观察其他对象时, 可使用该模式。 但仅能在有限时间内或特定情况下使用
请叙述观察者模式有几种角色 ?
Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态
请Java代码实现观察者模式的案例 ?
抽象被观察者
//(抽象被观察者)接口, 让(具体观察者)WeatherData 来实现
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
具体被观察者 类是核心
- 包含最新的天气情况信息
- 含有 观察者集合,使用ArrayList管理
- 当数据有更新时,就主动的调用 ArrayList, 通知所有的(接入方)就看到最新的信息
///具体被观察者
public class WeatherData implements Subject {
private float temperatrue;
private float pressure;
private float humidity;
//观察者集合
private ArrayList observers;
//加入新的第三方
public WeatherData() {
observers = new ArrayList();
}
public float getTemperature() {
return temperatrue;
}
public float getPressure() {
return pressure;
}
public float getHumidity() {
return humidity;
}
public void dataChange() {
//调用 接入方的 update
notifyObservers();
}
//当数据有更新时,就调用 setData
public void setData(float temperature, float pressure, float humidity) {
this.temperatrue = temperature;
this.pressure = pressure;
this.humidity = humidity;
//调用dataChange, 将最新的信息 推送给 接入方 currentConditions
dataChange();
}
//注册一个观察者
@Override
public void registerObserver(Observer o) {
// TODO Auto-generated method stub
observers.add(o);
}
//移除一个观察者
@Override
public void removeObserver(Observer o) {
// TODO Auto-generated method stub
if(observers.contains(o)) {
observers.remove(o);
}
}
//遍历所有的观察者,并通知
@Override
public void notifyObservers() {
// TODO Auto-generated method stub
for (int i = 0; i < observers.size(); i++) {
observers.get(i).update(this.temperatrue, this.pressure, this.humidity);
}
}
}
抽象观察者
//抽象观察者接口,由具体观察者来实现()
public interface Observer {
public void update(float temperature, float pressure, float humidity);
}
具体观察者
//具体观察者
public class BaiduSite implements Observer {
// 温度,气压,湿度
private float temperature;
private float pressure;
private float humidity;
// 更新 天气情况,是由 WeatherData 来调用,我使用推送模式
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
// 显示
public void display() {
System.out.println("===百度网站====");
System.out.println("***百度网站 气温 : " + temperature + "***");
System.out.println("***百度网站 气压: " + pressure + "***");
System.out.println("***百度网站 湿度: " + humidity + "***");
}
}
//具体观察者
public class CurrentConditions implements Observer {
// 温度,气压,湿度
private float temperature;
private float pressure;
private float humidity;
// 更新 天气情况,是由 WeatherData 来调用,我使用推送模式
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
// 显示
public void display() {
System.out.println("***Today mTemperature: " + temperature + "***");
System.out.println("***Today mPressure: " + pressure + "***");
System.out.println("***Today mHumidity: " + humidity + "***");
}
}
测试
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建一个WeatherData
WeatherData weatherData = new WeatherData();
//创建观察者
CurrentConditions currentConditions = new CurrentConditions();
BaiduSite baiduSite = new BaiduSite();
//注册到weatherData
weatherData.registerObserver(currentConditions);
weatherData.registerObserver(baiduSite);
//测试
System.out.println("通知各个注册的观察者, 看看信息");
weatherData.setData(10f, 100f, 30.3f);
weatherData.removeObserver(currentConditions);
//测试
System.out.println();
System.out.println("通知各个注册的观察者, 看看信息");
weatherData.setData(10f, 100f, 30.3f);
}
对称加密与非对称加密有什么区别?
1、加密和解密过程不同
对称加密的加密过程和解密过程使用的同一个密钥,加密过程相当于用原文+密钥可以传输出密文,同时解密过程用密文-密钥可以推导出原文。
但非对称加密采用了两个密钥,一般使用公钥进行加密,使用私钥进行解密。
2、加密解密速度不同
对称加密解密的速度比较快,适合数据比较长时的使用。非对称加密和解密花费的时间长、速度相对较慢,只适合对少量数据的使用。
3、传输的安全性不同
对称加密的过程中无法确保密钥被安全传递,密文在传输过程中是可能被第三方截获的,如果密码本也被第三方截获,则传输的密码信息将被第三方破获,安全性相对较低。
非对称加密算法中私钥是基于不同的算法生成不同的随机数,私钥通过一定的加密算法推导出公钥,但私钥到公钥的推导过程是单向的,也就是说公钥无法反推导出私钥。所以安全性较高
简述什么是非对称加密技术 ?
与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。
公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
非对称加密算法实现机密信息交换的基本过程是:甲方生成一对密钥并将其中的一把作为公用密钥向其它方公开;得到该公用密钥的乙方使用该密钥对机密信息进行加密后再发送给甲方;甲方再用自己保存的另一把专用密钥对加密后的信息进行解密。甲方只能用其专用密钥解密由其公用密钥加密后的任何信息。
如何提升非对称加密的运行效率 ?
可以采用独立的硬件加密模块的方式进行,非对称对称加密使用的算法需要考虑大数乘法、大因数分解、大数模运算,在计算机中简单的是加法和减法,因此乘法本身就是有损耗;其次,算法对于秘钥的存储需要空间和算法的复杂度,也会影响非对称加密的运行效率;
使用量子计算和椭圆曲线算法来进行加密的方式,椭圆曲线算法涉及到几何学,因此比大数乘法、大因数分解、大数模运算的复杂度要较低;
简述在现有的计算能力条件下,对于非对称密码算法Elgamal,被认为是安全的最小密钥长度是 ?
A:128 B:256 C:512 D:1024
答案:D
简述Android跨进程通信方式 ?
(1):使用Bundle封装我们想要传递的数据,随后通过Intent发送出去即可,但是有一点需要注意,Bundle里面封装的数据必须是可以序列化的,也就是实现了Serializable或者Parcelable接口的类实例才可以传递;
(2):使用文件共享的方式实现,这种方式适合于数据同步要求不高的进程间通信,并且需要处理好并发读/写问题;
(3):使用Messenger信使,他底层是通过AIDL的方式实现的,具体可以查看他的两个构造方法:Messenger(Handler target)和Messenger(IBinder target);在服务端是通过第一个构造方法创建Messenger对象的,客户端是通过第二个构造方法创建Messenger对象的;
(4):使用Android为我们提供的AIDL文件来进行进程间通信,他的内部是通过Binder实现的;
(5):使用ContentProvider来实现进程间通信,他的内部实现也是Binder,我们可以将一个进程中想要共享给另一个进程的数据封装起 来,对外提供一个URI对象,如果某个应用程序想要获取这些数据的话,可以通过ContentProvider使用URI来进行获取,具体就是通过 ContentResolver来进行增删改查操作了;
(6):采用Socket通信的方式,当然Socket也可以适用于不同设备之间的通信,因为他是使用套接字(IP+端口号)实现的,但是Binder的话只能适用于同一设备中
Kotlin 协程中的 launch/join 和 async/await 有什么区别?
launch用于触发并忘记协程。这就像开始一个新线程。如果内部的代码因launch异常而终止,则将其视为线程中未捕获的异常——通常在后端 JVM 应用程序中打印到 stderr 并使 Android 应用程序崩溃。join用于等待启动的协程完成,并且不会传播其异常。但是,崩溃的子协程也会取消其父协程,并出现相应的异常。
async用于启动计算某些结果的协程。结果由 的实例表示Deferred,您必须在其上使用await。异步代码中未捕获的异常存储在结果Deferred中,不会传递到其他任何地方,除非处理,否则它将被静默丢弃。你一定不要忘记你用异步启动的协程。
重点简述Git 中 merge 和 rebase命令 的区别?
1.采用merge和rebase后,git log的区别,merge命令不会保留merge的分支的commit:
2.处理冲突的方式:
(一股脑)使用merge命令合并分支,解决完冲突,执行git add .和git commit -m'fix conflict'。这个时候会产生一个commit。
(交互式)使用rebase命令合并分支,解决完冲突,执行git add .和git rebase --continue,不会产生额外的commit。这样的好处是,‘干净’,分支上不会有无意义的解决分支的commit;坏处,如果合并的分支中存在多个commit,需要重复处理多次冲突。
3.git pull和git pull --rebase区别:git pull做了两个操作分别是‘获取’和合并。所以加了rebase就是以rebase的方式进行合并分支,默认为merge。
注意:只有在冲突的时候,解决完冲突才会自动产生一个commit。
如果想在没有冲突的情况下也自动生成一个commit,记录此次合并就可以用:git merge --no-ff命令,
如果不加 --no-ff 则被合并的分支之前的commit都会被抹去,只会保留一个解决冲突后的 merge commit。
简述Bitmap 的理解, 什么时候应该手动调用 bitmap.recycle() ?
Bitmap 是 android 中经常使用的一个类,它代表了一个图片资源。 Bitmap 消耗内存很严重,如果不注意优化代码,经常会出现 OOM问题,优化方式通常有这么几种:
1.使用缓存;
2.压缩图片;
3.及时回收;
至于什么时候需要手动调用 recycle,这就看具体场景了,原则是当我们不再使用 Bitmap 时,需要回收之。
另外,我们需要注意,2.3 之前 Bitmap 对象与像素数据是分开存放的,Bitmap 对象存在java Heap中而像素数据存放在Native Memory中, 这时很有必要调用recycle 回收内存。
但是 2.3之后,Bitmap对象和像素数据都是存在Heap 中,GC 可以回收其内存。
简述Bitmap导致OOM的原因知道吗?如何优化 ?
Android 图片加载Bitmap OOM错误解决办法 Android加载资源图片时,很容易出现OOM的错误。
因为Android系统 对内存有一个限制,如果超出该限制,就会出现OOM。为了避免这个问题,需要在加载资源时尽量考虑如何节约内存,尽快释放资源等等。
Android系统版本对图片加载,回收的影响:
1,在Android 2.3以及之后,采用的是并发回收机制,避免在回收内存时 的卡顿现象。
2,在Android 2.3.3(API Level 10)以及之前,Bitmap的backing pixel 数据存储在native memory, 与 Bitmap本身是分开的,Bitmap本身存储在dalvik heap 中。导致其pixel数据不能判断是否还需要使用,不能及时释放,容易引起OOM错误。
从Android 3.0(API 11)开始,pixel数据与Bitmap一起存储在Dalvik heap中。
在加载图片资源时,可采用以下一些方法来避免OOM的问题:
1,在Android 2.3.3以及之前,建议使用 Bitmap.recycle()方法,及时释放资源。
2,在Android 3.0开始,可设置BitmapFactory.options.inBitmap值,(从缓存
中获取)达到重用Bitmap的目的。如果设置,则inPreferredConfig属性值会被重用的Bitmap该属性值覆盖。
3,通过设置Options.inPreferredConfig值来降低内存消耗: 默认为ARGB_8888: 每个像素4字节. 共32位。 Alpha_8: 只保存透明度,共8位,1字节。 ARGB_4444: 共16位,2字节。 RGB_565:共16位,2字节。 如果不需要透明度,可把默认值ARGB_8888改为RGB_565,节约一半内存。
4,通过设置Options.inSampleSize 对大图片进行压缩,可先设置
Options.inJustDecodeBounds,获取Bitmap的外围数据,宽和高等。然后计算压缩比例,进行压缩。
5,设置Options.inPurgeable和inInputShareable:让系统能及时回收内存。 inPurgeable:设置为True,则使用BitmapFactory
class ImageLoadTask extends AsyncTask {
String url;
boolean isCache = false;
@Override
protected Bitmap doInBackground(ImageEntity... imageEntities) {
ImageEntity imageEntity = imageEntities[0];
url = imageEntity.url;
isCache = imageEntity.isCache;
return ZHttp.getBitmap(imageEntity.url, imageEntity.width, imageEntity.height);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
ImageView imageView = imageRequestList.remove(url);
String originalUrl = (String) imageView.getTag();
if (originalUrl != null && originalUrl.equals(url))
imageView.setImageBitmap(bitmap);
if (isCache) {
saveToCache(bitmap, url);
if (getFromMemoryCache(url) == null)
putToMemoryCache(url, bitmap);
}
}
}
}
创建的Bitmap用于存储Pixel的内存空间,在系统内存不足时可以被回收,当应用需要再次访问该Bitmap的Pixel时,系统会再次调用BitmapFactory 的decode方法重新生成Bitmap的Pixel数组。 设置为False时,表示不能被回收。
inInputShareable:设置是否深拷贝,与inPurgeable结合使用,inPurgeable为false时,该参数无意义。
True:share a reference to the input data(inputStream, array,etc) 。 False :a deep copy。
6,使用decodeStream代替其
他decodeResource,setImageResource,setImageBitmap等方法来加载图片。 区别:decodeStream直接读取图片字节码,调用nativeDecodeAsset/nativeDecodeStream来完成decode。
无需使用Java空间的一些额外处理过程,节省dalvik内存。但是由于直接读取字节码,没有处理过程,因此不会根据机器的各种分辨率来自动适应,需要在hdpi,mdpi
和ldpi中分别配置相应的图片资源,否则在不同分辨率机器上都是同样的大小(像素点数量),显示的实际大小不对。
decodeResource会在读取完图片数据后,根据机器的分辨率,进行图片的适配处理,导致增大了很多dalvik内存消耗。
decodeStream调用过程: decodeStream(InputStream,Rect,Options) -> nativeDecodeAsset/nativeDecodeStream
decodeResource调用过程:即finishDecode之后,调用额外的Java层的createBitmap方法,消耗更多dalvik内存。
decodeResource(Resource,resId,Options) -> decodeResourceStream (设置Options的inDensity和inTargetDensity参
数) -> decodeStream() (在完成Decode后,进行finishDecode操作) finishDecode() -> Bitmap.createScaleBitmap()(根
据inDensity和inTargetDensity计算scale) -> Bitmap.createBitmap()
以上方法的组合使用,合理避免OOM错误。
【Android面试】View的绘制流程
TCP 协议如何保证传输的可靠性
Dart 到底是值传递还是引用传递
Dart 是一种类型化的语言,它支持值传递和对象引用传递。在 Dart 中,默认情况下,所有的对象都是通过引用传递的,但是对于基本数据类型(数字、字符串、布尔值等),则是值传递。
值传递意味着当你将一个参数传递给一个函数时,函数会接收到一个新的副本。如果在函数内部对这个参数进行修改,不会影响到函数外部的原始值。
引用传递意味着当你将一个对象传递给一个函数时,函数接收的是对象的引用(内存地址),而不是对象的副本。如果在函数内部修改了这个对象的属性,这些修改会反映到函数外部的原始对象上。
void main() {
var number = 10; // 基本数据类型,值传递
print('Before: $number'); // 输出 10
incrementValue(number);
print('After: $number'); // 输出 10,因为 number 没有改变
var obj = MyClass(); // 自定义类,对象引用传递
print('Before: ${obj.value}'); // 输出 0
incrementObject(obj);
print('After: ${obj.value}'); // 输出 1,因为 obj 的 value 属性被修改了
}
void incrementValue(int value) {
value++; // 值传递,不会影响原始值
}
class MyClass {
int value = 0;
}
void incrementObject(MyClass obj) {
obj.value++; // 引用传递,会影响原始对象的 value 属性
}
Android 股票app TCP 协议传输数据保证完整性
在Android股票app中使用TCP协议传输数据时,为了确保数据的完整性,可以采用以下几种方法:
- 数据分包:将数据分成固定大小的包,每个包都有序号,接收方可以根据序号重新组装数据。
- 校验和:计算每个数据包的校验和,接收方通过校验和来验证数据的完整性。
- 确认机制:对每个发送的数据包进行确认,如果在指定时间内未收到确认,发送方会重发数据包。
以下是一个简化的示例代码,展示了如何在发送数据时添加校验和来确保数据的完整性:
public class TCPPacket {
private byte[] data;
private int checksum;
public TCPPacket(byte[] data) {
this.data = data;
this.checksum = calculateChecksum(data);
}
public byte[] getData() {
return data;
}
public int getChecksum() {
return checksum;
}
public void send() {
// 发送数据和校验和...
}
public void receive() {
// 接收数据和校验和...
int receivedChecksum = calculateChecksum(data);
if (receivedChecksum != checksum) {
// 数据损坏,需要重新传输
}
}
private int calculateChecksum(byte[] data) {
// 实现一个校验和计算函数
int sum = 0;
for (byte b : data) {
sum += (b & 0xFF);
}
return sum;
}
}
在实际应用中,你需要使用Java的Socket类来建立TCP连接,并使用DataInputStream和DataOutputStream来发送和接收数据。同时,你还需要处理网络异常和并发问题,确保应用的稳定性和数据的安全性。
Flutter动画使用过吗
Flutter多任务
CSDN flutter多线程 CSD你 【Flutter 面试题】在flutter里streams是什么?有几种streams?有什么场景用到它?
Flutter本身不提供原生的后台任务支持,但是你可以使用原生平台的服务(例如iOS的Background Fetch或Background Processing,Android的WorkManager)来实现。使用 Workmanager 插件
flutter 面试题
Dart类的继承与混入(Mixin) extends、implements、with的用法与区别
Dart类的继承与混入(Mixin) extends、implements、with的用法与区别