Android 开发过程中平时遇到的一些问题及总结

相信大家都有面试的经历,相对比面试官的问的一些问题其实都是基础的知识,但就是一些基础的知识我们也不是很完美的回答出来,我们也知道现在的开发人员很多,一家公司一个岗位就会有很多的开发者投递,在那么多开发者中你如何让面试官很深的认识你,给面试官一个很深的印象,能让他在技术水平差不多的情况的下第一个想起的是你。
从这篇文章对整个面试中所问到的问题进行梳理,查缺补漏。

JAVA

String,StringBuffer,StringBuilder区别?
一般回答是这样的:
String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)
面试的时候千万不要这么说,这样说会让面试官你还只停留在会用的层面,不知道原理,你应该这么说:

String,StringBuffer,StringBuilder最终底层存储与操作的都是char数组,但是String里面的char数组是final的,而StringBuffer,StringBuilder不是,也就是说,String是不可变的,想要新的字符串只能重新生成String;而StringBuffer和StringBuilder只需要修改底层的char数组就行,相对来说,开销要小很多,String的大多数方法都是重新new一个新String对象返回,频繁重新生成容易生成很多垃圾。

为什么StringBuffer 是线程安全的?
StringBuffer是线程安全的,StringBuilder是线程不安全的,因为StringBuffer的方法是加了synchronized锁起来了的,而StringBuilder没有

增删比较多时用StringBuffer或StringBuilder(注意单线程与多线程)。

Serializable与Parcelable的区别?
1)Serializable接口是javaSE的,而Parcelable接口是Android特有的
2)Serializable接口实现简单,Parcelable略显复杂
3)Parcelable存储到磁盘上,Parcelable存储到内存中

HashMap的实现机制?
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。

/**
* The table, resized as necessary. Length MUST Always be a power of two.
 */
transient Entry[] table;

static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    final int hash;
    ……
}

可以看出,Entry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用,这就构成了链表。

HashMap如果Hash冲突了怎么解决?
可以看下这篇文章
https://blog.csdn.net/qq_27093465/article/details/52269862

如何线程安全的使用HashMap?
无非就三种实现方式:
1)Hashtable
2)ConcurrentHashMap
3)Synchronized Map

//Hashtable
Map<String, String> hashtable = new Hashtable<>();

//synchronizedMap
Map<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());

//ConcurrentHashMap
Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();

HashTable源码中是使用synchronized来保证线程安全的



public synchronized V get(Object key) {
       // 省略实现
    }
public synchronized V put(K key, V value) {
    // 省略实现
    }

所以当一个线程访问HashTable的同步方法时,其他线程如果也要访问同步方法,会被阻塞住。举个例子,当一个线程使用put方法时,另一个线程不但不可以使用put方法,连get方法都不可以,好霸道啊!!!so~~,效率很低,现在基本不会选择它了。

ConcurrentHashMap(以下简称CHM)是JUC包中的一个类,Spring的源码中有很多使用CHM的地方。CHM的一些重要特性和什么情况下应该使用CHM。需要注意的是,上面博客是基于Java 7的,和8有区别,在8中CHM摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现,利用CAS算法。



// synchronizedMap方法
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
       return new SynchronizedMap<>(m);
   }
// SynchronizedMap类
private static class SynchronizedMap<K,V>
       implements Map<K,V>, Serializable {
       private static final long serialVersionUID = 1978198479659022715L;

       private final Map<K,V> m;     // Backing Map
       final Object      mutex;        // Object on which to synchronize

       SynchronizedMap(Map<K,V> m) {
           this.m = Objects.requireNonNull(m);
           mutex = this;
       }

       SynchronizedMap(Map<K,V> m, Object mutex) {
           this.m = m;
           this.mutex = mutex;
       }

       public int size() {
           synchronized (mutex) {return m.size();}
       }
       public boolean isEmpty() {
           synchronized (mutex) {return m.isEmpty();}
       }
       public boolean containsKey(Object key) {
           synchronized (mutex) {return m.containsKey(key);}
       }
       public boolean containsValue(Object value) {
           synchronized (mutex) {return m.containsValue(value);}
       }
       public V get(Object key) {
           synchronized (mutex) {return m.get(key);}
       }

       public V put(K key, V value) {
           synchronized (mutex) {return m.put(key, value);}
       }
       public V remove(Object key) {
           synchronized (mutex) {return m.remove(key);}
       }
       // 省略其他方法
   }

synchronizedMap从源码中可以看出调用synchronizedMap()方法后会返回一个SynchronizedMap类的对象,而在SynchronizedMap类中使用了synchronized同步关键字来保证对Map的操作是线程安全的。

HasMap为什么会线程不安全?
个人觉得HashMap在并发时可能出现的问题主要是两方面,
首先如果多个线程同时使用put方法添加元素,而且假设正好存在两个put的key发生了碰撞(hash值一样),那么根据HashMap的实现,这两个key会添加到数组的同一个位置,这样最终就会发生其中一个线程的put的数据被覆盖。

第二就是如果多个线程同时检测到元素个数超过数组大小*loadFactor,这样就会发生多个线程同时对Node数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋给table,也就是说其他线程的都会丢失,并且各自线程put的数据也丢失。

HashMap出现死循环的原因?
因为多线程会导致HashMap的Entry节点形成环链,这样当遍历集合时Entry的next节点用于不为空,从而形成死循环






Activity:

当面试官问你什么Activity,你是不是会觉得一阵懵* ?

在日常应用中,Android是用户交互的接口,他提供了一个界面,让用户进行点击,各种滑动操作等。

Activity的四种状态:
running / paused / stopped / killed
running:点击屏幕,屏幕做出响应,处于activity栈顶的状态
paused:失去焦点不能进行操作,或者是一个透明的在栈顶的Activity,注意并不是被销毁了啊,它的成员信息和变量都还在,当然还有另外一种状态,当内存紧张的时候会北回收掉
stopped:当一个Activity被另外一个Activity完全覆盖的时候,被覆盖的那个处于stopped状态,不可见,成员信息和变量都还在,如果内存紧张也是会被回收的
killed:已经被系统回收

生命周期:
启动:onCreate >> onStart >> onResume
点击HOME键回到主界面:onPause >> onStop
再次回到远Activity:onResrart>>onStart >> onResume
退出当前Activity:onPause >> onStop >> onDestroy

进程优先级:
前台 / 可见 /服务 /后台 / 空
前台:正在进行交互的Activity或者与前台activity绑定的services
可见:一个activity可见但并处于前台,不能点击
服务:在后台开启的服务
后台:当一个Activity被点击home键,退居后台,没有=被回收
空 :不属于前面四种进程的任意一种,处于缓存的目的而保留

启动模式:
standard / singletop / singletask / singleinstance

如何配置Activity的启动模式?
直接在AndroidManifest.xml配置的android:launchMode属性为以上四种之一即可

standrd:标准模式,每次启动都会重新创建一个activity实例加入到任务栈中,不会考虑是不是有此实例存在,不会复用,消耗内存资源

singletop :栈顶复用模式,只检测任务栈栈顶,只有在栈顶的Activity不会被创建,就算是在第二位也是会被创建

singletask:栈内复用模式,也是一个单例模式,检测整个任务栈,如果有并把在之上的实例全部移除掉,回掉onNewIntent方法

singleinstance:一个activity如果在整个系统中只存在一个实例,而且这个activity独享整个任务栈

应用场景:
singleTop:适合接收通知启动的内容显示页面。例如,某个新闻客户端的新闻内容页面,如果收到10个新闻推送,每次都打开一个新闻内容页面是很烦人,另外,singleTop启动模式适合于一些不常用的Activity页面,比如“找回密码”、“设置界面”等。

singleTask:最典型的样例就是应用中展示的主页(Home页),假设用户在主页跳转到其他页面,运行多次操作后想返回到主页,假设不使用SingleTask模式,在点击返回的过程中会多次看到主页,这明显就是设计不合理了。

singleInstance:比如说,使用微信调起自己的客户端某个页面,不做任何处理的情况下,按下回退或者当前Activity.finish(),页面不会停留在自己的客户端而是返回到微信的客户端页面。但是如果这个页面的启动模式设置为singleTask,当按下返回键或者Activity。finish(),页面都会停留在自己的客户端(因为自己的Application回退栈不为空),这明显不符合逻辑的。产品的要求是,回退必须回到微信客户端,而且要保证不杀死自己的Application.因此,显然其他的其他的启动模式都不具备这个功能。

使用方法:
方法一:

android:launchMode="standard|singleInstance|single Task|singleTop

使用android:launchMode=”standard|singleInstance|single Task|singleTop”来控制Acivity任务栈。

方法二:

Intent Flags:
Intent intent=new Intent();
intent.setClass(MainActivity.this, MainActivity2.class);
intent.addFlags(Intent. FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);

Flags有很多,比如:
Intent.FLAG_ACTIVITY_NEW_TASK 相当于singleTask
Intent. FLAG_ACTIVITY_CLEAR_TOP 相当于singleTop

scheme(sigeimo)跳转协议:
android中的scheme是一种页内跳转协议,是一种非常好的实现机制,通过定义自己的scheme协议,可以非常方便的在app内部跳转到各个界面;通过scheme协议,服务器可以定制化告诉app跳转到哪个页面,可以通过通知栏消息栏定制化跳转页面,也可以通过H5页面中定义连接跳转指定的activity页面等

应用场景:
1 服务端下发一个url路径,客户端根据下方的路径跳转到指定界面
2 从H5跳转到App内
3 App根据url挑战到另外的一个App

两个 Activity 之间跳转时必然会执行的是哪几个方法?
一般情况下比如说有两个 activity,分别叫 A,B,当在 A 里面激活 B 组件的时候, A 会调用 onPause()方法,然后 B 调用 onCreate() ,onStart(), onResume()。
这个时候 B 覆盖了窗体, A 会调用 onStop()方法. 如果 B 是个透明的,或者 是对话框的样式, 就不会调用 A 的 onStop()方法。

横竖屏切换时 Activity 的生命周期?
此时的生命周期跟清单文件里的配置有关系。
1.不设置 Activity 的 android:configChanges 时,切屏会重新调用各个生命周期默认首先销毁当前 activity,然后重新加载。
2.设置 Activity android:configChanges=”orientation|keyboardHidden|screenSize”时,切 屏不会重新调用各个生命周期,只会执行 onConfigurationChanged 方法。
通常在游戏开发, 屏幕的朝向都是写死的。

如何将一个 Activity 设置成窗口的样式?
只需要给我们的 Activity 配置如下属性即可。
android:theme=”@android:style/Theme.Dialog”

Android 中的 Context, Activity,Appliction 有什么区别?
相同:Activity 和 Application 都是 Context 的子类。
Context 从字面上理解就是上下文的意思,在实际应用中它也确实是起到了管理 上下文环境中各个参数和变量的总用,方便我们可以简单的访问到各种资源。

不同:维护的生命周期不同。Context 维护的是当前的 Activity 的生命周期, Application 维护的是整个项目的生命周期。使用 context 的时候,小心内存泄露,防止内存泄露,注意一下几个方面:
1)不要让生命周期长的对象引用 activity context,即保证引用 activity 的对 象要与 activity 本身生命周期是一样的。
2 )对于生命周期长的对象,可以使用 application,context。
3 )避免非静态的内部类,尽量使用静态类,避免生命周期问题,注意内部类 对外部对象引用导致的生命周期变化。

如何获取当前屏幕Activity的对象?
使用ActivityLifecycleCallbacks
应用场景:可以利用ActivityLifecycleCallbacks 做一些数据埋点,统计之类的应用,对其统一做处理。这样对减少Activity的代码入侵。尽量简化和模块化的注入生命周期方法。

ActivityLifecycleCallbacks 是什么?
见名知意,Activity生命周期回调,Application通过此接口提供了一套回调方法,用于让开发者对Activity的生命周期事件进行集中处理。
但是这个要求API 14+ (Android 4.0+)以上使用,幸好我们这个最低支持,满足需求。

ActivityLifecycleCallbacks 怎么使用?
重写Application的onCreate()方法,或在Application的无参构造方法内,调用Application.registerActivityLifecycleCallbacks()方法,并实现ActivityLifecycleCallbacks接口

知道onNewIntent吗?
如果IntentActivity处于任务栈的顶端,也就是说之前打开过的Activity,现在处于onPause、onStop 状态的话,其他应用再发送Intent的话,执行顺序为:
onNewIntent,onRestart,onStart,onResume。

除了用Intent 去启动一个Activity,还有其他方法吗?
使用adb shell am 命令

1)ams启动一个activity
adb shell am start com.example.fuchenxuan/.MainActivity
2)am发送一个广播,使用action
adb shell am broadcast -a magcomm.action.TOUCH_LETTER

Android Service与Activity之间通信的几种方式?
通过Binder对象
1)当Activity通过调用bindService(Intent service, ServiceConnection conn,int flags),得到一个Service的一个对象,通过这个对象我们可以直接访问Service中的方法。
2)通过Broadcast Receiver(广播)的形式
3)EventBus 是一个Android开源事件总线框架 后面我们会有专门的讲解。

TaskAffinity 是什么?
标识Activity任务栈名称的属性:TaskAffinity,默认为应用包名

如果新Activity是透明主题时,旧Activity会不会走onStop?
不会!

android完全退出应用程序的三种方式?

第一种方法:首先获取当前进程的id,然后杀死该进程。 建议使用这种方式

android.os.Process.killProcess(android.os.Process.myPid())

第二种方法:终止当前正在运行的Java虚拟机,导致程序终止

System.exit(0);

第三种方法:强制关闭与该包有关联的一切执行

ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);   
manager.restartPackage(getPackageName());

使用这种方式关闭应用程序需要加上权限

<uses-permission android:name="android.permission.RESTART_PACKAGES" />

介绍下Android应用程序启动过程
1)Launcher通过Binder进程间通信机制通知ActivityManagerService,它要启动一个Activity;

2)ActivityManagerService通过Binder进程间通信机制通知Launcher进入Paused状态;

3)Launcher通过Binder进程间通信机制通知ActivityManagerService,它已经准备就绪进入Paused状态,于是ActivityManagerService就创建一个新的进程,用来启动一个ActivityThread实例,即将要启动的Activity就是在这个ActivityThread实例中运行;

4)ActivityThread通过Binder进程间通信机制将一个ApplicationThread类型的Binder对象传递给ActivityManagerService,以便以后ActivityManagerService能够通过这个Binder对象和它进行通信;

5)ActivityManagerService通过Binder进程间通信机制通知ActivityThread,现在一切准备就绪,它可以真正执行Activity的启动操作了。
如果想深入研究的可以查看罗升阳大神的博客https://blog.csdn.net/luoshengyang/article/details/6689748






Fragment:

三个小问题看你能完美解答几个
1 Fragment为什么被称为第五大组件?
2 Fragment的生命周期 ?
3 Fragment之间的通信 ?

1 Fragment为什么被称为第五大组件?
首先Fragment的使用频率并不低于其他四大组件,他有自己的生命周期,同时可以动态灵活的加载到Activity中,所以说Fragment可以被称为第五大组件

加载到Activity的两种方式:
1)添加Fragment到Activity的布局文件当中,也叫静态加载,name属性哦
2)动态的在activity中添加fragment ,也叫动态加载
步骤:
FragmentManage用来管理所有要启动的fragment,并用FragmentTransaction添加和替换相对应的fragment,并用容器资源来作为标志位来设置fragment所要显示到activity当中的位置,最后提交commit方法
(1)创建待添加的碎片实例
(2)获取FragmentManager,在活动中可以直接通过调用 getSupportFragmentManager()方法得到。
(3)开启一个事务,通过调用beginTransaction()方法开启。
(4)向容器内添加或替换碎片,一般使用repalce()方法实现,需要传入容器的id和待添加的碎片实例。
(5)提交事务,调用commit()方法来完成。

FragmentPageAdapter与FragmentStatePageAdapter的区别?
1 FragmentStatePageAdapter适合界面多,FragmentPageAdapter适合界面少
2 FragmentStatePageAdapter比FragmentPageAdapter更节省内存
源码分析:
FragmentPageAdapter适用于页面较少的情况下,因为只对ui分离并没有回收内存, 因为源码里面destoryItem是detach方法()只是对fragment和activity的ui脱离开来,并不回收内存
FragmentStatePageAdapter用于界面较多的情况下,界面多也就意味着更加的消耗内存,FragmentStatePageAdapter在每次切换fragment的时候,他是回收内存的,因为源码里面destoryItem的remove方法真正的释放的内存

Fragement的生命周期:
onAttach >> onCreate >> onCreateView >> onActivityCreated >>onStart >> onResume >> onPause >> onStop >> onDestoryView >> onDestory >>onDetach
很显然你要是像背书似的将上面的生命周期说一遍,你认为你跟其他的竞争者有什么优势?

你应该将Activity与Fragment的生命周期结合起来说:
首先,调用Activity的onCreate方法
当Fragment创建的时候会执行onAttach,是在Activity与fragment关联之后调用的
在之后执行onCreate方法,也就是在fragment创建的时候调用,注意,此时的activity还没有创建完毕
在调用fragment的onCreateView方法,系统首次绘制用户界面的时候调用
调用fragment的onActivityCreated方法,也就是activity被渲染绘制成功后调用,
现在是要调用activity的onStart的方法,当activity可见之后调用,
fragment的onStart方法,表示fragment也可见了
接着调用activity的onResume的方法,表示当前activity可以与用户交互了,当activity可见之后调用
fragment的onResume方法,表示当前fragment也可以与用户进行交互操作了,以上步骤完成了从创建到交互的所有步骤

在之后,Fragment的onPause
Activity的onPause
Fragment的onStop
Activity的onStop
现在来到了Frament的onDestoryView方法,表示当前fragment不在被使用,界面销毁,紧接着来到onDestory方法,表示fragment已经销毁了,最后调用onDetach来解除与activity的联系
最后调用Activity的onDestory方法

Fragment通信:
1)在Fragment中调用Activity的方法 geyActivity
2)在Activity中调用Fragment的方法 接口回调(在Fragment创建接口,在Activity实现)
3)在Fragment调用Fragment的方法 getActivity.findFragmentById 或者广播

Fragment的replace、add、remove?

add:将一个fragment实例添加到Activity的最上层,一般在使用add的时候会配合 hide,show一起使用,为了避免Fragment的重复创建节省资源。
remove:将fragment从fragment队列中删除
replace:替换fragment的实例,replace是FragmentManager的方法

commitAllowingStateLoss与commit的区别?
Activity被系统回收(界面已经不存在了),为了能在下次打开的时候恢复原来的样子,系统为我们保存界面的所有状态,这个时候再去修改界面理论上肯定是不被允许的,为了避免这种异常可以使用:

transaction.commitAllowingStateLoss();

来提交添加Fragment到Activity的事务,与commit()不同的是使用这种方法允许丢失界面的状态和信息。

ViewPager与Fragment结合使用时的懒加载问题?
所谓的 “懒加载” 就是数据只有在Fragment对于用户可见的时才进行加载,我们需要判定Fragment在什么时候是处于可见的状态。一般我们通常是通过Fragment中的生命周期方法onResume来判断Fragment是否可见,但是由于ViewPager预加载的特性,Fragment即便不可见也会执行onResume方法,可以通过setUserVisibleHint()方法来进行判断:

什么时候被调用?

当fragment被创建的时,setUserVisibleHint(boolean isVisibleToUser)方法会被调用,且传入参数值为false。

当fragment可见时,setUserVisibleHint(boolean isVisibleToUser)方法会被调用,且传入参数值为true。

当fragment由 可见 -> 不可见 时,setUserVisibleHint(boolean isVisibleToUser)方法会被调用,且传入参数值为false

所以我们只需要当setUserVisibleHint(boolean isVisibleToUser)方法中的 isVisibleToUser 参数的值为true的时候我们才开始进行数据的加载就可以了。

但是有一点需要需要注意的是 setUserVisibleHint(boolean isVisibleToUser)方法在Fragment的生命周期方法onCreate 之前调用的,也就是说他并不在Fragment的生命周期中。既然是在 onCreate 方法之前被调用,这样就存在许多不确定因素,如果Fragmnet的View还没有完成初始化之前,就在setUserVisibleHint()方法中进行UI的操作,这样显然会导致空指针的出现。因此我们需要对Fragment创建的View进行缓存,确保缓存的View不为空的情况下我们才可以在setUserVisibleHint方法中进行UI操作。







Service

Service是什么?
四大组件之一,可以在后台处理一些耗时的逻辑,也可以执行某些长时间运行的任务,而且看不到界面,包括在程序退出的时候依然能在继续运行

Service与Broadcastrecevier有一个共同点,都是运行在主线程当中,都不能进行长耗时操作

Service与Thread的区别?
Thread程序最小的执行单元,Thread可以进行异步操作,相对独立;而Service是Android的一种机制,如果是本地的Service,依赖与它所在的主线程之上,相比Thread没有那么独立

为什么要用Service而不是Thread呢?
Thread的运行是独立于Activity的,也就是当一个Activity被finish之后,如果没有主动停止Thread或者Thread中的run没有执行完毕时那么这个线程会一直执行下去。因此这里会出现一个问题:当 Activity 被 finish 之后,你不再持有该 Thread 的引用。另一方面,你没有办法在不同的 Activity 中对同一 Thread 进行控制。

Service 是否在 main thread 中执行, service 里面是否 能执行耗时的操作?
默认情况,如果没有显示的指 servic 所运行的进程, Service 和 activity 是运 行在当前 app 所在进程的 main thread(UI 主线程)里面。
service 里面不能执行耗时的操作(网络请求,拷贝数据库,大文件 )
特殊情况 ,可以在清单文件配置 service 执行所在的进程 ,让 service 在另 外的进程中执行

<service android:name="com.baidu.location.f" android:enabled="true" android:process=":remote" >
</service>

Service 里面可以弹吐司么?
可以的。弹吐司有个条件就是得有一个 Context 上下文,而 Service 本身就是 Context 的子类,因此在 Service 里面弹吐司是完全可以的。比如我们在 Service 中完成下载任务后可以弹一个吐司通知用户

Service 的 onStartCommand 方法有几种返回值?各代表什么意思?

有四种返回值,不同值代表的意思如下:
START_STICKY:如果 service 进程被 kill 掉,保留 service 的状态为开始状态,但不保留递送的 intent 对象。随 后系统会尝试重新创建 service,由于服务状态为开始状态,所以创建服务后一定会调用 onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到 service,那么参数 Intent 将为 null。

START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完 onStartCommand 后,服务被异常 kill 掉,系统不会自动重启该服务。

START_REDELIVER_INTENT:重传 Intent。使用这个返回值时,如果在执行完
onStartCommand 后,服务被异 常 kill 掉,系统会自动重启该服务,并将 Intent 的值传入。

START_STICKY_COMPATIBILITY:START_STICKY 的兼容版本,但不保证服务被 kill 后一定能重启。

Service 的 onRebind(Intent)方法在什么情况下会执行?
如果在 onUnbind()方法返回 true 的情况下会执行,否则不执行。

Activity 调用 Service 中的方法都有哪些方式?
Binder:
通过 Binder 接口的形式实现,当 Activity 绑定 Service 成功的时候 Activity 会在 ServiceConnection 的类 的 onServiceConnected()回调方法中获取到 Service 的 onBind()方法 return 过来的 Binder 的子类,然后通过对象调用方法。
Aidl:
aidl 比较适合当客户端和服务端不在同一个应用下的场景。

Messenger:
它引用了一个Handler对象,以便others能够向它发送消息(使用mMessenger.send(Message msg)方法)。该类允许跨进程间基于Message的通信(即两个进程间可以通过Message进行通信),在服务端使用Handler创建一个Messenger,客户端持有这个Messenger就可以与服务端通信了。一个Messeger不能同时双向发送,两个就就能双向发送了

如何提高service的优先级?
可以用 setForeground(true) 来设置 Service 的优先级。

service 如何定时执行?
使用AlarmManager,根据AlarmManager的工作原理,alarmmanager会定时的发出一条广播,然后在自己的项目里面注册这个广播,重写onReceive方法,在这个方法里面启动一个service,然后在service里面进行网络的访问操作,当获取到新消息的时候进行推送,同时再设置一个alarmmanager进行下一次的轮询,当本次轮询结束的时候可以stopself结束改service。这样即使这一次的轮询失败了,也不会影响到下一次的轮询。这样就能保证推送任务不会中断

在 service 的生命周期方法 onstartConmand()可不可以执行网络操作?如何在 service 中执行网络操作?
可以直接在 Service 中执行网络操作,在 onStartCommand()方法中可以执行网络操作

Service 和 Activity 在同一个线程吗?
对于同一 app 来说默认情况下是在同一个线程中的,main Thread (UI Thread)。

什么是 IntentService?有何优点?
会创建独立的工作线程来处理所有的 Intent 请求;
会创建独立的工作线程来处理 onHandleIntent()方法实现的代码,无需
处理多线程问题;
所有请求处理完成后,IntentService 会自动停止,无需调用 stopSelf()方法
停止 Service;
为 Service 的 onBind()提供默认实现,返回 null;
为 Service 的 onStartCommand 提供默认实现,将请求 Intent 添加到队列
中;

Activity 怎么和 Service 绑定,怎么在 Activity 中启动自 己对应的 Service?
Activity 通过 bindService(Intent service, ServiceConnection conn, int flags)跟 Service 进行绑定,当绑定成功的时候 Service 会将代理对象通过回调 的形式传给 conn,这样我们就拿到了 Service 提供的服务代理对象。
在 Activity 中可以通过 startService 和 bindService 方法启动 Service。一 般情况下如果想获取 Service 的服务对象那么肯定需要通过 bindService()方 法,比如音乐播放器,第三方支付等。如果仅仅只是为了开启一个后台任务那么 可以使用 startService()方法。

IntentService 适用场景
IntentService 内置的是 HandlerThread 作为异步线程,每一个交给 IntentService 的任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理
正在运行的 IntentService 的程序相比起纯粹的后台程序更不容易被系统杀死,该程序的优先级是介于前台程序与纯后台程序之间的






Broadcast Receiver

广播的定义:
在Android中,Broadcast是一种广泛运用在程序之间的传输信息的机制,Android中我们要发送的广播内容中是一个Intent,这个Intent可以携带我们要传输的数据

广播的使用场景:
1)在同一个App具有多个进程的不同组件之间的消息传递
2)不同app之间的消息通信

广播的种类:
1)普通广播
2)系统广播 (sendOrderedBroadcast)
3 ) 本地广播(只在app内部传播)

不同注册方式广播接收器回调onReceive(context, intent)中context类型不一致?
manifest静态注册的ContextReceiver,回调onReceive(context, intent)中的context是ReceiverRestrictedContext

代码动态注册的ContextReceiver,回调onReceive(context, intent)中的context是Activity Context;

内部实现机制?
1)自定义广播接受者Broadcast Receiver,并复写onRecvice方法
2)通过Binder机制像AMS进行注册
3)广播发送者通过Binder机制像AMS发送广播
4)AMS查找符合相应条件(IntentFilter/Permission等)的Broadcast Receiver,将广播发送到Broadcast Receiver相应的消息队列循环当中去(一般是Activity中)
5)消息循环拿到此广播,回调Broadcast Receiver中的onReceive方法、

如何让自己的广播只让指定的 app 接收?
通过自定义广播权限来保护自己发出的广播。 在清单文件里receiver必须有这个权限才能收到广播。 首先,需要定义权限: 然后,声明权限: 这时接收者就能收到发送的广播。

广播的优先级对无序广播生效吗?
生效的**

动态注册的广播优先级谁高?
谁先注册谁优先级高。

如何判断当前 BroadcastReceiver 接收到的是有序广播还是无序广播?
在 BroadcastReceiver 类中 onReceive()方法中,可以调用
boolean b = isOrderedBroadcast();判断接收到的广播是否为有序广播。

粘性广播有什么作用?怎么使用?

粘性广播主要为了解决,在发送完广播之后,动态注册的接收者,也能够收到广播。举个例子首先发送一广播,我的接收者是通过程序中的某个按钮动态注册的。如果不是粘性广播,我注册完接收者肯定无法收到广播了。这是通过发送粘性广播就能够在我动态注册接收者后也能收到广播。






网络

HttpConnection 与HttpURLConnection的区别?
在Android 2.2版本之前,HttpClient拥有较少的bug,因此使用它是最好的选择
2.3之后使用HttpURLConnection,它的API简单,体积较小,压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用,利于维护与优化

TCP与UDP的区别?
1)基于连接与无连接
2)对系统资源的要求(TCP较多,UDP较少)
3)UDP程序结构较简单一些
4)流模式与数据包模式
5)TCP保证数据的顺序性以及正确性,UDP不能保证,可能存在丢包

Socket:
soket是套接字,我们可以先在服务端初始化ServerSocket,然后对指定的端口进行绑定与监听,通过调用accept方法与getInputstream方法进行等待客户端的连接与数据的接收。现在客户端进行创建socket对象传入ip和端口号,通过getOutputStream进行数据的输入,并且制定结束字符,否则服务端会一直处于阻塞状态。

socket.close() 与socket.shutdownOutput()的区别?
1)在客户端或者服务端通过socket.shutdownOutput()都是单向关闭的,即关闭客户端的输出流并不会关闭服务端的输出流,所以是一种单方向的关闭流;
2)通过socket.shutdownOutput()关闭输出流,但socket仍然是连接状态,连接并未关闭
3)如果直接关闭输入或者输出流,即:in.close()或者out.close(),会直接关闭socket

socket长连接?
在不关闭流的情况下,开启循环线程进行定时发送与服务端约定的心跳包数据

http1.0与http1.1的区别:
1)缓存处理(缓存处理策略不同1.1更丰富)
2)带宽优化及网络连接的使用(1.1允许只请求某一部分的数据,不会浪费带宽)
3)Host头处理 (1.1请求消息支持Host头改进 )
4)长连接(1.1支持长连接 ,默认开启KeepAlive ,避免创建浪费的资源)

http1.1与1.0的存在问题:

a. 1.0在传输数据时,每次都需要重新建立连接,无疑增加了大量的延迟时间
b. 1.1在传输数据的时候传输的都是明文,客户端和服务端都无法验证对方的身份(https)
c. 1.1在使用的时候 ,header里携带的内容过大,在一定程度上增加了传输的成本
d. 虽然1.1支持了keep-alive来弥补多次创建连接产生的延迟,但是keep-alive使多了同样会给服务端带来大量的性能压力

get与post 的区别?
主要区别:get获取资源,post提供来更新服务器上的资源

a、提交的数据(get数据一般放在url之后用?分隔,post提交数据放在http包的body中)
b、提交数据大小限制问题(get有限制,url有限制,post没有限制)
c、取得变量值Requst.QueryStrig 与 Requst.Form的区别
d、安全问题(get用户名密码暴露在url上,不安全;post则不然)

cookie与session的区别??

a、存放位置不同(cookie保存在客户端,session保存在服务端)
b、存取方式不同(cookie保存的ASCII字符串,session可以存取任何类型的字符串)
c、安全性的不同(cookie存在客户端可能会被修改数据)
d、有效期上的不同(cookie可以设置很长的时间,session依赖id)
e、对服务器造成的压力不同(并发很多的时候可以选择cookie,服务器压力小)






Handler

什么是Handler?
handler通过发送和处理Message和Runnable对象来关联相对应线程的MessageQueue
作用:
1)可以让对应的Message和Runnable在未来的某个时间点进行相应处理
2)让自己想要处理的耗时操作放在子线程,让更新ui的操作放在主线程

Handler发送消息的几种方式?
1)post(Runnable)延迟消息处理
2)sendMessage(message)

PS:post(Runnable)的Runnable中可以更新UI吗?
可以的 ,因为经常会post一个Runnable,处理的代码直接写在Runnable的run方法中,其实就是将这个Runnable发送到Handler所在线程(一般是主线程)的消息队列中。sendMessage方法主线程处理方法一般则是写在handleMessage中

Handler处理消息有哪几种方式?

直接看dispatchMessage()源码

//1. post()方法的最终处理方法
private static void handleCallback(Message message) {
    message.callback.run();
}
//2. sendMessage()方法的最终处理方法
public void handleMessage(Message msg) {
}

Handler、Message、Looper、MessageQueue

Message(消息)
定义:Handler接收和处理的消息对象(Bean对象)
作用:通信时相关信息的存放和传递

ThreadLocal
定义:ThreadLocal是线程内部的存储类,通过它可以实现在每个线程中存储自己的私有数据。即数据存储以后,只能在指定的线程中获取这个存储的对象,而其它线程则不能获取到当前线程存储的这个对象。
作用:负责存储和获取本线程的Looper

MessageQueue(消息队列)
定义:采用单链表的数据结构来存储消息列表
作用:用来存放通过Handler发过来的Message,按照先进先出执行

Handler(处理者)
定义:Message的主要处理者
作用:负责发送Message到消息队列&处理Looper分派过来的Message

Looper(循环器)
定义:扮演Message Queue和Handler之间桥梁的角色
作用:
消息循环:循环取出Message Queue的Message
消息派发:将取出的Message交付给相应的Handler

首先知道定义人,然后结合关系说清楚一些在加上HandlerThread基本上Handler这块是没什么大问题了

Message、Handler、MessageQueue、Looper的之间的关系?
首先,是这个MessagQueue,MessageQueue是一个消息队列,它可以存储Handler发送过来的消息,其内部提供了进队和出队的方法来管理这个消息队列,其出队和进队的原理是采用单链表的数据结构进行插入和删除的,即enqueueMessage()方法和next()方法。这里提到的Message,其实就是一个Bean对象,里面的属性用来记录Message的各种信息。
然后,是这个Looper,Looper是一个循环器,它可以循环的取出MessageQueue中的Message,其内部提供了Looper的初始化和循环出去Message的方法,即prepare()方法和loop()方法。在prepare()方法中,Looper会关联一个MessageQueue,而且将Looper存进一个ThreadLocal中,在loop()方法中,通过ThreadLocal取出Looper,使用MessageQueue的next()方法取出Message后,判断Message是否为空,如果是则Looper阻塞,如果不是,则通过dispatchMessage()方法分发该Message到Handler中,而Handler执行handlerMessage()方法,由于handlerMessage()方法是个空方法,这也是为什么需要在Handler中重写handlerMessage()方法的原因。这里要注意的是Looper只能在一个线程中只能存在一个。这里提到的ThreadLocal,其实就是一个对象,用来在不同线程中存放对应线程的Looper。
最后,是这个Handler,Handler是Looper和MessageQueue的桥梁,Handler内部提供了发送Message的一系列方法,最终会通过MessageQueue的enqueueMessage()方法将Message存进MessageQueue中。我们平时可以直接在主线程中使用Handler,那是因为在应用程序启动时,在入口的main方法中已经默认为我们创建好了Looper。

Handler引起的内存泄漏以及解决办法?

HandlerThread作用
当系统有多个耗时任务需要执行时,每个任务都会开启一个新线程去执行耗时任务,这样会导致系统多次创建和销毁线程,从而影响性能。为了解决这一问题,Google提供了HandlerThread,HandlerThread是在线程中创建一个Looper循环器,让Looper轮询消息队列,当有耗时任务进入队列时,则不需要开启新线程,在原有的线程中执行耗时任务即可,否则线程阻塞。

HandlerThread是什么?有哪些特点

1)HandlerThread本质上是一个线程类,它继承了Thread
2)HandlerThread有自己内部Looper对象,可以进行Looper循环
3)通过获取HandlerThread的looper对象传递给Handler对象,可以在handlerMessage方法中执行异步任务
4)优点是不会阻塞,减少对性能的消耗,缺点是不能同时进行多任务的处理,需要等待进行处理
5)与线程池注重并发不同,HandlerThread是一个串行队列,HandlerThread背后只有一个线程

子线程为什么不能开启handler?
handler在调用sendMessage或者post(Runnable)的时候都需要一个MessageQueue 消息队列来保存发送的消息,而默认子线程中是没有开启Looper轮循器的,而消息队列又是由Looper来进行管理的,所以是没有办法开启的,
如果子线程想要开启,需要初始化looper,并且调用loop.loopers开启一个循环

如果简历中写了AsyncTask,就看一下,没写的直接跳过上Rxjava

AsyncTask是什么
AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并主线程中更新UI,通过AsyncTask可以更加方便执行后台任务以及在主线程中访问UI,但是AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。

AsyncTask使用方法
三个参数
Params:表示后台任务执行时的参数类型,该参数会传给AysncTask的doInBackground()方法
Progress:表示后台任务的执行进度的参数类型,该参数会作为onProgressUpdate()方法的参数
Result:表示后台任务的返回结果的参数类型,该参数会作为onPostExecute()方法的参数
五个方法
onPreExecute():异步任务开启之前回调,在主线程中执行
doInBackground():执行异步任务,在线程池中执行
onProgressUpdate():当doInBackground中调用publishProgress时回调,在主线程中执行
onPostExecute():在异步任务执行之后回调,在主线程中执行
onCancelled():在异步任务被取消时回调

AsyncTask引起的内存泄漏
原因:非静态内部类持有外部类的匿名引用,导致Activity无法释放
解决:
AsyncTask内部持有外部Activity的弱引用
AsyncTask改为静态内部类
Activity的onDestory()中调用AsyncTask.cancel()

结果丢失
屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。

AsyncTask并行or串行
AsyncTask在Android 2.3之前默认采用并行执行任务,AsyncTask在Android 2.3之后默认采用串行执行任务
如果需要在Android 2.3之后采用并行执行任务,可以调用AsyncTask的executeOnExecutor();

AsyncTask内部的线程池
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
sDefaultExecutor是SerialExecutor的一个实例,而且它是个静态变量。也就是说,一个进程里面所有AsyncTask对象都共享同一个SerialExecutor对象。






ListView+RecyclerView

既然RecyclerView在很多方面能取代ListView,Google为什么没把ListView划上一条过时的横线?
ListView采用的是RecyclerBin的回收机制在一些轻量级的List显示时效率更高

ListView怎么和ScrollView兼容?
方法一:重写ListView, 覆盖onMeasure()方法
方法二:动态设置listview的高度,不需要重写ListView
方法三:在xml文件中,直接将Listview的高度写死

listview怎么优化?
1)、convertView复用,对convetView进行判空,当convertView不为空时重复使用,为空则初始化,从而减少了很多不必要的View的创建
2)定义一个ViewHolder,封装Listview Item条目中所有的组件,将convetView的tag设置为ViewHolder,不为空时通过ViewHolder的属性获取对应组件即可
3)、当ListView加载数据量较大时可以采用分页加载和图片异步加载

上拉加载和下拉刷新怎么实现?
实现OnScrollListener 接口重写onScrollStateChanged 和onScroll方法,
使用onscroll方法实现”滑动“后处理检查是否还有新的记录,如果有,调用 addFooterView,添加记录到adapter, adapter调notifyDataSetChanged 更新数据;如果没有记录了,把自定义的mFooterView去掉。使用onScrollStateChanged可以检测是否滚到最后一行且停止滚动然后执行加载

listview失去焦点怎么处理?
在listview子布局里面写,可以解决焦点失去的问题
android:descendantFocusability=”blocksDescendants”

ListView图片异步加载实现思路?
1.先从内存缓存中获取图片显示(内存缓冲)
2.获取不到的话从SD卡里获取(SD卡缓冲,,从SD卡获取图片是放在子线程里执行的,否则快速滑屏的话会不够流畅)
3.都获取不到的话从网络下载图片并保存到SD卡同时加入内存并显示(视情况看是否要显示)

你知道Listview里有Button就点不动了你知道吗?
原因是button强制获取了item的焦点,只要设置button的focusable为false即可。

listview分页加载的步骤?
通常实现分页加载有两种方式,一种是在ListView底部设置一个按钮,用户点击即加载。另一种是当用户滑动到底部时自动加载。
当用户滑动到底部时自动加载实现思路:
实现OnScrollListener 接口重写onScrollStateChanged 和onScroll方法,使用onscroll方法实现”滑动“后处理检查是否还有新的记录,如果有,添加记录到adapter, adapter调用 notifyDataSetChanged 更新数据;如果没有记录了,则不再加载数据。使用onScrollStateChanged可以检测是否滚到最后一行且停止滚动然后执行加载.

ViewHolder内部类非得要声明成static的呢?
因为非静态成员类的实例会包含一个额外的指向外围对象的引用,保存这份引用要消耗时间和空间,并且导致外围类实例符合垃圾回收时仍然被保留。如果没有外围实例的情况下,也需要分配实例,就不能使用非静态成员类,因为非静态成员类的实例必须要有一个外围实例。

ScrollView嵌套ListView和GridView冲突的方法
重写ListView的onMeasure方法,来自定义高度:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }

BaseAdapter四个关键方法的含义?
1) getCount()
返回数据源中数据的个数,如果该方法的返回值为0,那么适配器就不用生成布局对象了,提高了程序性能
2)getItem(int position)
根据位置返回数据项
3)getItemId(int position)
返回数据项的位置
4)getView(int position, View convertView, ViewGroup parent)

RecyclerView滑动删除原理实现?
两种方法:
一种就是通过重写RecyclerView的onTouchEvent()方法来检测手势的变化实现的,大致的流程如下:
1、根据手指触摸的坐标点找到对应Item的ViewHolder,进而得到相应的Item布局View。
2、手指继续移动,在条件满足的情况下,通过scrollBy()使Item布局View内容跟随手指一起移动,当然要注意边界检测。
3、手指抬起时,根据Item布局View内容移动的距离以及手指的滑动速度,判断是否显示删除按钮,进而通过startScroll()使Item布局View自动滑动到目标位置。
4、点击删除按钮则删除对应Item,点击其它区域则隐藏删除按钮。

另外一种:
实现原理
主要是借助 ItemTouchHelper.Callback 类来实现,我们要关注的方法为
* getMovementFlags( )
* onMove()
* onSwiped()
* onSelectedChanged()
* clearView()
* isLongPressDragEnabled()

首先自定义一个MyCallback类继承 ItemTouchHelper.Callback ,定义两个int变量dragFlags 与 swipeFlags并实现下方法。这个方法主要是为了获取我们当前的事件是拖动还是滑动






其他:

Android View刷新机制?
在Android的布局体系中,父View负责刷新、布局显示子View;而当子View需要刷新时,则是通知父View来完成

RelativeLayout和LinearLayout性能比较?
1.RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子View2次onMeasure
2.RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。
3.在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。

View和ViewGroup什么区别?
Android的UI界面都是由View和ViewGroup及其派生类组合而成的。其中,View是所有UI组件的基类,而ViewGroup是容纳这些组件的容器,其本身也是从View派生出来的

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

View树的绘制流程?
measure>layout>draw

View树的绘制流程就像是一个递归过程,在onMeasure方法中,它的view会对它的所有子元素进行测量,测量过程就从它的父的ViewGroup传递到子的view,经过子元素的递归,测量好了所有的子元素的长度之后,进行一个递归,反复之后就完成了整个父元素ViewGroup的测量,而layout也是相类似树的递归过程

onMeasure:
ViewGroup.LayoutParams:用来指定视图高度个宽度的参数。
MeasureSpec:测量规格,32位的int值,前两位是测量模式,后者表示在这种模式下的尺寸的大小

开始于我们的父控件ViewGroup,它会不断的遍历子控件的measure方法,然后根据ViewGroup的MeasureSpec和子View的LayoutParams来决定我们的子视图的MeasureSpec测量规格,通过这个测量规格,MeasureSpec进一步获取到子View的宽高,然后一层一层的向下传递,不断的保存父控件的测量宽高,整个Measure的测量流程就是一个树型的递归流程

layout:
layout其实也是一个树形的结构,所以当它进行数据摆放的时候,他会以此的从ViewGroup调用它的子控件,所以也是依次进行的数据摆放,这就是layout与measure的相同的地方

onDraw:
经过测量和摆放之后进行绘制,需要注意两个容易混淆的方法:
invalidate:invalidate()是用来刷新View的,必须是在UI线程中进行工作。比如在修改某个view的显示时,调用invalidate()才能看到重新绘制的界面。invalidate()的调用是把之前的旧的view从主UI线程队列中pop掉。

requestlayout:当布局发生变化的时候,方向变化或者尺寸变化,某些情况下重新测量view的大小啊,调用完改方法后就会触发它的measure onlayout过程,但是不会调用onDraw方法。






音视频

SurfaceView是什么 ?
它继承自类View,因此它本质上是一个View。但与普通View不同的是,它有自己的Surface。有自己的Surface,在WMS中有对应的WindowState,在SurfaceFlinger中有Layer。我们知道,一般的Activity包含的多个View会组成View hierachy的树形结构,只有最顶层的DecorView,也就是根结点视图,才是对WMS可见的。这个DecorView在WMS中有一个对应的WindowState。相应地,在SF中对应的Layer。而SurfaceView自带一个Surface,这个Surface在WMS中有自己对应的WindowState,在SF中也会有自己的Layer。虽然在App端它仍在View hierachy中,但在Server端(WMS和SF)中,它与宿主窗口是分离的。这样的好处是对这个Surface的渲染可以放到单独线程去做,渲染时可以有自己的GL context。这对于一些游戏、视频等性能相关的应用非常有益,因为它不会影响主线程对事件的响应。但它也有缺点,因为这个Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中,一些View中的特性也无法使用。

SurfaceView优点及缺点?
优点:可以在一个独立的线程中进行绘制,不会影响主线程。使用双缓冲机制,播放视频时画面更流畅

缺点:Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中。SurfaceView 不能嵌套使用

SurfaceView中双缓冲?
双缓冲:在运用时可以理解为:SurfaceView在更新视图时用到了两张Canvas,一张frontCanvas和一张backCanvas,每次实际显示的是frontCanvas,backCanvas存储的是上一次更改前的视图,当使用lockCanvas()获取画布时,得到的实际上是backCanvas而不是正在显示的frontCanvas,之后你在获取到的backCanvas上绘制新视图,再unlockCanvasAndPost(canvas)此视图,那么上传的这张canvas将替换原来的frontCanvas作为新的frontCanvas,原来的frontCanvas将切换到后台作为backCanvas。例如,如果你已经先后两次绘制了视图A和B,那么你再调用lockCanvas()获取视图,获得的将是A而不是正在显示的B,之后你讲重绘的C视图上传,那么C将取代B作为新的frontCanvas显示在SurfaceView上,原来的B则转换为backCanvas。

TextureView是什么?
在4.0(API level 14)中引入,与SurfaceView一样继承View, 它可以将内容流直接投影到View中,它可以将内容流直接投影到View中,可以用于实现Live preview等功能。和SurfaceView不同,它不会在WMS中单独创建窗口,而是作为View hierachy中的一个普通View,因此可以和其它普通View一样进行移动,旋转,缩放,动画等变化。值得注意的是TextureView必须在硬件加速的窗口中。它显示的内容流数据可以来自App进程或是远端进程。从类图中可以看到,TextureView继承自View,它与其它的View一样在View hierachy中管理与绘制。TextureView重载了draw()方法,其中主要SurfaceTexture中收到的图像数据作为纹理更新到对应的HardwareLayer中。SurfaceTexture.OnFrameAvailableListener用于通知TextureView内容流有新图像到来。SurfaceTextureListener接口用于让TextureView的使用者知道SurfaceTexture已准备好,这样就可以把SurfaceTexture交给相应的内容源。Surface为BufferQueue的Producer接口实现类,使生产者可以通过它的软件或硬件渲染接口为SurfaceTexture内部的BufferQueue提供graphic buffer。

TextureView优点及缺点?

优点:支持移动、旋转、缩放等动画,支持截图

缺点:必须在硬件加速的窗口中使用,占用内存比SurfaceView高,在5.0以前在主线程渲染,5.0以后有单独的渲染线程。

谁的性能更优?
这里写图片描述

播放器应该选择谁?

从性能和安全性角度出发,使用播放器优先选SurfaceView。
1、在android 7.0上系统surfaceview的性能比TextureView更有优势,支持对象的内容位置和包含的应用内容同步更新,平移、缩放不会产生黑边。 在7.0以下系统如果使用场景有动画效果,可以选择性使用TextureView

2、由于失效(invalidation)和缓冲的特性,TextureView增加了额外1~3帧的延迟显示画面更新

3、TextureView总是使用GL合成,而SurfaceView可以使用硬件overlay后端,可以占用更少的内存带宽,消耗更少的能量

4、TextureView的内部缓冲队列导致比SurfaceView使用更多的内存

5、SurfaceView: 内部自己持有surface,surface 创建、销毁、大小改变时系统来处理的,通过surfaceHolder 的callback回调通知。当画布创建好时,可以将surface绑定到MediaPlayer中。SurfaceView如果为用户可见的时候,创建SurfaceView的SurfaceHolder用于显示视频流解析的帧图片,如果发现SurfaceView变为用户不可见的时候,则立即销毁SurfaceView的SurfaceHolder,以达到节约系统资源的目的

视频编码标准两大系统是什么?

视频编码标准有两大系统:MPEG和ITU-T,如下
视频编码标准
MPEG标准由MPEG制定
MPEG-1 | MPEG-2 | (MPEG-3) | MPEG-4 | MPEG-7 | MPEG-21
ITU-T标准由VCEG制定
H.261 | (H.262) | H.263 | H.263v2 | H.264

什么是音视频编码格式?什么是音视频封装格式?

常见的AVI、RMVB、MKV、ASF、WMV、MP4、3GP、FLV等文件其实只能算是一种封装标准。

一个完整的视频文件是由音频和视频2部分组成的。H264、Xvid等就是视频编码格式,MP3、AAC等就是音频编码格式。

例如:将一个Xvid视频编码文件和一个MP3视频编码文件按AVI封装标准封装以后,就得到一个AVI后缀的视频文件,这个就是我们常见的AVI视频文件了。

由于很多种视频编码文件、音频编码文件都符合AVI封装要求,则意味着即使是AVI后缀,也可能里面的具体编码格式不同。因此出现在一些设备上,同是AVI后缀文件,一些能正常播放,还有一些就无法播放。

同样的情况也存在于其他容器格式。即使RMVB、WMV等也不例外,事实上,很多封装容器对音频编码和视频编码的组合方式放的很开,如AVI还可以使用H.264+AAC组合,可以在具体使用中自己体会。尤其是MKV封装容器,基本无论什么样的组合都可以!但一般MKV用的最多的就是H.264+AAC组合,此组合文件体积最小,清晰度最高。因此网上很多MKV视频都是高清晰度的。

因此,视频转换需要设置的本质就是:A设置需要的视频编码、B设置需要的音频编码、C选择需要的容器封装。一个完整的视频转换设置都至少包括了上面3个步骤。

平时说的软解和硬解,具体是什么?

硬解就是硬件解码,指利用GPU来部分代替CPU进行解码,软解就是软件解码,指利用软件让CPU来进行解码。两者的具体区别如下所示:

硬解码:是将原来全部交由CPU来处理的视频数据的一部分交由GPU来做,而GPU的并行运算能力要远远高于CPU,这样可以大大的降低对CPU的负载,CPU的占用率较低了之后就可以同时运行一些其他的程序了,当然,对于较好的处理器来说,比如i5 2320,或者AMD 任何一款四核心处理器来说,硬解和软件的区别只是个人偏好问题了吧。  

软解码:即通过软件让CPU来对视频进行解码处理;而硬解码:指不借助于CPU,而通过专用的子卡设备来独立完成视频解码任务。曾经的VCD/DVD解压卡、视频压缩卡等都隶属于硬解码这个范畴。而现如今,要完成高清解码已经不再需要额外的子卡,因为硬解码的模块已经被整合到显卡GPU的内部,所以目前的主流显卡(集显)都能够支持硬解码技术。

何为直播?何为点播?

直播:是一个三方交互(主播、服务器、观众),这个交互式实时的!尽管会根据选择的协议不同而有一些延迟,但我们仍认为它直播是实时的!—>主播在本地发送音视频给服务器(推流),观众从服务器实时解码(拉流)收看收听主播发送给服务器的音视频(直播内容)。直播是不能快进的
点播:首先一定要明确的一点,点播不存在推流这一过程,你本身你的流已经早就推给服务器了,或者这么说也不对,应该是你的音视频早就上传到了服务器,观众只需要在线收看即可,由于你的音视频上传到了服务器,观众则可以通过快进,快退,调整进度条等方式进行收看!

简述推流、拉流的工作流程?
推流:在直播中,一方向服务器发送请求,向服务器推送自己正在实时直播的数据,而这些内容在推送到服务器的这一过程中是以 “流” 的形式传递的,这就是“推流”,把音视频数据以流的方式推送(或上传)到服务器的过程就是“推流”!推流方的音视频往往会很大,在推流的过程中首先按照 aac音频-编码 和 h264视频-编码的标准把推过来的音视频压缩 ,然后合并成 MP4或者 FLV格式,然后根据直播的封装协议,最后传给服务器完成推流过程。

拉流:与推流正好相反,拉流是用户从服务器获取推流方给服务器的音视频的过程,这就是“拉流”!拉流首先aac音频-解码 和 h.264视频-解码的内部把推过来的音视频解压缩,然后合成 MP4或者 FLV 格式,再解封装,最后到我们的客户端与观众进行交互。

常见的直播协议有哪些?之间有什么区别?

常见的直播协议有三种 RTMP、HLS、FLV…

1)RTMP:real time messaging protocol~实时传输协议,RTMP协议比较全能,既可以用来推送又可以用来直播,其核心理念是将大块的视频帧和音频帧“剁碎”,然后以小数据包的形式在互联网上进行传输,而且支持加密,因此隐私性相对比较理想,但拆包组包的过程比较复杂,所以在海量并发时也容易出现一些不可预期的稳定性问题。

2)FLV:FLV协议由Adobe公司主推,格式极其简单,只是在大块的视频帧和音视频头部加入一些标记头信息,由于这种极致的简洁,在延迟表现和大规模并发方面都很成熟。唯一的不足就是在手机浏览器上的支持非常有限,但是用作手机端APP直播协议却异常合适。

3)HLS:苹果原生:HTTP Live Streaming,遵循的是 HTTP 超文本传输协议,端口号8080,将视频分成5-10秒的视频小分片,然后用m3u8索引表进行管理,由于客户端下载到的视频都是5-10秒的完整数据,故视频的流畅性很好,但也同样引入了很大的延迟(HLS的一般延迟在10-30s左右)。

点播中常见的数据传输协议主要有哪些?

常见的点播协议:HLS,HTTP

何为Nginx?有什么特点?
Nginx 是一个遵循 HTTP 协议的服务器!内存占用少,并发能力强! 还有等等的优点,可自行google

何为homebrew?你用它安装过什么?常用命令有哪些?
homebrew是一个 Mac系统下所独有的套件管理器,我要做直播,需要 rtmp 和 nginx ,单独安装很复杂,只要在终端里输入简单的安装相应的套件命令即可完成安装,复杂的过程都靠 homebrew 规避掉了!
我用它安装过很多东西,比如nginx 搭建流媒体服务器等。
常用命令:brew install 、brew uninstall、brew search、brew list、brew update、brew help 等~

FFmpeg是什么?

FFmpeg是一套用来记录和转换数字音视频,并能将其转化为流的开源计算机程序。拉流和推流离不开 FFmpeg 的帮助!

RTMP、HLS协议各自的默认端口号是?

RTMP端口号:1935
HLS端口号 :8080

m3u8构成是?直播中m3u8、ts如何实时更新?

是一个索引地址/播放列表,通过FFmpeg将本地的xxx.mp4进行切片处理,生成m3u8播放列表(索引文件)和N多个 .ts文件,并将其(m3u8、N个ts)放置在本地搭建好的webServer服务器的指定目录下,我就可以得到一个可以实时播放的网址,我们把这个m3u8地址复制到 VLC 上就可以实时观看!
在 HLS 流下,本地视频被分割成一个一个的小切片,一般10秒一个,这些个小切片被 m3u8管理,并且随着终端的FFmpeg 向本地拉流的命令而实时更新,影片进度随着拉流的进度而更新,播放过的片段不在本地保存,自动删除,直到该文件播放完毕或停止,ts 切片会相应的被删除,流停止,影片不会立即停止,影片播放会滞后于拉流一段时间

PS: FFmpeg推流至Nginx:可以推两种流:RTMP流,推流至rtmplive;HLS流,推流至hls;其中,HLS流表现较明显,在nginx的临时目录下,直观的可看到m3u8索引文件和N多个.ts文件。m3u8列表会实时更新,且会动态更改当前播放索引切片(.ts)。这种实时更新的机制,不会使得.ts文件长时间存在于Nginx服务器上,且当推流结束之后,该目录下的内容会被全部清除,这样无形中减缓了nginx服务器的压力。另外,也阐释了HLS这种流媒体播放相较RTMP延时较高的原因。

说说你平时在播放过程中做的优化工作
预加载,弱网优化,播放出错重试机制,运营商劫持引起的起播慢,mediaserver的cpu占有率很高,引起播放卡顿。起播时,只保留播放进程,kill 其他进程 。

未完待续

  • 10
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值