今日头条Android面试的10道题

Android开发面试集合(准备阶段) 1.你提到深度集成了ExoPlayer,请具体说明在实现视频播放功能时遇到了哪些技术难点?如何解决的?2.在适配Android TV的大屏设备时,如何处理UI布局和焦点聚焦的优化?是否有遇到过特别棘手的适配问题?3.你是如何实现分页加载和滚动体验优化的?这种优化对性能提升有什么具体效果? 阅读详情

首先说一下,今日头条的面试主要分为三轮到四轮,如果是旺季面三轮,首先是基础面试,基本面试一般10个题左右,最近面试了一下今日头条的移动Android资深工程师,记录下。
第一面是北京的开发进行视频面试,有理论和编程题组成。用的是在线编程工具,如下图。

 

这里写图片描述

第一面
1,请编程实现单例模式,懒汉和饱汉写法。

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;">//饱汉写法public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
} //懒汉写法
private static final Singleton1 single = new Singleton1();
//静态工厂方法
public static Singleton1 getInstance() {
return single;
} 123456789101112131415161718</pre>

2,请编程实现Java的生产者-消费者模型
看到这个有点懵逼,要是大学毕业的时候写这个肯定没问题,这都工作多年,这也只能按照自己的思路写了。这里使用synchronized锁以及wait notify实现一个比较简单的。

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;">import java.io.IOException;public class WaitAndNotify{
public static void main(String[] args) throws IOException
{
Person person = new Person(); new Thread(new Consumer("消费者一", person)).start(); new Thread(new Consumer("消费者二", person)).start(); new Thread(new Consumer("消费者三", person)).start(); new Thread(new Producer("生产者一", person)).start(); new Thread(new Producer("生产者一", person)).start(); new Thread(new Producer("生产者一", person)).start();

}

}

class Producer implements Runnable
{ private Person person; private String producerName; public Producer(String producerName, Person person)
{ this.producerName = producerName; this.person = person;
} @Override
public void run()
{ while (true)
{ try
{
person.produce();
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}

}

class Consumer implements Runnable
{ private Person person; private String consumerName; public Consumer(String consumerName, Person person)
{ this.consumerName = consumerName; this.person = person;
} @Override
public void run()
{ try
{
person.consume();
} catch (InterruptedException e)
{
e.printStackTrace();
}

}

}

class Person
{ private int foodNum = 0; private Object synObj = new Object(); private final int MAX_NUM = 5; public void produce() throws InterruptedException
{ synchronized (synObj)
{ while (foodNum == 5)
{
System.out.println("box is full,size = " + foodNum);
synObj.wait();
}
foodNum++;
System.out.println("produce success foodNum = " + foodNum);
synObj.notifyAll();
}

}    public void consume() throws InterruptedException
{        synchronized (synObj)
    {            while (foodNum == 0)
        {
            System.out.println("box is empty,size = " + foodNum);
            synObj.wait();
        }
        foodNum--;
        System.out.println("consume success foodNum = " + foodNum);
        synObj.notifyAll();
    }

}

}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112</pre>

关于更多的知识可以https://zhuanlan.zhihu.com/p/20300609
3,HashMap的内部结构? 内部原理?
关于HashMap的问题,不再详述,这方面的资料也挺多,不多需要注意的是Java1.7和1.8版本HashMap内部结构的区别。
4,请简述Android事件传递机制, ACTION_CANCEL事件何时触发?
关于第一个问题,不做任何解释。
关于ACTION_CANCEL何时被触发,系统文档有这么一种使用场景:在设计设置页面的滑动开关时,如果不监听ACTION_CANCEL,在滑动到中间时,如果你手指上下移动,就是移动到开关控件之外,则此时会触发ACTION_CANCEL,而不是ACTION_UP,造成开关的按钮停顿在中间位置。
意思是当滑动的时候就会触发,不知道大家搞没搞过微信的长按录音,有一种状态是“松开手指,取消发送”,这时候就会触发ACTION_CANCEL。

5,Android的进程间通信,Liunx操作系统的进程间通信。
关于这个问题也是被问的很多,此处也不做解释。

6,JVM虚拟机内存结构,以及它们的作用。
这个问题也比较基础,JVM的内存结构如下图所示。

 

这里写图片描述

可以通过下面的问题来学习:
https://www.cnblogs.com/jiyukai/p/6665199.html
https://www.zhihu.com/question/65336620

7,简述Android的View绘制流程,Android的wrap_content是如何计算的。

8,有一个整形数组,包含正数和负数,然后要求把数组内的所有负数移至正数的左边,且保证相对位置不变,要求时间复杂度为O(n), 空间复杂度为O(1)。例如,{10, -2, 5, 8, -4, 2, -3, 7, 12, -88, -23, 35}变化后是{-2, -4,-3, -88, -23,5, 8 ,10, 2, 7, 12, 35}。

要实现上面的效果有两种方式:
第一种:两个变量,一个用来记录当前的遍历点,一个用来记录最左边的负数在数组中的索引值。然后遍历整个数组,遇到负数将其与负数后面的数进行交换,遍历结束,即可实现负数在左,正数在右。

第二种:两个变量记录左右节点,两边分别开始遍历。左边的节点遇到负值继续前进,遇到正值停止。右边的节点正好相反。然后将左右节点的只进行交换,然后再开始遍历直至左右节点相遇。这种方式的时间复杂度是O(n).空间复杂度为O(1)

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;">//方法1
public void setParted(int[] a){
int temp=0;
int border=-1;

    for(int i=0;i<a.length;i++){  
        if(a[i]<0){  
            temp=a[i];  
            a[i]=a[border+1];  
            a[border+1]=temp;  
            border++;  
        }  
    }  
    for(int j=0;j<a.length;j++){  
        System.out.println(a[j]);  
    }  
}  

//方法2public void setParted1(int[] a,int left,int right){
if(left>=right||left==a.length||right==0){
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}
return ;
}
while(a[left]<0){
left++;
}
while(a[right]>=0){
right--;
}
if(left>=right||left==a.length||right==0){
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}
return;
}
swap(a,left,right);
left++;
right--;
setParted1(a,left,right);
}
private void swap(int a[],int left,int right){
int temp=0;
temp=a[left];
a[left]=a[right];
a[right]=temp;
}
public static void main(String[] args) {
int a[]={1,2,-1,-5,-6,7,-7,-10};
new PartTest().setParted1(a,0,a.length-1);
} 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253</pre>

显然,第二种实现的难点比较高,不过只要此种满足条件。

第二面
1,bundle的数据结构,如何存储,既然有了Intent.putExtra,为啥还要用bundle。

bundle的内部结构其实是Map,传递的数据可以是boolean、byte、int、long、float、double、string等基本类型或它们对应的数组,也可以是对象或对象数组。当Bundle传递的是对象或对象数组时,必须实现Serializable 或Parcelable接口。

2,android的IPC通信方式,是否使用过
这方面的资料比较多,也不方便阐述

3,Android的多点触控如何传递
核心类

4,asynctask的原理
AsyncTask是对Thread和Handler的组合包装。
https://blog.youkuaiyun.com/iispring/article/details/50670388
5,android 图片加载框架有哪些,对比下区别
主要有4种:Android-Universal-Image-Loader、Picasso、Glide和Fresco
Android-Universal-Image-Loader
优点:支持下载进度监听(ImageLoadingListener) * 可在View滚动中暂停图片加载(PauseOnScrollListener) * 默认实现多种内存缓存算法(最大最先删除,使用最少最先删除,最近最少使用,先进先删除,当然自己也可以配置缓存算法)
缺点:2015年之后便不再维护,该库使用前需要进行配置。
Picasso
优点:包较小(100k) * 取消不在视野范围内图片资源的加载 * 使用最少的内存完成复杂的图片转换 * 自动添加二级缓存 * 任务调度优先级处理 * 并发线程数根据网络类型调整 * 图片的本地缓存交给同为Square出品的okhttp处理,控制图片的过期时间。
缺点:
功能较为简单,自身无法实现“本地缓存”功能。
Glide
优点:多种图片格式的缓存,适用于更多的内容表现形式(如Gif、WebP、缩略图、Video) * 生命周期集成(根据Activity或者Fragment的生命周期管理图片加载请求) * 高效处理Bitmap(bitmap的复用和主动回收,减少系统回收压力) * 高效的缓存策略,灵活(Picasso只会缓存原始尺寸的图片,Glide缓存的是多种规格),加载速度快且内存开销小(默认Bitmap格式的不同,使得内存开销是Picasso的一半)。
缺点:方法较多较复杂,因为相当于在Picasso上的改进,包较大(500k),影响不是很大。
Fresco
缺点:最大的优势在于5.0以下(最低2.3)的bitmap加载。在5.0以下系统,Fresco将图片放到一个特别的内存区域(Ashmem区) * 大大减少OOM(在更底层的Native层对OOM进行处理,图片将不再占用App的内存) * 适用于需要高性能加载大量图片的场景。
缺点:包较大(2~3M) * 用法复杂 * 底层涉及c++领域

5,主线程中的Looper.loop()一直无限循环为什么不会造成ANR?
ActivityThread.java 是主线程入口的类,ActivityThread.java 的main函数的内容如下。

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;"> Looper.prepareMainLooper(); ...
//创建Looper和MessageQueue
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
} if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
} // End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); //轮询器开始轮询
Looper.loop();123456789101112131415161718192021</pre>

然后再看Looper.loop()的源码,可以发现:

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;">while (true) {
//取出消息队列的消息,可能会阻塞 Message msg = queue.next(); // might block …
//解析消息,分发消息 msg.target.dispatchMessage(msg);
…
}12345678</pre>

显然,ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的应用也就退出了。那么这个死循环不会造成ANR异常呢?
说明:因为Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。也就说我们的代码其实就是在这个循环里面去执行的,当然不会阻塞了。来看一下handleMessage的源码:

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;">public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, “>>> handling: ” + codeToString(msg.what)); switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “activityStart”); final ActivityClientRecord r = (ActivityClientRecord) msg.obj; r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); }
break; case RELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “activityRestart”); ActivityClientRecord r = (ActivityClientRecord) msg.obj; handleRelaunchActivity(r); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); }
break; case PAUSE_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “activityPause”); handlePauseActivity((IBinder) msg.obj, false, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 2) != 0); maybeSnapshot(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case PAUSE_ACTIVITY_FINISHING:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “activityPause”); handlePauseActivity((IBinder) msg.obj, true, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 1) != 0); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; ...
}
}1234567891011121314151617181920212223242526272829303132</pre>

可以看见Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施。
如果某个消息处理时间过长,比如你在onCreate(),onResume()里面处理耗时操作,那么下一次的消息比如用户的点击事件不能处理了,整个循环就会产生卡顿,时间一长就成了ANR。

总结:Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。

6,图片框架的一些原理知识

7,其他的一些Android的模块化开发,热更新,组件化等知识。

Android面试之主流框架

在Android面试的时候,经常会被问到一些Android开发中用到的一些开发框架,如常见的网络请求框架Retrofit/OkHttp,组件通信框架EventBus/Dagger2,异步编程RxJava/RxAndroid等。本文给大家整理下上面的几个框架,以备面试用。

EventBus

EventBus是一个Android发布/订阅事件总线,简化了组件间的通信,让代码更加简介,但是如果滥用EventBus,也会让代码变得更加辅助。面试EventBus的时候一般会谈到如下几点:

(1)EventBus是通过注解+反射来进行方法的获取的

注解的使用:@Retention(RetentionPolicy.RUNTIME)表示此注解在运行期可知,否则使用CLASS或者SOURCE在运行期间会被丢弃。
通过反射来获取类和方法:因为映射关系实际上是类映射到所有此类的对象的方法上的,所以应该通过反射来获取类以及被注解过的方法,并且将方法和对象保存为一个调用实体。

(2)使用ConcurrentHashMap来保存映射关系

调用实体的构建:调用实体中对于Object,也就是实际执行方法的对象不应该使用强引用而是应该使用弱引用,因为Map的static的,生命周期有可能长于被调用的对象,如果使用强引用就会出现内存泄漏的问题。

说明:并发编程实践中,ConcurrentHashMap是一个经常被使用的数据结构,相比于Hashtable以及Collections.synchronizedMap(),ConcurrentHashMap在线程安全的基础上提供了更好的写并发能力,但同时降低了对读一致性的要求。详情可以查看下面的文章:
http://www.importnew.com/22007.html。

(3)方法的执行

使用Dispatcher进行方法的分派,异步则使用线程池来处理,同步就直接执行,而UI线程则使用MainLooper创建一个Handler,投递到主线程中去执行。

Retrofit

首先要明确EventBus中最核心的就是动态代理技术。

Java中的动态代理:

首先动态代理是区别于静态代理的,代理模式中需要代理类和实际执行类同时实现一个相同的接口,并且在每个接口定义的方法前后都要加入相同的代码,这样有可能很多方法代理类都需要重复。而动态代理就是将这个步骤放入运行时的过程,一个代理类只需要实现InvocationHandler接口中的invoke方法,当需要动态代理时只需要根据接口和一个实现了InvocationHandler的代理对象A生成一个最终的自动生成的代理对象A。这样最终的代理对象A无论调用什么方法,都会执行InvocationHandler的代理对象A的invoke函数,你就可以在这个invoke函数中实现真正的代理逻辑。

动态代理的实现机制实际上就是使用Proxy.newProxyInstance函数为动态代理对象A生成一个代理对象A的类的字节码从而生成具体A对象过程,这个A*类具有几个特点,一是它需要实现传入的接口,第二就是所有接口的实现中都会调用A的invoke方法,并且传入相应的调用实际方法(即接口中的方法)。

Retrofit中的动态代理

Retrofit中使用了动态代理是不错,但是并不是为了真正的代理才使用的,它只是为了动态代理一个非常重要的功能,就是“拦截”功能。我们知道动态代理中自动生成的A对象的所有方法执行都会调用实际代理类A中的invoke方法,再由我们在invoke中实现真正代理的逻辑,实际上也就是A的所有方法都被A对象给拦截了。
而Retrofit的功能就是将代理变成像方法调用那么简单。

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;">public interface ServiceApi {
@GET("/api/columns/{user} ")
Call<Author> getAuthor(@Path("user") String user)
}1234</pre>

再用这个retrofit对象创建一个ServiceApi对象,并通过getAuthor函数来调用函数。

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;">ServiceApi api = retrofit.create(ServiceApi.class);Call<Author> call = api.getAuthor("zhangsan");12</pre>

也就是一个网络调用你只需要在你创建的接口里面通过注解进行设置,然后通过retrofit创建一个api然后调用,就可以自动完成一个Okhttp的Call的创建。Retrofit的create()函数的代码如下:

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;">public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);if (validateEagerly) {
eagerlyValidateMethods(service);
}return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable { // If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args);
} if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall);
}
});1234567891011121314151617181920212223</pre>

我们可以看出怎么从接口类创建成一个API对象?就是使用了动态代理中的拦截技术,通过创建一个符合此接口的动态代理对象A,那A呢?就是这其中创建的这个匿名类了,它在内部实现了invoke函数,这样A调用的就是A中的invoke函数,也就是被拦截了,实际运行invoke。而invoke就是根据调用的method的注解(,从而生成一个符合条件的Okhttp的Call对象,并进行真正的请求。

Retrofit作用

Retrofit实际上是为了更方便的使用Okhttp,因为Okhttp的使用就是构建一个Call,而构建Call的大部分过程都是相似的,而Retrofit正是利用了代理机制带我们动态的创建Call,而Call的创建信息就来自于你的注解。

OkHttp3

关于OkHttp3的内容大家可以访问下面的博客链接:OkHttp3源码分析。该文章主要从以下几个方面来讲解OkHttps相关的内容:
OkHttp3源码分析[综述]
OkHttp3源码分析[复用连接池]
OkHttp3源码分析[缓存策略]
OkHttp3源码分析[DiskLruCache]
OkHttp3源码分析[任务队列]

请求任务队列

Okhttp使用了一个线程池来进行异步网络任务的真正执行,而对于任务的管理采用了任务队列的模型来对任务执行进行相应的管理,有点类似服务器的反向代理模型。Okhttp使用分发器Dispatcher来维护一个正在运行任务队列和一个等待队列。如果当前并发任务数量小于64,就放入执行队列中并且放入线程池中执行。而如果当前并发数量大于64就放入等待队列中,在每次有任务执行完成之后就在finally块中调用分发器的finish函数,在等待队列中查看是否有空余任务,如果有就进行入队执行。Okhttp就是使用任务队列的模型来进行任务的执行和调度的。

复用连接池

Http使用的TCP连接有长连接和短连接之分,对于访问某个服务器的频繁通信,使用短连接势必会造成在建立连接上大量的时间消耗;而长连接的长时间无用保持又会造成资源你的浪费。Okhttp底层是采用Socket建立流连接,而连接如果不手动close掉,就会造成内存泄漏,那我们使用Okhttp时也没有做close操作,其实是Okhttp自己来进行连接池的维护的。在Okhttp中,它使用类似引用计数的方式来进行连接的管理,这里的计数对象是StreamAllocation,它被反复执行aquire与release操作,这两个函数其实是在改变Connection中的List<WeakReference<StreamAllocation>>大小。List中Allocation的数量也就是物理socket被引用的计数(Refference Count),如果计数为0的话,说明此连接没有被使用,是空闲的,需要通过淘汰算法实现回收。

在连接池内部维护了一个线程池,这个线程池运行的cleanupRunnable实际上是一个阻塞的runnable,内部有一个无限循环,在清理完成之后调用wait进行等待,等待的时间由cleanup的返回值决定,在等待时间到了之后再进行清理任务。相关代码如下:

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;"> while (true) {//执行清理并返回下场需要清理的时间long waitNanos = cleanup(System.nanoTime());if (waitNanos == -1) return;if (waitNanos > 0) { synchronized (ConnectionPool.this) { try { //在timeout内释放锁与时间片
ConnectionPool.this.wait(TimeUnit.NANOSECONDS.toMillis(waitNanos));
} catch (InterruptedException ignored) {
}
}
}12345678910111213</pre>

其中,Cleanup函数的执行过程如下:

  1. 遍历Deque中所有的RealConnection,标记泄漏的连接;

  2. 如果被标记的连接满足(空闲socket连接超过5个&&keepalive时间大于5分钟),就将此连接从Deque中移除,并关闭连接,返回0,也就是将要执行wait(0),提醒立刻再次扫描;

  3. 如果(目前还可以塞得下5个连接,但是有可能泄漏的连接(即空闲时间即将达到5分钟)),就返回此连接即将到期的剩余时间,供下次清理;

  4. 如果(全部都是活跃的连接),就返回默认的keep-alive时间,也就是5分钟后再执行清理;

  5. 如果(没有任何连接),就返回-1,跳出清理的死循环。

说明:“并发”==(“空闲”+“活跃”)==5,而不是说并发连接就一定是活跃的连接。

如何标记空闲的连接呢?我们前面也说了,如果一个连接身上的引用为0,那么就说明它是空闲的,那么就要使用pruneAndGetAllocationCount来计算它身上的引用数,如同引用计数过程。
其实标记引用为0的算法很简单,就是遍历它的List<Reference<StreamAllocation>>,删除所有已经为null的弱引用,剩下的数量就是现在它的引用数量,pruneAndGetAllocationCount函数的源码如下:

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;">//类似于引用计数法,如果引用全部为空,返回立刻清理private int pruneAndGetAllocationCount(RealConnection connection, long now) {//虚引用列表List<Reference<StreamAllocation>> references = connection.allocations;//遍历弱引用列表for (int i = 0; i < references.size(); ) {
Reference<StreamAllocation> reference = references.get(i); //如果正在被使用,跳过,接着循环
//是否置空是在上文connectionBecameIdle的release控制的
if (reference.get() != null) { //非常明显的引用计数
i++; continue;
} //否则移除引用
references.remove(i);
connection.noNewStreams = true; //如果所有分配的流均没了,标记为已经距离现在空闲了5分钟
if (references.isEmpty()) {
connection.idleAtNanos = now - keepAliveDurationNs; return 0;
}
}return references.size();
}12345678910111213141516171819202122232425262728</pre>

RxJava

从15年开始,前端掀起了一股异步编程的热潮,在移动Android编程过程中,经常会听到观察者与被观察者等概念。

观察者与被观察者通信

Observable的通过create函数创建一个观察者对象。

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;">public final static <T> Observable<T> create(OnSubscribe<T> f) { return new Observable<T>(hook.onCreate(f));
}123</pre>

Observable的构造函数如下:

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;">protected Observable(OnSubscribe<T> f) { this.onSubscribe = f;
}123</pre>

创建了一个Observable我们记为Observable1,保存了传入的OnSubscribe对象为onSubscribe,这个很重要,后面会说到。

onSubscribe方法

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;">public final Subscription subscribe(Subscriber<? super T> subscriber) { return Observable.subscribe(subscriber, this);
}
private static <T> Subscription subscribe(Subscriber<? super T> subscriber, Observable<T> observable) { ...
subscriber.onStart();
onSubscribe.call(subscriber); return hook.onSubscribeReturn(subscriber);
}123456789</pre>

Rxjava的变换过程

在RxJava中经常会数据转换,如map函数,filtmap函数和lift函数。
lift函数

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;">public <R> Observable<R> lift(Operator<? extends R, ? super T> operator) { return Observable.create(new OnSubscribe<R>() {
@Overridepublic void call(Subscriber subscriber) {
Subscriber newSubscriber = operator.call(subscriber);
newSubscriber.onStart();
onSubscribe.call(newSubscriber);
}
});
}12345678910</pre>

我们可以看到这里我们又创建了一个新的Observable对象,我们记为Observable2,也就是说当我们执行map时,实际上返回了一个新的Observable对象,我们之后的subscribe函数实际上执行再我们新创建的Observable2上,这时他调用的就是我们新的call函数,也就是Observable2的call函数(加粗部分),我们来看一下这个operator的call的实现。这里call传入的就是我们的Subscriber1对象,也就是调用最终的subscribe的处理对象。

call函数

<pre class="prettyprint" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; margin: 0px 0px 24px; padding: 8px 16px 4px 56px; font-size: 14px; font-family: Consolas, Inconsolata, Courier, monospace; color: rgb(102, 102, 102); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; outline: 0px; position: relative; overflow-y: hidden; overflow-x: auto; line-height: 22px; word-break: break-all; background-color: rgb(246, 248, 250); border: none;">public Subscriber<? super T> call(final Subscriber<? super R> o) { return new Subscriber<T>(o) { @Override
public void onNext(T t) {
o.onNext(transformer.call(t));
}
};
}12345678</pre>

这里的transformer就是我们在map调用是传进去的func函数,也就是变换的具体过程。那看之后的onSubscribe.call(回到call中),这里的onSubscribe是谁呢?就是我们Observable1保存的onSubscribe对象,也就是我们前面说很重要的那个对象。而这个o(又回来了)就是我们的Subscriber1,这里可以看出,在调用了转换函数之后我们还是调用了一开始的Subscriber1的onNext,最终事件经过转换传给了我们的结果。

线程切换过程(Scheduler)

RxJava最好用的特点就是提供了方便的线程切换,但它的原理归根结底还是lift,使用subscribeOn()的原理就是创建一个新的Observable,把它的call过程开始的执行投递到需要的线程中;而 observeOn() 则是把线程切换的逻辑放在自己创建的Subscriber中来执行。把对于最终的Subscriber1的执行过程投递到需要的线程中来进行。

 

这里写图片描述

从图中可以看出,subscribeOn() 和 observeOn() 都做了线程切换的工作(图中的 “schedule…” 部位)。不同的是, subscribeOn()的线程切换发生在 OnSubscribe 中,即在它通知上一级 OnSubscribe 时,这时事件还没有开始发送,因此 subscribeOn() 的线程控制可以从事件发出的开端就造成影响;而 observeOn() 的线程切换则发生在它内建的 Subscriber 中,即发生在它即将给下一级 Subscriber 发送事件时,因此 observeOn() 控制的是它后面的线程。

为什么subscribeOn()只有第一个有效?
因为它是从通知开始将后面的执行全部投递到需要的线程来执行,但是之后的投递会受到在它的上级的(但是执行在它之后)的影响,如果上面还有subscribeOn() ,又会投递到不同的线程中去,这样就不受到它的控制了。

小编给大家准备了安卓进阶学习资料。可以进群交流
群号:4112676

Android最新实习面试经验总结,androidframework面试 (一)调整好心态心态是一个人能否成功的关键,如果不调整好自己的心态,是很难静下心来学习的,尤其是现在这么浮躁的社会,大部分的程序员的现状就是三点一线,感觉很累,一些大龄的程序员更多的会感到焦虑,而且随着年龄的增长,这种焦虑感会越来越强烈,那么唯一的解决办法就是调整好自己的心态,要做到自信、年轻、勤奋。这样的调整,一方面对自己学习有帮助,另一方面让自己应对面试更从容,更顺利。(二)时间挤一挤,制定好计划一旦下定决心要提升自己,那么再忙的情况下也要每天挤一挤时间,切记不可“两天打渔三天晒网”。 阅读详情

相关推荐

2020Android春招面试经历(已拿今日头条28Koffer),连续四年百度Android岗必问面试题

后期阶段 有了上面两个阶段的准备后,前两轮的基础面试基本没什么问题了。如果目标岗位是资深开发或者架构师的话,一般还会问到更底层原理和更抽象的宏观层面问题。 底层原理方面:比如虚拟机的内存区域和gc流程、tcp的流量和拥塞控制、https建立连接的交互流程等,这里可以去找对应的技术文章熟悉了解。 宏观层面:一般是架构模式(MVC、MVP、MVVM)、开发模式(模块化、组件化、模块组件化)以及设计模式相关问题,要能熟练掌握到灵活运用的层度,并总结出它们之间的异同特点。 另一大块就是算法了,某些一线公司比较喜欢

wqwq_22的博客 438

史上最全的Android面试题集锦

每个 Activity 都包含一个 Window 对象,Window 对象通常是由 PhoneWindow 实现的。PhoneWindow:将 DecorView 设置为整个应用窗口的根 View,是 Window 的实现类。它是 Android 中的最基本的窗口系统,每个 Activity 均会创建一个 PhoneWindow 对象,是 Activity 和整个 View 系统交互的接口。

贵公子丶笔记 8526

2024年最全今日头条文字渐变特效项目实战(一):文字绘制(2),android 高级工程师 面试

其实客户端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。(以下体系的复习资料是我从各路大佬收集整理好的)《Android开发七大模块核心知识笔记》《960全网最全Android开发笔记》《379页Android开发面试宝典》历时半年,我们整理了这份市面上最全面的安卓面试题解析大全包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。

2401_84558590的博客 839

Android面试题大全(2024年最新)

声明:有人说, 有些面试题很变态,个人认为其实是因为我们基础不扎实或者没有深入。本篇文章来自一位很资深的前辈对于最近Android面试题目所做的总结归纳,有1600+道题目 ,知识面很广 ,而且这位前辈对于每个题都自己测试给出了答案 ,如果你对某个题有疑问或者不明白,可以电脑端登录把题目复制下来然后发表评论,大家一起探讨,也可以电脑端登录后关注我给我发私信,我们一起进步!

Androiddddd的博客 3万+

GB-T2423.10-2019 试验Fc:振动(正弦).pdf

GB-T2423.10-2019 试验Fc:振动(正弦).pdf

Python自动化神器:OP插件64位版从安装到实战(附雷电模拟器截图技巧)

本文详细介绍了Python自动化神器OP插件64位版的安装与实战应用,包括环境搭建、窗口控制、鼠标键盘模拟等核心功能。特别针对雷电模拟器提供了最小化截图技术和OCR识别优化方案,帮助开发者高效实现Windows自动化任务。

weixin_29094539的博客 330

常见Android面试题及答案(详细整理)

对于Android的学习,很多⼈可能学了之后,不知道⾃⼰处于哪个阶段,也不到究竟要学到哪个程度,验证⾃⼰学得如何最好的⾯试,就是尝试去⾯试,⽽⾯试⽆⾮就是问你⼀些⾯试题。所以呢,小编整理了这些 Android ⾯试题,从 Java 基础,并发,虚拟机到Android Framework,开源框架,性能优化,并且附带了详细的答案,⽆论是想⾯试还是想看看⾃⼰学得如何,那么这份⾯试题,都值得你去学习。答: 如下图所示。

Android 开发架构 1万+

给宇树Go2机器人装‘眼睛’:在Jetson Orin Nano上从零部署YOLOv5的保姆级避坑实录

本文详细介绍了在Jetson Orin Nano上为宇树Go2机器人部署YOLOv5视觉系统的完整流程,涵盖硬件选型、环境配置、模型优化及系统集成等关键步骤。针对机器人应用场景特有的离线部署、版本冲突和实时推理等挑战,提供了实用的解决方案和性能优化技巧,帮助开发者快速实现机器人的视觉能力升级。

weixin_29169505的博客 378

今日头条Android面试的10道题,你都知道吗?

首先说一下,今日头条的面试主要分为三轮到四轮,如果是旺季面三轮,首先是基础面试,基本面试一般10个题左右,最近面试了一下今日头条的移动Android资深工程师,记录下。 第一面是北京的开发进行视频面试,有理论和编程题组成。用的是在线编程工具,如下图。 这里写图片描述 第一面 1,请编程实现单例模式,懒汉和饱汉写法。 <pre class="prettyprint" style...

AndroidUpUp的博客 2117

手把手教你:在openEuler 22.03上离线搞定OpenSSH 9.8升级(CentOS 7.9通用)

本文详细介绍了在openEuler 22.03和CentOS 7.9系统上离线升级OpenSSH 9.8的完整流程,包括准备工作、离线包获取、安装步骤、服务管理及回滚方案。特别针对无外网环境的企业服务器,提供安全可靠的离线升级解决方案,帮助运维人员有效提升系统安全性。

weixin_30682415的博客 192

华为服务器RAID配置避坑指南:如何通过iBMC快速识别阵列卡型号并完成初始化

本文详细解析华为服务器RAID配置全流程,重点介绍通过iBMC快速识别阵列卡型号、硬盘健康诊断及RAID级别选择的场景化策略。提供从iBMC登录到缓存策略调优的实战技巧,帮助工程师避免常见配置陷阱,提升服务器存储系统性能和稳定性。

weixin_28475533的博客 490

精仿今日头条

转载请注明出处:http://blog.youkuaiyun.com/Chay_Chan/article/details/75319452 由来篇 当初的梦想   一直都想尝试开发自己还没接触过的某类APP,以前刚入门的时候,就有一个梦想:开发社交类、地图类、新闻类、支付、电商类、直播类、游戏类这些APP。社交类的大二的时候已经开发过一款即时通讯的APP,用的是网易云信的SDK;地图、支付、电商

Chay_Chan的博客 2万+

一篇教你杰理63系列蓝牙双设备主从连接传输数据过程及配置

首先打开632sdk的app_config.h文件,将宏CONFIG_APP_SPP_LE写0,将宏CONFIG_APP_MULTI写1,对应的CONFIG_BT_GATT_CLIENT_NUM写1代表我只连接一个主机设备。属性都可以修改为自己想要的属性,然后关掉文件,双击gatt_inc_generator.exe文件运行,就会自动弹出生成/更新后的gatt_profile_cfg.h文件,然后里面就是我们代码里面所需要的配置,都是十六进制表示,找到我们刚刚添加的服务属性配置,复制到代码里面去。

xiaolizaixian的博客 1375

2020Android春招面试经历(已拿今日头条28Koffer)

其实客户端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。[外链图片转存中…(img-6cqfhiO0-1734852044898)][外链图片转存中…(img-UJSuyXAg-1734852044899)]然而Android架构学习进阶是一条漫长而艰苦的道路,不能靠一时激情,更不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。贵在坚持!上面分享的。

2401_89789477的博客 827

OpenClaw Windows 最详细安装指南

Windows上养龙虾:OpenClaw完整安装教程+钉钉接入,助你养只大龙虾

weixin_43405859的博客 2212

AVEVA PDMS 二次开发之PML实战:从宏到自定义窗体的效率提升

本文详细介绍了AVEVA PDMS二次开发中PML语言的应用,从基础宏编写到高级窗体开发,全面提升工程设计效率。通过实战案例展示如何利用PML实现批量操作、流程自动化和交互优化,帮助工程师快速掌握这一强大工具。

weixin_42549451的博客 338

2024Android春招面试经历(已拿今日头条28Koffer)

建议先分为两种,第一种是在校期间没有什么实习经验的同学,那简历中可以着重突出自己在校期间做过跟岗位相关的项目经验,我参考过很多同学的简历里面只有过项目的名字,导致面试官很难去判断,所以大家写项目和实习经验的时候,可以参考一下star法则~对于应聘研发岗位的朋友,在描述自己的项目经验的时候建议详细的说明一下,比如自己用了什么语言,什么框架去实现blablabla,时刻要记得我们在求职第一步的时候别人只能通过这个来评估你的能力呀~~~

2401_85959003的博客 992

第四章:无感FOC的平滑启动艺术:告别“咔哒”声与抖动

无感FOC启动技术面临"先有位置还是先有旋转"的核心矛盾,导致常见故障包括位置失准、负载突变等。基础方案采用开环I/F控制,但存在重载启动困难等局限性。进阶方案结合IPD位置检测技术,通过脉冲电压注入法精准定位转子初始位置。关键技术在于开环到闭环的平滑切换,采用混合观测器设计和相位对齐技术。量产优化包括自适应参数整定和故障恢复机制,确保启动可靠性和一致性。该系统工程方案可有效解决启动抖动、异响等问题,提升用户体验。

汽车软件开发 730

今日头条屏幕适配方案终极版正式发布!,2024最新百度、头条等公司Android面试题目

在使用上非常简单,只需要填写设计图尺寸这一步即可接入项目,但需要注意的是,有两种类型的布局单位可以选择,一个是主单位 (dp、sp),一个是副单位 (pt、in、mm),两种单位面向的应用场景都有不同,也都有各自的优缺点主单位: 使用dp、sp为单位进行布局,侵入性最低,会影响其他三方库页面、三方库控件以及系统控件的布局效果,但也通过这个特性,使用实现了在不修改三方库源码的情况下适配三方库的功能副单位: 使用pt、in、mm。

1048

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

今日头条研发面经

今日头条研发面经 今日头条上海 后台开发工程师 今日头条 后端研发工程师 找牛客大佬要了白金码,跳过死亡笔试,直接视频面,从3点开始,断断续续到晚上8点结束。 每个面试官给我的感觉都是怎么这么高冷啊。 1一面 一面: 1 写一个题,找一个无序数组的中位数 2 写了个快排,然后让我找到无序数组第k大的...

Java技术江湖 8959

今日头条笔试面试大全

整理了一下今日头条往届笔试面试题,希望对大家有帮助: 超级有用的面试题:Java常见面试题    常见算法面试题   数据库常见面试题  操作系统常见面试题   C/C++常见面试题  大数据常见面试   python常见面试 来源:今日头条笔试面试圈&gt;&gt; 1、2018今日头条校招算法方向(第一批) 2、2018今日头条校招大数据方向(第一批) 3、2018今日头条校招测试开...

wodwl的专栏 6385

Android应届生,靠什么拿到的今日头条、携程双offer,android开发平台

自我介绍 abstract和interface区别 int和Integer区别 四种引用 Java里面int、char、long的字节数 线程的创建方式 RxJava的优缺点 EventBus 从网络上加载一个10M的图片,要考虑哪些 http与https http 1.0 和 http 2.0之间的区别 设计模式准则 Handler机制 算法:判断一个链表是否回文算法,给定一个数组,将数组里面的每一位向后移动k位 网易游戏 网易游戏当时投的时候就没抱希望,招聘信息上明确指定只招固定的那几所985高校.

sa3221sa的博客 1471

今日头条面试

首先说一下,今日头条的面试主要分为三轮到四轮,如果是旺季面三轮,首先是基础面试,基本面试一般10个题左右,最近面试了一下今日头条的移动Android资深工程师,记录下。 第一面是北京的开发进行视频面试,有理论和编程题组成。用的是在线编程工具,如下图。 第一面 1,请编程实现单例模式,懒汉和饱汉写法。 //饱汉写法 public static Singleton getInstance(...

xiangzhihong8的专栏 1万+

今日头条Android面试

首先说一下,今日头条的面试主要分为三轮到四轮,如果是旺季面三轮,首先是基础面试,基本面试一般10个题左右,最近面试了一下今日头条的移动Android资深工程师,记录下。  第一面是北京的开发进行视频面试,有理论和编程题组成。用的是在线编程工具,如下图。  ​第一面  1,请编程实现单例模式,懒汉和饱汉写法。 ​2,请编程实现Java的生产者-消费者模型  原文链接...

weixin_40581617的博客 1923
上一篇: 解决组件化开发butterknife 在 library中使用的坑
下一篇: 一篇文章看明白Android hook技术如何实现一键换肤
动脑学院[David]
博客等级 码龄8年 44粉丝 31原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值