01.面试题

目录

  • 算法
  • 冒泡排序
  • 网络通信协议TCP和UDP的区别
  • 注册广播有几种方式,他们的区别是什么?
  • Get请求和Post请求的区别
  • Android中创建线程的两种方式及优缺点?
  • 活动的启动模式
  • ArrayList与LinkedList和Vector的区别
  • 简述Handler机制
  • 在项目中怎样对ListView优化
  • 说说线程池管理机制
  • Fragment的生命周期
  • Service和IntentService的区别
  • 写下单例模式

算法

   /**
     * 猫扑素数:
     * 猫扑数:以2开头,后面拼接任意多个3的十进制整数
     * 素数:大于1的自然数中,除了1和他本身,没有其他的因数
     */

    public static boolean isMopNumber(int num) {
        if (num < 10) return num == 2;
        else {
            return (num % 10 == 3) && isMopNumber(num / 10);
        }
    }

//如果n不是素数,2=< d <= Math.sqrt(n) ,则这个区间中一定有是它的因数
    public static boolean isPrimeNumber(int num) {
        if (num < 2) return false;
        else {
            for (int i = 2; i<= Math.sqrt(num); i++) {
                if (num % i==0){
                    return false;
                }
            }
            return true;
        }
    }

   //测试
  for (int i = 0; i < 1000000; i++) {
       if (Utils.isMopNumber(i) && Utils.isPrimeNumber(i)) {
           Log.e("猫扑素数", "=" + i);
       }
   }
01-17 17:37:08.359 5032-5032/com.zsl.aa E/猫扑素数: =23
01-17 17:37:08.359 5032-5032/com.zsl.aa E/猫扑素数: =233
01-17 17:37:08.359 5032-5032/com.zsl.aa E/猫扑素数: =2333
01-17 17:37:08.359 5032-5032/com.zsl.aa E/猫扑素数: =23333

1.冒泡排序

private int[] arr = {6,3,8,2,9,1};
private int temp;

private void bubbleSort(int[] arr){

    for(int i=0;iarr[j+1]){
                temp=arr[j];
                arr[j]=arr[j+1];
                arr[j+1]=temp;
            }
        }
    
    }
}
最后的排序结果:1,2,3,6,8,9
//二分法查找
public static int search(int[] arr, int key) {
       int start = 0;
       int end = arr.length - 1;
       while (start <= end) {
           int middle = (start + end) / 2;
           if (key < arr[middle]) {
               end = middle - 1;
           } else if (key > arr[middle]) {
               start = middle + 1;
           } else {
               return middle;
           }
       }
       return -1;
   }

2.网络通信协议TCP和UDP的区别

TCP:Transmission Control Protocol 传输控制协议
UDP:User Dataprogram Protocol 用户数据协议

TCP协议是面向连接的通信协议,即在数据传输前现在发送端和接收端建立逻辑联系,然后再传输数据,它提供了两台计算机间可靠无差错的数据传输。在tcp协议中必须明确客户端和服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。第一次握手,客户端向服务器端发出连接请求,等待服务器确认;第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求;第三次握手,客户端再次向服务器端发送确认信息,确认连接。

UDP协议是无连接的通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说就是,当一台计算机向另一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。由于使用udp协议消耗的资源小,通信效率高,所以通常会用于音频、视频和普通数据的传输,例如开视频会议时都使用udp协议,因为这种情况即是偶尔丢失一两个数据包,也不会对接收的结果产生太大的影响。但是在使用udp协议传输数据时,由于udp面向无连接性,不能保证数据的完整性,因此在传输重要的数据时不建议使用udp协议。

TCP的三次握手过程?为什么会采用三次握手,若采用二次握手可以吗?

答:建立连接的过程是利用客户服务器模式,假设主机A为客户端,主机B为服务器端。
(1)TCP的三次握手过程:主机A向B发送连接请求;主机B对收到的主机A的报文段进行确认;主机A再次对主机B的确认进行确认。
(2)采用三次握手是为了防止失效的连接请求报文段突然又传送到主机B,因而产生错误。失效的连接请求报文段是指:主机A发出的连接请求没有收到主机B的确认,于是经过一段时间后,主机A又重新向主机B发送连接请求,且建立成功,顺序完成数据传输。考虑这样一种特殊情况,主机A第一次发送的连接请求并没有丢失,而是因为网络节点导致延迟达到主机B,主机B以为是主机A又发起的新连接,于是主机B同意连接,并向主机A发回确认,但是此时主机A根本不会理会,主机B就一直在等待主机A发送数据,导致主机B的资源浪费。
(3)采用两次握手不行,原因就是上面说的实效的连接请求的特殊情况。

3. 注册广播有几种方式,他们的区别是什么?

有两种方式,分别为在代码中的动态注册和在AndroidManifest.xml中的静态注册,动态注册的方式缺点是程序必须启动才能收到广播,而在静态注册的方式不需要程序启动就能就能接受广播,比如监听开机的广播。注册广播是注意要添加相应的权限。

动态注册的核心代码:

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("....");  //系统级别的广播或是自定义的广播
MyReciver reciver = new MyReciver();
registerReciver(reciver,intentFilter);



public class MyReciver extend BroadcastReciver{
    @override
    public void onRecive(Context context,Intent intent){
        //接受到广播后的处理逻辑
    }
}

静态注册的核心代码:

public class MyReciver extend BroadcastReciver{
    @override
    public void onRecive(Context context,Intent intent){
        //接受到广播后的处理逻辑
    }
}


    
        
     

4. Get请求和Post请求的区别

  • 1.get请求是从服务器获取信息,它并不能修改服务器的信息,所以它是安全的,一般不会产生副作用。而post一般是将数据发给服务器,它会更改服务器的信息。
  • 2.get使用url或cookie传参,而post是将数据放在请求体(request body)中。
  • 3.get的url会有长度的限制,而post的数据可以非常大。
  • 4.post比get安全,因为数据在地址栏不可见。

上面是网上流传最多的说法,但实际并不完全正确:
1.get和post请求是http协议定义的,http没有要求如果是get请求,请求参数就必须放在url中,而不能放在body中;也没有规定如果是post请求,请求参数就必须放在请求体中,那么网上的答案是怎么来的呢,这只是我们的一种习惯用法,它并不是两者的区别。

5.Android中创建线程的两种方式及优缺点?

方法一:继承java.lang.Thread类,重写run()方法,调用start()方法开启
方法二:实现java.lang.Runnable接口,并实现run()方法

public class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            //线程内的逻辑处理
        }
 }

 MyThread thread = new MyThread();
 thread.start();
 public class MyThread implements Runnable{
      @Override
      public void run() {
        //线程内部处理逻辑  
      }
 }

MyThread thread = new MyThread();
new Thread(thread).start();

优缺点:
1.java中类仅支持单继承, 如果自定义线程类继承了Thread就不能继承其他类了,拥有其他类的方法,降低了扩展性。而如果你用实现Runnable的方式就可以避免这种情况。
2.还有最重要的一点是,使用实现Runnable接口的方式创建的线程可以处理同一资源,从而实现资源共享。

比如:一个火车站的售票系统,比如有100张车票,允许所有的窗口卖这100张票,那么每一个窗口相当于一个线程,但是处理的资源是同一个资源,即这100张车票。 那么方式一和方式二实现买票的逻辑有什么不同呢?

public class Example {
    public static void main(String[] args){
        new TicketWindow().start();
        new TicketWindow().start();
        new TicketWindow().start();
        new TicketWindow().start();
    }
}

class TicketWindow extends  Thread{
        private int tickets = 100;
        @Override
        public void run() 
            while (true){
                if (tickets>0){
                    Thread th = Thread.currentThread();
                    String name = th.getName();
                    System.out.print(name+"正在发售第"+tickets--+"张票");
                }
            }
        }
 }

运行结果是每张票都被打印了4次,其实这种写法是开启了4个卖票的程序,各自都卖100张票。这肯定是不符合现实的逻辑的,所以我们来看下另一种方式。

public class Example {
    public static void main(String[] args){
        TicketWindow tw = new TicketWindow();
        new Thread(tw,"窗口1").start();
        new Thread(tw,"窗口2").start();
        new Thread(tw,"窗口3").start();
        new Thread(tw,"窗口4").start();
    }
}

class TicketWindow implements Runnable{
        private int tickets = 100;
        @Override
        public void run() {
            while (true){
                if (tickets>0){
                    Thread th = Thread.currentThread();
                    String name = th.getName();
                    System.out.print(name+"正在发售第"+tickets--+"张票");
                }
            } 
        }
}

这种方式只创建了一个TicketWindow对象,然后开启了4个线程,在每个线程都去调用TickeWindow对象中的run()方法,这样就确保了四个线程访问的是同一个tickets变量,共享100张车票。

6.活动的启动模式

怎样指定活动的启动模式: android:launchMode="singleTop" ,栈是遵从后进先出的原则
四种启动模式,分别是:
1、standard:标准模式,默认情况下活动都使用标准模式,在该模式下每当启动一个活动,它都会进入栈中,并处于栈定。这种模式下,无论栈中是否有需要启动的活动实例,系统都会创建一个活动实例进栈。
2、singleTop:栈顶模式,该种模式下要启动一个活动如果发现已经已经是该活动,可以直接使用它,不需要再创建活动实例。
3、singleTask:该种模式下,启动一个活动时如果发现栈中存在该活动的实例,也同样可以直接使用这个活动,不在重新创建实例。栈中的变化就是,这个活动上面的活动都退出栈,使这个活动处于栈顶。
4、singleInstance:单实例模式,该种模式下活动会启用一个新的活动栈来管理这个活动。

01.面试题_第1张图片
Paste_Image.png

如上图所示,栈中有四个活动,想问怎样使A跳转到D,然后按返回键直接回到主界面,不回到A?
答案:A,B,C都是默认模式,D设置singleTask模式
注意:A,B,C都是默认模式,D设置singleInstance模式,这种方法是不行的

7.ArrayList与LinkedList和Vector的区别

ArrayList:基于数组结构,有角标索引,所以通过索引查询比较快,因为增删元素相当于底层重新创建数组,所以增删慢,线程不安全
LinkedList:基于双向循环链表结构,增删元素只是改变了该元素节点的引用,所以增删比较快,查询慢,线程不安全
Vector:基于数组结构,查询快,增删慢,线程安全

01.面试题_第2张图片
LinkedList增删元素.png

8.简述Handler机制

Android系统规定主线程中不能做耗时的操作,只能在子线程进行,但是子线程不能更新ui,为了解决类似的问题,Android提供了handler机制,它是一个异步消息处理机制,我们可以在子线程做些耗时的操作,比如网络请求数据,得到数据后通过handler发送到主线程,进行相应ui的更新。
Message:携带数据的消息
MessageQueue:消息队列,由looper来创建它
Looper:每个主线程都默认持有一个looper对象,它负责轮循messagequeue中的所有消息并回调消息处理函数

当我们通过handler.send一个消息后,消息进入消息队列,looper没轮循一次,都会从中去除一个消息,交给handle.message方法处理,当messagequeue中没有消息了,线程则会阻塞停止等待新的消息进来。

9.在项目中怎样对ListView优化

1.在Adapter的getView方法中使用ConvertView,即ConvertView的复用,不需要每次执行getView方法时都inflate一个View,这样既浪费时间也消耗内存。
2.使用ViewHolder,既不要不再getView方法中频繁的findViewById,这样更加消耗系统的开销。
3.使用分页加载
4.如果ListView中要加载图片需要进行缓存处理,我们可以借助开源的框架,自带缓存机制。
5.当我们滑动ListView时不要加载图片,当我们停止滑动时再加载图片。

10.说说线程池管理机制

首先,线程池有一下优点:

  • 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
  • 能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占资源而导致的阻塞现象。
  • 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。

线程池主要包括线程池和任务队列,如下图所示:

01.面试题_第3张图片
线程池.png

ThreadPoolExecutor的构造方法:

public ThreadPoolExecutor(
   int corePoolSize,    //核心线程数
   int maxPoolSize,    //最大线程数
   long keepAliveTime,  //非核心线程闲置时的超时时长
   TimeUnit unit,    //制定keepAliveTime参数的时间单位,它是一个枚举
   BlockingDeque workQueue,   //线程池中的任务队列
   ThreadFactory threadFactory  //线程工厂,为线程池创建新线程
){ }

ThreadPoolExecutor执行任务时遵循的原则:

  • ①如果线程池中的线程数未达到核心线程数,那么会直接启动一个核心线程来执行任务。
  • ②如果线程池中的线程数量已经达到或已经超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
  • ③ 如果在步骤2中无法将任务插入到任务队列中,可能是任务队列已满,这时如果线程数未达到线程池规定的最大值,就会立刻启动一个非核心线程来执行任务。
  • ④如果步骤3中的线程数量已经达到线程池规定的最大值,那么就会拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。

11.Fragment的生命周期

onAttach()—onCreate()—onCreateView()—onActivityCreated()
—onStart()—onResume()—onPause()—onStop()—onDestroyView()
—onDestroy()—onDetach(),共11个生命周期。
比如有A,B两个Fragmeng,可以切换显示,如下图所以。

01.面试题_第4张图片
Paste_Image.png

最开始A显示,A执行的方法有:
onAttach()—onCreate()—onCreateView()—onActivityCreated()
—onStart()—onResume()
点击B,B Fragment显示,A隐藏,A要执行的方法:
如果不执行addToBackStack()方法,A将会执行:
onPause()—onStop()—onDestroyView()—onDestroy()—onDetach()
如果添加了addToBackStack()方法,则会执行下面的方法:
onPause()—onStop()—onDestroyView()
这时按Back返回键,A重新显示,执行下面的方法:
onActivityCreated()—onStart()—onResume()

12.Service和IntentService的区别

Android中的Service是用于后台服务的,当应用程序被挂到后台时,为了保证某些组件仍然可以工作,引入了Service。但是,需要强调的是Service不是独立的进程,也不是独立的线程,它是依赖于应用的主线程。因此不建议在Service中编写耗时的逻辑和操作,否则会引起ANR。

当我们编写耗时逻辑,不得不被Service管理的时候,就需要引进IntentService,IntentService是继承Service,包含了Service的全部特性,当然也包含Service的生命周期。那么与Service不同的是,IntentService在执行onCreate()方法的时候,内部开启了一个线程,去执行你的耗时操作。

IntentService内部有个关键的方法onHandleIntent,它是什么时候被调用的呢?当IntentService执行onCreat()方法时会内部开启一个线程ThreadHandler,并获得了当前线程队列管理的looper,并且在onStart的时候,把消息置入了消息队列。

@Override  
public void handleMessage(Message msg) {  
    onHandleIntent((Intent)msg.obj);  
    stopSelf(msg.arg1);  
}  

@Override  
public void onCreate() {  
    super.onCreate();  
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");  
    thread.start();  
  
    mServiceLooper = thread.getLooper();  
    mServiceHandler = new ServiceHandler(mServiceLooper);  
}  
  
@Override  
public void onStart(Intent intent, int startId) {  
    Message msg = mServiceHandler.obtainMessage();  
    msg.arg1 = startId;  
    msg.obj = intent;  
    mServiceHandler.sendMessage(msg);  
}  

在消息被handler接受并且回调的时候,执行了onHandlerIntent方法,该方法的实现是子类去做的。

13.写下单例模式

http://www.jianshu.com/p/bfc62d9bddb0

14、Android中常用的五种布局

LinearLayout 线性布局
RelativeLayout 相对布局
FrameLayout 帧布局
TableLayout 表格布局
AbsoluteLayout 绝对布局

15、Android里的动画有几种,它们的区别是什么?

Android3.0后有3中动画,分别是帧动画(FrameAnimation)、补间动画(TweenAnimation)、属性动画(PropertyAnimation)。

区别:

  1. 帧动画类似于幻灯片,由多张图片顺序播放而成
  2. 补间动画望文生义,就是在两点之间插入渐变值来平滑过渡。它包括透明动画、平移动画、缩放动画、旋转动画。补间动画只是在视觉上View在改变,实际位置并没有改变
  3. 属性动画和补间动画类似,区别是属性动画是真实改变位移。

垃圾回收资料:https://juejin.im/post/59f929135188254897330937

16、简述Android垃圾回收机制,垃圾回收算法有几种?

垃圾回收意义:
它使java程序员不需要时时刻刻关注内存管理分配的工作,垃圾回收机制会自动管理jvm的内存空间,将那些不再会用到的“垃圾对象”清理掉,释放出更多的内存空间。

17、说说Android 对象四大引用?

  1. 强引用:java中最普遍的,只要强引用还在,垃圾回收器就永远不会回收掉被引用的对象。
  2. 软引用:描述一些非必须的对象,在系统内存不足时,这类对象会被垃圾回收器回收掉。
  3. 弱引用:描述一些非必须对象,如果GC发生了,无论系统内存是否足够,都会回收掉这类对象。
  4. 虚引用:持有虚引用的对象,就像没有任何引用一样,时刻都有可能被垃圾回收器回收掉。

18、在Activity的onCreate方法中,开启一个线程并更新UI元素,如下代码,会报错吗?为什么,如果报错报什么错误?

参考:https://juejin.im/entry/58133f59c4c97100553f1056

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        tv = findViewById(R.id.tv_text);
        btn = findViewById(R.id.btn_button);

        new Thread(new Runnable() {
            @Override
            public void run() {
                tv.setText("哈哈");
            }
        }).start();
   }

当你运行这段代码后发现并没有报错,可以正常修改UI,如果我们把代码稍微做下修改,看下结果:

new Thread(new Runnable() {
         @Override
         public void run() {
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              tv.setText("哈哈");
          }
   }).start();

运行后,你会发现程序崩溃了,我们看下崩溃日志:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6581) 
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:924)

CalledFromWrongThreadException :only the original thread that created a view hierarchy can touch its views.
ViewRootImpl.checkThread()
ViewRootImpl.requestLayout()
以上是报错的三点重要信息,首先我们来查看下checkThread()的源码:

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

到这里我们还有些不太清楚,但是大体知道了是这样的:
Android系统在更新UI的时候会调用ViewRootImpl.checkThread()方法来检查当前线程是否是主线程。
那我们开始时运行为啥没有报错,因为第一种写法,ViewRootImpl还没有创建,所以不会执行checkThread()方法。那我们要来看下什么时候创建ViewRootImpl呢?
答案:ViewRootImpl是在WindownManagerGloable.addView()方法中创建的,在回调onResume方法后,ViewRootImpl就创建好了。

你可能感兴趣的:(01.面试题)