Android 语音TTS 识别全链路过程
- 本地录音 =》
ASR识别
=》请求后台语义 =》语义落域分发返回 =》 本地仲裁处理落域分发 =》TTS播报
- 下面是语音链路的一些基本思路
- 录音 :Android基本录音为48K的采样率 语音这边需要做降采样处理 降采样为16K。通过Android原生录音将音频给到引擎
- 唤醒:一般唤醒都是做本地唤醒,所有wakeup唤醒引擎。也可以通过唤醒引擎做一些免唤醒功能
- 语音识别,识别分为两种:
3.1 离线识别,走本地识别引擎,
优点:识别快
缺点:需要精准识别,并不能做太多泛化处理。对音频要求比较高
3.2 在线识别,走云端识别引擎
优点 :可以模糊匹配,多泛化
缺点 :网路查的情况下识别很慢 - 云端与离线云端技能分发
- TTS播报:由云端或本地接收文本进行语音音频合成。进行播报
Android 屏幕适配相关,方案
-
- 通过dp加上自适应布局可以基本解决屏幕碎片化的问题。也是Android推荐使用的屏幕兼容性适配方案。
-
- 根据ui设计图的宽度dp值,算出当前屏幕每dp占当前屏幕多少像素值(也就是
density
)。
- 根据ui设计图的宽度dp值,算出当前屏幕每dp占当前屏幕多少像素值(也就是
-
- 根据ui设计图的宽度dp值,算出当前屏幕分成ui设计图的宽高度dp份后,每dp占当前屏幕实际多少dp,然后这个实际dp值再根dpi转换成具体的像素值。
-
- 自定义像素适配,以美工的设计尺寸为原始尺寸,根据不同设备的密度 计算出宽和高 参考
UIAdapter
。如果想显示屏幕的1/3的话就是360了宽度,是根据设计师给出来的宽度进行设置
- 自定义像素适配,以美工的设计尺寸为原始尺寸,根据不同设备的密度 计算出宽和高 参考
-
- 百分比适配。这是Google 提出来的一个解决适配方案,想要使用必须添加依赖
implementation 'com.android.support:percent:28.0.0'
主要就是两个类
PercentRelativeLayout
PercentFrameLayout
多线程,线程池 相关
- 线程的创建,线程创建的常用方法
- 1.继承Thread重写run方法
- 2.实现Runnable重写run方法
- 3.实现Callable重写call方法
1.3 实现Callable重写call方法
实现Callable和实现Runnable类似,但是功能更强大
可以在任务结束后提供一个返回值,Runnable不行
call方法可以抛出异常,Runnable的run方法不行
可以通过运行Callable得到的Fulture对象监听目标线程调用call方法的结果,得到返回值,(fulture.get(),调用后会阻塞,直到获取到返回值)
- Android中的四类线程池
-
- Android中最常见的四类具有不同特性的线程池分别为FixThreadPool、CachedThreadPool、ScheduleThreadPool和SingleThreadExecutor
-
- FixThreadPool(一堆人排队上公厕)
public static ExecutorService newFixThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
}
//使用
Executors.newFixThreadPool(5).execute(r);
- 从配置参数来看,FixThreadPool只有核心线程,并且数量固定的,也不会被回收,所有线程都活动时,因为队列没有限制大小,新任务会等待执行。
- FixThreadPool其实就像一堆人排队上公厕一样,可以无数多人排队,但是厕所位置就那么多,而且没人上时,厕所也不会被拆迁
- 由于线程不会回收,FixThreadPool会更快地响应外界请求,这也很容易理解,就好像有人突然想上厕所,公厕不是现用现建的
-
- SingleThreadPool(公厕里只有一个坑位)
public static ExecutorService newSingleThreadPool (int nThreads){
return new FinalizableDelegatedExecutorService ( new ThreadPoolExecutor (1, 1, 0, TimeUnit. MILLISECONDS, new LinkedBlockingQueue()) );
}
//使用
Executors.newSingleThreadPool ().execute(r);
- 从配置参数可以看出,SingleThreadPool只有一个核心线程,确保所有任务都在同一线程中按顺序完成。因此不需要处理线程同步的问题。
- 可以把SingleThreadPool简单的理解为FixThreadPool的参数被手动设置为1的情况,即Executors.newFixThreadPool(1).execute(r)。所以SingleThreadPool可以理解为公厕里只有一个坑位,先来先上。为什么只有一个坑位呢,因为这个公厕是收费的,收费的大爷上年纪了,只能管理一个坑位,多了就管不过来了(线程同步问题)
-
- CachedThreadPool(一堆人去一家很大的咖啡馆喝咖啡)
public static ExecutorService newCachedThreadPool(int nThreads){
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit. SECONDS, new SynchronousQueue());
}
//使用
Executors.newCachedThreadPool().execute(r);
- CachedThreadPool只有非核心线程,最大线程数非常大,所有线程都活动时,会为新任务创建新线程,否则利用空闲线程(60s空闲时间,过了就会被回收,所以线程池中有0个线程的可能)处理任务。
- 任务队列
SynchronousQueue
相当于一个空集合,导致任何任务都会被立即执行。 - CachedThreadPool就像是一堆人去一个很大的咖啡馆喝咖啡,里面服务员也很多,随时去,随时都可以喝到咖啡。但是为了响应国家的“光盘行动”,一个人喝剩下的咖啡会被保留60秒,供新来的客人使用,哈哈哈哈哈,好恶心啊。如果你运气好,没有剩下的咖啡,你会得到一杯新咖啡。但是以前客人剩下的咖啡超过60秒,就变质了,会被服务员回收掉。
- 比较适合执行大量的耗时较少的任务。喝咖啡人挺多的,喝的时间也不长
-
- ScheduledThreadPool(4个里面唯一一个有延迟执行和周期重复执行的线程池)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize){
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedQueue ());
}
//使用,延迟1秒执行,每隔2秒执行一次Runnable r
Executors. newScheduledThreadPool (5).scheduleAtFixedRate(r, 1000, 2000, TimeUnit.MILLISECONDS);
- 核心线程数固定,非核心线程(闲着没活干会被立即回收)数没有限制。
- 从上面代码也可以看出,ScheduledThreadPool主要用于执行定时任务以及有固定周期的重复任务。
Handler 介绍
- 由于Android中主线程是不能进行耗时操作的,子线程是不能进行更新UI的。所以就有了handler,它的作用就是实现线程之间的通信。 handler整个流程中,主要有四个对象,handler,Message,MessageQueue, Looper。当应用创建的时候,就会在主线程中创建handler对象, 我们通过要传送的消息保存到Message中,handler.post handler通过调用sendMessage方法将Message发送到MessageQueue中,Looper对象就会不断的调用loop()方法 不断的从MessageQueue中取出Message交给handler进行处理。从而实现线程之间的通信
说下 handler 原理
- Handler,Message,looper 和 MessageQueue 构成了安卓的消息机制,handler创建后可以通过 sendMessage 将消息加入消息队列,然后 looper不断的将消息从 MessageQueue 中取出来,回调到 Hander 的 handleMessage方法,从而实现线程的通信。
- 在UI线程创建Handler,此时我们不需要手动开启looper,因为在应用启动时,在ActivityThread的main方法中就创建了一个当前主线程的looper,并开启了消息队列,消息队列是一个无限循环,为什么无限循环不会ANR ? 因为应用的整个生命周期就是运行在这个消息循环中的,安卓是由事件驱动的,Looper.loop不断的接收处理事件,每一个点击触摸或者Activity每一个生命周期都是在Looper.loop的控制之下的,looper.loop一旦结束,应用程序的生命周期也就结束了。我们可以想想什么情况下会发生ANR,第一,事件没有得到处理
- 事件正在处理,但是没有及时完成,而对事件进行处理的就是looper,所以只能说事件的处理如果阻塞会导致ANR,而不能说looper的无限循环会ANR。
另一种情况就是在子线程创建Handler,此时由于这个线程中没有默认开启的消息队列,所以我们需要手动调用looper.prepare(),并通过looper.loop开启消息 - 主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗
Activity A跳转Activity B,再按返回键,生命周期执行的顺序?
- A.onPause() B.onCreate() B.onStart() B.onResume() A.onStop()
- 另外 如果Activity B是透明的 或者Activity B 并未完全遮住Activity A,那么上述操作点击Activity A 跳转 Activity B 生命周期中A.onStop()是不会被调用的,因为Activity A还可见,所以Activity A不能被停止
View 的绘制流程
- Activity、Window、DecorView之间关系
public void setContentView(@LayoutRes int layoutResID) {
// 将xml布局传递到Window当中
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
- 从代码可以看出,Activity的setContentView实质是将View传递到Window的setContentView()方法中,Window的setContenView会在内部调用installDecor()方法创建DecorView
- View的绘制是从
ViewRootImpl
的performTraversals()方法开始,从最顶层的View(ViewGroup)开始逐层对每个View进行绘制操作
- measure:为测量宽高过程,如果是ViewGroup还要在onMeasure中对所有子View进行measure操作。
- layout:用于摆放View在ViewGroup中的位置,如果是ViewGroup要在onLayout方法中对所有子View进行layout操作。
- draw:往View上绘制图像。
- View 的绘制流程是 measure -> layout -> draw
View 事件分发机制
- View 的事件分发机制主要涉及到以下几个方法
- dispatchTouchEvent ,这个方法主要是用来分发事件的
- onInterceptTouchEvent,这个方法主要是用来拦截事件的(需要注意的是 ViewGroup 才有这个方法,View 没有 onInterceptTouchEvent 这个方法)
- onTouchEvent 这个方法主要是用来处理事件的
- requestDisallowInterceptTouchEvent(true),这个方法能够影响父View是否拦截事件,true 表示父 View 不拦截事件,false 表示父 View 拦截事件
- 当触摸事件发生时,首先 Activity 将 TouchEvent 传递给最顶层的 View,TouchEvent最先到达最顶层 view 的 dispatchTouchEvent,然后由 dispatchTouchEvent方法进行分发,
如果dispatchTouchEvent返回true 消费事件,事件终结。
如果dispatchTouchEvent返回 false ,则回传给父View的onTouchEvent事件处理;
如果dispatchTouchEvent返回super的话,默认会调用自己的onInterceptTouchEvent方法。
默认的情况下onInterceptTouchEvent回调用super方法,super方法默认返回false,所以会交给子View的onDispatchTouchEvent方法处理
如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理,
如果 interceptTouchEvent 返回 false ,那么就传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。
Activity 四种启动模式
- standard : 默认启动模式,每开启一个
activity
就在任务栈中创建一个新的实例.Top single 顶部只有一个 不允许存在两个相同的Activity
.使用场景:基本绝大多数地方都可以用。 - singleTop: 如果在任务的栈顶正好存有该
Activity
的实例,则会通过调用onNewIntent()
方法进行重用,否则就会同 standard 模式一样,创建新的实例并放入栈顶。即便栈中已经存在了该 Activity 的实例,也会创建新的实例
当且仅当启动的 Activity 和上一个 Activity 一致的时候才会通过调用onNewIntent()
方法重用 Activity 。使用场景:资讯阅读类 APP 的内容界面。 - singleTask : 在同一个任务栈中,如果要启动的目标Activity已经在栈中,则会复用该Activity,并调用其
onNewIntent()
方法,并且该Activity上面的Activity会被清除,如果栈中没有,则创建新的实例。使用场景:浏览器的主页面,或者大部分 APP 的主页面。 - singleInstance: 指定为
singleInstance
模式的活动会启用一个新的返回栈来管理这个活动。在一个新栈中创建该 Activity 的实例,并让多个应用共享该栈中的该 Activity 实例。一旦该模式的 Activity 实例已经存在于某个栈中,任何应用再激活该 Activity 时都会重用该栈中的实例,是的,依然是调用onNewIntent()
方法。其效果相当于多个应用共享一个应用,不管是谁激活,该 Activity 都会进入同一个应用中。但值得引起注意的是:singleInstance
不要用于中间页面,如果用户中间页面,跳转会出现很难受的问题。 这个在实际开发中我暂未遇到过, Android 系统的来电页面,多次来电都是使用的同一个Activity
。
Service启动方式
- startService. startService() 启动一个 Service。一旦启动,Service 将一直运行在后台,即使启动这个 Service 的组件已经被销毁。通常一个被 start 的 Service 会在后台执行单独的操作,也并不需要给启动它的组件返回结果。只有当 Service 自己调用 stopSelf() 或者其它组件调用 stopService() 才会终止。
- **bindService **. bindService() 来绑定一个 Service。这种方式会让 Service 和启动它的组件绑定在一起,当启动它的组件销毁的时候,Service 也会自动进行 unBind 操作。同一个 Service 可以被多个组件绑定,只有所有绑定它的组件都进行了 unBind 操作,这个 Service 才会被销毁
- Service 的生命周期
当调用 startService() 去 start 一个 Service 后,仍然可以 bind 这个 Service。比如:当播放音乐的时候,需要调用 startService() 启动指定的音乐,当需要获取该音乐的播放进度的时候,又需要调用 bindService(),在这种情况下,除非 Service 被 unbind,此前调用 stopService() 和 stopSelf() 都不能停止该 Service
完整生命周期(entire lifetime):从 onCreate() 被调用,到 onDestroy() 返回。和 Activity 类似,一般在 onCreate() 方法中做一些初始化的工作,在 onDestroy() 中做一些资源释放的工作。如,若 Service 在后台播放一个音乐,就需要在 onCreate() 方法中开启一个线程启动音乐,并在 onDestroy() 中结束线程。
活动生命周期(activity lifetime):从 onStartCommand() 或 onBind() 回调开始,由相应的 startService() 或 bindService() 调用。start 方式的活动生命周期结束就意味着完整证明周期的结束,而 bind 方式,当 onUnbind() 返回后,Service 的活动生命周期结束。
是 startService() 还是 bindService() 启动 Service,onCreate() 和 onDestroy() 均会被回调
Service 的 onCreate() 可以执行耗时操作吗?
- Service 运行在主线程中,它并不是一个新的线程,也不是新的进程,所以并不能执行耗时操作。
如果要在 Service 中执行耗时操作,怎么做?
- 使用 AysncTask 或 HandlerThread 来替代 Thread 创建线程。
- IntentService 继承于 Service,若 Service 不需要同时处理多个请求,那么使用 IntentService 将是最好选择。只需要重写 onHandleIntent() 方法,该方法接收一个回调的 Intent 参数, 在方法内进行耗时操作,因为它默认开启了一个子线程,操作执行完成后也无需手动调用 stopSelf() 方法,onHandleIntent() 将会自动调用该方法
Service 与 IntentService区别
- Service 不是运行在独立的线程,所以不建议在Service中编写耗时的逻辑和操作,否则会引起ANR。
- IntentService
- 可用于执行后台耗时的任务,任务执行后会自动停止。
- 具有高优先级,适合高优先级的后台任务,且不容易被系统杀死。
- 可以多次启动,每个耗时操作都会以工作队列的方式在IntentService的onHandleIntent回调方法中执行
Serializable 和Parcelable的区别
- 平台区别。Serializable是属于 Java 自带的,表示一个对象可以转换成可存储或者可传输的状态,序列化后的对象可以在网络上进行传输,也可以存储到本地。
Parcelable 是属于 Android 专用。不过不同于Serializable,Parcelable实现的原理是将一个完整的对象进行分解。而分解后的每一部分都是Intent所支持的数据类型。 - 编写上的区别。Serializable代码量少,写起来方便
Parcelable代码多一些,略复杂 - 选择的原则
- 如果是仅仅在内存中使用,比如activity、service之间进行对象的传递,强烈推荐使用Parcelable,因为Parcelable比Serializable性能高很多。因为Serializable在序列化的时候会产生大量的临时变量, 从而引起频繁的GC。
- 如果是持久化操作,推荐Serializable,虽然Serializable效率比较低,但是还是要选择它,因为在外界有变化的情况下,Parcelable不能很好的保存数据的持续性。
- 本质的区别
- Serializable的本质是使用了反射,序列化的过程比较慢,这种机制在序列化的时候会创建很多临时的对象,比引起频繁的GC、
- Parcelable方式的本质是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的类型,这样就实现了传递对象的功能了
Serializable中为什么要设置UID,设置UID与不设置UID值的区别和影响 ?
- serialVersionUID 是用来辅助序列化和反序列化的过程。 序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID一致才能成功的反序列化
- serialVersionUID 适用于java序列化机制。简单来说,JAVA序列化的机制是通过判断类的serialVersionUID来验证的版本一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID于本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是InvalidCastException。
- 具体序列化的过程 :序列化操作时会把系统当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会自动检测文件中的serialVersionUID,判断它是否与当前类中的serialVersionUID一致。如果一致说明序列化文件的版本与当前类的版本是一样的,可以反序列化成功,否则就失败;
- serialVersionUID有两种显示的生成方式:
- 默认的1L,比如:private static final long serialVersionUID = 1L;
- 根据包名,类名,继承关系,非私有的方法和属性,以及参数,返回值等诸多因子计算得出的,极度复杂生成的一个64位的哈希字段。基本上计算出来的这个值是唯一的。比如:private static final long serialVersionUID = xxxxL;
注意:显示声明serialVersionUID可以避免对象不一致
内存泄漏的场景和解决办法
- 非静态内部类的静态实例
非静态内部类会持有外部类的引用,如果非静态内部类的实例是静态的,就会长期的维持着外部类的引用,组织被系统回收,解决办法是使用静态内部类 - 多线程相关的匿名内部类和非静态内部类
匿名内部类同样会持有外部类的引用,如果在线程中执行耗时操作就有可能发生内存泄漏,导致外部类无法被回收,直到耗时任务结束,解决办法是在页面退出时结束线程中的任务 - Handler内存泄漏
Handler导致的内存泄漏也可以被归纳为非静态内部类导致的,Handler内部message是被存储在MessageQueue中的,有些message不能马上被处理,存在的时间会很长,导致handler无法被回收,如果handler是非静态的,就会导致它的外部类无法被回收,解决办法是1.使用静态handler,外部类引用使用弱引用处理2.在退出页面时移除消息队列中的消息 - Context导致内存泄漏
根据场景确定使用Activity的Context还是Application的Context,因为二者生命周期不同,对于不必须使用Activity的Context的场景(Dialog),一律采用Application的Context,单例模式是最常见的发生此泄漏的场景,比如传入一个Activity的Context被静态类引用,导致无法回收 - 静态View导致泄漏
使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致无法回收,解决办法是在Activity销毁的时候将静态View设置为null(View一旦被加载到界面中将会持有一个Context对象的引用,在这个例子中,这个context对象是我们的Activity,声明一个静态变量引用这个View,也就引用了activity) - WebView导致的内存泄漏
WebView只要使用一次,内存就不会被释放,所以WebView都存在内存泄漏的问题,通常的解决办法是为WebView单开一个进程,使用AIDL进行通信,根据业务需求在合适的时机释放掉 - 资源对象未关闭导致
如Cursor,File等,内部往往都使用了缓冲,会造成内存泄漏,一定要确保关闭它并将引用置为null - 集合中的对象未清理
集合用于保存对象,如果集合越来越大,不进行合理的清理,尤其是入股集合是静态的 - Bitmap导致内存泄漏
bitmap是比较占内存的,所以一定要在不使用的时候及时进行清理,避免静态变量持有大的bitmap对象 - 监听器未关闭
很多需要register和unregister的系统服务要在合适的时候进行unregister,手动添加的listener也需要及时移除
EventBus原理
- 主要是维护了几个数组,然后根据对应的key找到对应的注册对象,通过放射的方式调用对应的方法。
- EventBus 2.x 是采用反射的方式对整个注册的类的所有方法进行扫描来完成注册,当然会有性能上的影响。EventBus 3.0 中EventBus提供了EventBusAnnotationProcessor注解处理器来在编译期通过读取@Subscribe()注解并解析、处理其中所包含的信息,然后生成java类来保存所有订阅者关于订阅的信息,这样就比在运行时使用反射来获得这些订阅者的信息速度要快
/注册事件
EventBus.getDefault().register(this);
//注册方法
@Subscribe
public void event(BaseEventBusBeaan message) {
LogUtils.d("EventBusActivity event");
}
//发送事件
EventBus.getDefault().post(new BaseEventBusBeaan("123", new Bundle()));
//反注册
EventBus.getDefault().unregister(this);
总结一下大概的流程
- 通过apt在编译期将所有被
@Subscribe
注解的函数添加到MyEventBusIndex
对象中。 - 在
register
过程中生成subscriptionsByEventType
的数据。 - 在
post
过程中通过subscriptionsByEventType
数据查找对应的函数,然后再通过反射的方式调用。
优先级的问题
这个问题也十分简单,只需要在插入数据的时候,做下优先级判断即可。
Android 热更新 流程和原理
- 一个完整的项目应该有如下分支:
develop分支---- 这个分支中放的是线上的发布版本。
bugfix分支---- 这个是热更新分支,由develop中迁出。
master分支---- 这个是开发中的分支 - 热更新应当按照如下步骤进行:
- 线上检测到严重的crash
- 从develop中拉出一个线上的最新版,在bugfix分支上进行问题的修复
- jenkins构建和补丁的生成
- app通过推送或主动拉取补丁文件
- 将bugfix代码合到master上,保证以后不会出现该问题
- 主流热更新框架介绍
- Dexposed :该框架是阿里巴巴开源的一个Android平台下的无侵入的运行时AOP(面向方向编程)框架。基于以前开源的一个Xposed框架实现的。Dexposed框架基于Hook技术实现功能,不仅可以hook你自己的程序代码,也可以hook你的应用程序中调用的Android框架中的函数。基于动态类加载技术,运行中的app可以加载一小段经过编译的Java的代码,而且在不需要重写APP的前提下,就可以实现修改APP
- AndFix:该框架同样出自阿里。与Dexposed不是同一个团队。AndFix也是基于Xposed思想。而相比第一个,它是一个更纯粹的热修复的框架。
- Nuwa:它其实是基于类加载器ClassLoader加载Dex文件。如果多个Dex文件存在相同的类,那么排在前面的Dex文件就将优先被选择。这是热更新最主要的思想,它通过不断地去轮询,去遍历Dex的一个数组,然后把我们需要修改的类的dex文件加到最前面,这样当轮询遍历时就不会加载有问题的那个类。
- 热更新的原理
- Android的类加载机制 :Android的类加载器主要有这样两个:PathClassLoader和DexClassLoader。PathClassLoader主要用于加载系统的类和应用类,DexClassLoader主要加载Dex文件,Jar文件,apk文件等。
- 热修复机制:在BaseClassLoader中会创建一个dexElements数组,然后我们会通过ClassLoader遍历这个数组,加载这个数组中的dex文件。这样当BaseClassLoader加载到正确的类以后,就不会去加载有Crash的那个类。因此我们就将这个有问题修复后的类放入Dex文件当中,让这个Dex文件排在dexElements前面。这样BaseClassLoader就不会加载到处于后面的那个Dex文件。这样就完成了整个热修复过程。
Android线程间通信四种方式:
-
- 通过
Handler
机制
主线程中定义Handler,子线程发消息,通知Handler完成UI更新,Handler对象必须定义在主线程中,如果是多个类直接互相调用,就不是很方便,需要传递content对象或通过接口调用。另外Handler机制与Activity生命周期不一致的原因,容易导致内存泄漏,不推荐使用。
- 通过
-
-
runOnUiThread
方法,用Activity对象的runOnUiThread方法更新,在子线程中通过runOnUiThread()方法更新UI,强烈推荐使用。
-
- 3.
View.post(Runnable r)
这种方法更简单,但需要传递要更新的View过去,推荐使用 -
-
AsyncTask
,即异步任务,是Android给我们提供的一个处理异步任务的类.通过此类,可以实现UI线程和后台线程进行通讯,后台线程执行异步任务,并把结果返回给UI线程
-
Android中有哪些进程间通信方式?
-
Binder
简单易用 只能传输Bundle支持的数据类型 四大组件间的进程间通信
文件共享 简单易用 不适用高并发场景,并且无法做到进程间即时通信 适用于无关发的情况下,交换简单的数据,对实时性要求不高的场景。 -
AIDL
功能强大,支持一对多实时并发通信 使用稍复杂,需要处理好线程间的关系 一对多通信且有RPC需求 -
Messenger
功能一般,支持一对多串行通信,支持实时通信 不能很好地处理高并发的情形,不支持RPC,由于数据通过Message传输,因此只能传输Bundle支持的数据类型 低并发的一对多实时通信,无RPC需求,或者无需要返回结果的RPC需求
-ContentProvider
支持一对多的实时并发通信,在数据源共享方面功能强大,可通过Call方法扩展其它操作 可以理解为受约束的AIDL,主要提供对数据源的CRUD操作 一对多的进程间数据共享 -
BroadcastReceiver
操作简单,对持一对多实时通信 只支持数据单向传递,效率低且安全性不高 一对多的低频率单向通信 -
Socket
功能强大,可通过网络传输字节流,支持一对多实时并发通信 实现细节步骤稍繁琐,不支持直接的RPC 网络间的数据交换
websocket 和 http相关
-
- 什么是
websocket
?
websocket
是HTML5
的一种新协议,允许服务器想客户端传递信息,实现浏览器和客户端双工通信。
- 什么是
-
-
websocket
特点
(1)与http
协议有良好的兼容性;
(2)建立在TCP
协议之上,和http
协议同属于应用层;
(3)数据格式比较轻量,性能开销小,通信高效;
(4)可以发送文本,也可以发送二进制;
(5)没有同源限制,可以与任意服务器通信。
-
-
-
http
和websocket
的区别
3.1http
协议是短链接,因为请求之后,都会关闭连接,下次请求需要重新打开链接。
3.2websocket
协议是一种长连接,只需要通过一次请求来初始化连接,然后所有请求和响应都是通过TCP
链接进行通信。
-
-
- 特点
最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
其他特点包括:
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws
(如果加密,则为wss
),服务器网址就是 URL
- 特点