android面试实用篇

前言:面试的误区,很多人不会面试,甚至很多有经验的人。面试最重要的是交流,半个小时的时间,怎么能突出你的技能。首先一点,基础知识要牢靠,可能有人会说,你这不是废话吗。这还真不是废话,有些人会做不一定会说,甚至有的人代码能写出来都讲不出来。面试即考察基础知识,又考察语言组织能力,有的人可能心里知道,但是说出来就不一样了。面试一定要有逻辑性,和代码是一样的。这就要求我们在梳理知识点的时候,一定要有知识体系图,比如思维导图,类图等。有些人虽然知道原理,但是不知道怎么去说,不知道从哪里开始说。这里讲述一下最简单的方式,从简到繁,从外到内,先总述,再细讲。

面试技术核心:把知识点提炼提炼再提炼。把简历上的知识点全都弄明白,不求多只求精炼

本篇文章亮点:每一个知识点,最后都会用一张图去解决。每个知识点都可以串起来说个5分钟,告别面试语言障碍。

一,内存区域,内存分配,Full GC,Minor GC,Major GC让这些问题不再困扰我们。

一想到Java虚拟机,大家可能都慌了,那是一本书呀,怎么三言两语就能说清楚呢。在面试中只要问到这一类问题,都可以用以下方式统一回答。

1,先介绍内存区域
为什么先介绍内存区域呢,如果上来就问你Full GC你会怎么回答,可能一脸萌萌哒,或者根本不知道Full Gc是什么。如果知道了,把概念一说:“Full GC是清除堆空间的,包括新生代和老年代”,这就完了吗,如果对知识结构不清楚,接下来会有很多问题。比如:什么情况下产生Full GC等问题。面试不要让面试官牵着鼻子走,要主动出击,如果是体系的回答,这样会少很多问题。好了,不扯别的了,开始脑补。

先上图:这张图就是体系,一定要记住。记住这张图才能继续往下说,每个区都是干嘛的。

在这里插入图片描述
简单介绍各个区域:注意堆和栈一定要搞清楚,不然面试会很难看。可能会问你堆和栈是干嘛的,最起码的理论得知道。
①,方法区:存储已被虚拟机加载的类,常量,静态变量等。
②,堆区:存储对象的实例,是GC管理的主要区域。
③,虚拟机栈:用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
④,本地方法栈:与虚拟机栈一样,只不过为native层服务。
⑤,程序计数器:当前线程所执行的字节码的行号指示器,此区域不会出现内存溢出。

再次脑补,内存区域图,然后想想每个区域的作用。

2,接下来就该介绍内存分配了

内存区域知道了,就该说说内存分配了,这样很有条理和逻辑。

先上图:这是堆内存图,分为新生代,老年代,永久代(jdk1.8无永久代,使用metaspace实现)

在这里插入图片描述
新生代:新生代又分为Eden和Survivor区,对象优先分配在Eden区。
老年代:分配大对象,长期存活的对象。

3,开始讲GC吧

有了上边的介绍,这时候就可以说说GC了,这样说的时候就很清晰了。

Minor GC:清理新生代,当Eden区没有足够的空间进行分配时,虚拟机触发Minor GC。
Major GC:清理老年代,经常会伴随至少一次的Minor GC。
Full GC:清理新生代和老年代。

Full GC产生的条件:

  • 老年代空间不足 。
  • 调用System.gc()时,系统建议执行Full GC,但是不必然执行。
  • 通过Minor GC后进入老年代的平均大小 > 老年代的可用内存 。

虚拟机是如何区分新生代和老年代的,这里还可以讲一下,对象年龄计数器,也就是分代收集算法。如果Eden区满了执行Minor GC后,对象依然存活,将会被移到Survivor区,同时对象年龄设置为1。以此类推如果Minor GC后再Survivor区依然存活,对象再加1,直到达到年龄阈值,被移到老年区。

讲到这里其实已经不错了,如果再去说一下垃圾收集器算法就更厉害了。

4,再说说垃圾收集器算法

在垃圾回收前怎么知道对象要被回收呢,如下:

①,引用计数:很好理解,就是记录对象被引用的次数。
②,可达性分析算法:当一个对象到GC Roots没有任何引用链时,证明对象不可用。

再谈引用

①,强引用:只要引用存在,垃圾回收器不会回收。
②,软引用:在出现内存溢出之前,将会把这些对象回收。
③,弱引用:垃圾回收器工作时,无论当前内存是否足够,都会回收。
④,虚引用:为对象设置虚引用是能在对象回收时收到通知。

垃圾收集算法

①,标记清除法
②,标记整理法
③,分代收集法
④,复制收集法

到这里就可以了,然后生成思维导图,把知识点串起来。看着这个体系去一条条的说,还可以每个点再去延伸,我大概试了一下,把这些说完需要六分钟左右。这样以来延长你说话时间,等于缩小面试官问问题的时间,概率大大提升。
在这里插入图片描述

二,Android的消息机制

在面试中Handler经常被问到吧,但是你的回答是:“MessageQueue,Looper,MessageQueue保存数据,Looper从MessageQueue中取数据,Handler发送和接收数据”。如果是这样的回答,那就太简单了,可能自己都不知道说些啥。好了,那就来个知识体系吧,告别Handler带来的恐惧。

提问知识点

系统为什么不允许在子线程中访问UI:因为Android的UI控件不是线程安全的。

为什么不对UI控件加锁访问呢:加锁会让UI访问变得复杂,其次锁机制会降低UI访问效率,锁机制会阻塞线程。

Handler是怎么实现延时任务的?

Handler中的post方法?

在面试中如果被问到Handler了,就把涉及到的类先简单介绍一下,然后再逐个进行深入详解。

1,MessageQueue:存取消息

MessageQueue主要包含两个操作:插入和读取。插入和读取对应方法enqueueMessage和next。

内部实现是单链表的数据结构,单链表插入和删除有优势。(单链表:每个节点都有数据元素区和指针区,指针指向后续节点)

2,Looper:查询消息

Looper何时产生:在UI线程(ActivityThread)创建时就会初始化Looper。

查看Looper源码,发现源码中有用到ThreadLocal这个类,那就说说ThreadLocal类。

①,ThreadLocal:是一个线程内部的数据存储类,怎么去理解呢,举个例子:

使用ThreadLocal存Boolean类型的数据,代码如下:

private ThreadLocal<Boolean> mThreadLocal=new ThreadLocal<Boolean>();

分别在主线程,子线程1和子线程2中设置和访问,代码如下:

        mThreadLocal.set(true);
        Log.e("ThreadLocal","main value="+mThreadLocal.get());

        new Thread(new Runnable() {
            @Override
            public void run() {
                mThreadLocal.set(false);
                Log.e("ThreadLocal","thread1 value="+mThreadLocal.get());
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e("ThreadLocal","thread2 value="+mThreadLocal.get());
            }
        }).start();

打印结果:看到打印结果自然明白了,虽然在不同线程访问同一个ThreadLocal对象,但是他们取到的值不一样,互不影响。对于Looper的作用域就是线程并且不同的线程具有不同的Looper,所以通过ThreadLocal就可以轻松实现Looper在线程中的存取。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200417224059531.png
接下来说一下ThreadLocal内部实现。

从ThreadLoacl 的 set 和 get 方法来看,它们操作的对象都是当前线程对象中的 ThreadLocalMap 对象的 Entry[] 数组。ThreadLocalMap用于存储当前线程的副本。

Entry数组保存局部变量。通过key(ThreadLocal类型)的hashcode来计算数组存储的索引位置i。如果i位置已经存储了对象,那么就往后挪一个位置依次类推,直到找到空的位置,再将对象存放。

②,Looper工作原理

Looper就是负责不停的从MessageQueue中取消息,在主线程中已经存在Looper了。如果在子线程中用Handler就需要主动创建Looper,代码如下:

   new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler=new Handler();
                Looper.loop();
            }
    }).start();

Looper.prepare()方法是为当前线程创建Looper,Looper.loop()方法是开启消息循环。

3,Handler:负责发送接收消息

Handler发送消息是向消息队列中插入了一条消息,MessageQueue的next方法返回消息给Looper,Looper将消息交给Handler处理,即Handler的dispatchMessage方法被调用。


又到了上图的时候,看着这张图,让Handler问题不再恐慌。建议梳理的过程中,把源码大致看一下,最起码要看看关键方法的实现,这样更有助于理解Handler。

在这里插入图片描述

三,Activity启动流程

activity的启动流程很复杂,不过这也是面试常问的问题。在这里怎么去提炼信息呢,原则上把涉及到的主要类,核心方法梳理一下就可以。这里不可能完全掌握,但是必要的还是需要去构建知识体系。

在网上找了很多关于activity启动的文章,最后感觉还是这张图能简单易懂的说明问题,比起长篇大论,不如这张图。

在这里插入图片描述
启动一个activity的方式有两种,一种是在launcher中点击一个app,这时候会启动app。另一种是在app中跳转activity页面。不管是哪一种,总体流程类似。

1,在启动activity之前,先要了解一下几个进程

①,init进程

Android是基于linux系统的,手机开机之后,linux内核进行加载。加载完成之后会启动init进程。init进程会启动ServiceManager,孵化一些守护进程,并解析init.rc孵化Zygote进程。

②,Zygote进程

所有的App进程都是由Zygote进程fork生成的,包括SystemServer进程。Zygote初始化后,会注册一个等待接受消息的socket,OS层会采用socket进行IPC通信。

③,SystemServer进程

System Server是Zygote孵化的第一个进程。SystemServer负责启动和管理整个Java framework,包含AMS,PMS等服务。

2,开始启动了,动次打次

把启动流程简化一下,提炼提炼再提炼,如果分步骤去说,更有条理。

第一步:Launcher通知AMS要启动新的Activity

  • Launcher.startActivitySafely //首先Launcher发起启动Activity的请求
  • Activity.startActivity
  • Activity.startActivityForResult
  • Instrumentation.execStartActivity //交由Instrumentation代为发起请求
  • ActivityManager.getService().startActivity //通过IActivityManagerSingleton.get()得到一个AMP代理对象
  • ActivityManagerProxy.startActivity //通过AMP代理通知AMS启动activity

第二步:AMS先校验一下Activity的正确性,如果正确的话,会暂存一下Activity的信息。然后,AMS会通知Launcher程序pause Activity。

  • ActivityManagerService.startActivity
  • ActivityManagerService.startActivityAsUser
  • ActivityStackSupervisor.startActivityMayWait
  • ActivityStackSupervisor.startActivityLocked :检查有没有在AndroidManifest中注册
  • ActivityStackSupervisor.startActivityUncheckedLocked
  • ActivityStack.startActivityLocked :判断是否需要创建一个新的任务来启动Activity。
  • ActivityStack.resumeTopActivityLocked:获取栈顶的activity,并通知Launcher应该pause掉这个Activity以便启动新的activity。
  • ActivityStack.startPausingLocked
  • ApplicationThreadProxy.schedulePauseActivity

第三步:检查activity所在进程是否存在,如果存在,就直接通知这个进程,在该进程中启动Activity;不存在的话,会调用Process.start创建一个新进程。

  • ActivityManagerService.activityPaused
  • ActivityStack.activityPaused
  • ActivityStack.completePauseLocked
  • ActivityStack.resumeTopActivityLocked
  • ActivityStack.startSpecificActivityLocked
  • ActivityManagerService.startProcessLocked
  • Process.start //在这里创建了新进程,新的进程会导入ActivityThread类,并执行它的main函数

第四步:创建ActivityThread实例,执行一些初始化操作,并绑定Application。如果Application不存在,会调用LoadedApk.makeApplication创建一个新的Application对象。

  • ActivityThread.main
  • ActivityThread.attach(false) //声明不是系统进程
  • ActivityManagerProxy.attachApplication

第五步:启动目标activity

  • ActivityManagerService.attachApplication //AMS绑定本地ApplicationThread对象,后续通过ApplicationThreadProxy来通信。
  • ActivityManagerService.attachApplicationLocked
  • ActivityStack.realStartActivityLocked //真正要启动Activity了!
  • ApplicationThreadProxy.scheduleLaunchActivity //AMS通过ATP通知app进程启动Activity

涉及到的相关类

  • ActivityStack:Activity在AMS的栈管理,用来记录已经启动的Activity的先后关系,状态信息等。通过ActivityStack决定是否需要启动新的进程。
  • ActivitySupervisor:管理 activity 任务栈。
  • ActivityThread:ActivityThread 运行在UI线程(主线程),App的真正入口。
  • ApplicationThread:用来实现AMS和ActivityThread之间的交互。
  • ApplicationThreadProxy:ApplicationThread在服务端的代理。AMS就是通过该代理与ActivityThread进行通信的。
  • IActivityManager:继承与IInterface接口,抽象出跨进程通信需要实现的功能。
  • AMN:运行在server端(SystemServer进程)。实现了Binder类,具体功能由子类AMS实现。
  • AMS:AMN的子类,负责管理四大组件和进程,包括生命周期和状态切换。AMS因为要和ui交互,所以极其复杂,涉及window。
  • AMP:AMS的client端代理(app进程)。了解Binder知识可以比较容易理解server端的stub和client端的proxy。AMP和AMS通过Binder通信。
  • Instrumentation:仪表盘,负责调用Activity和Application生命周期。测试用到这个类比较多。
  • ActivityStackSupervisor:负责所有Activity栈的管理。内部管理了mHomeStack、mFocusedStack和mLastFocusedStack三个Activity栈。其中,mHomeStack管理的是Launcher相关的Activity栈;mFocusedStack管理的是当前显示在前台Activity的Activity栈;mLastFocusedStack管理的是上一次显示在前台Activity的Activity栈。

3,继续提炼呀

在这里,activity整个流程已经很清楚了。但是要知道,面试不可能记住这么多过程和类。我们只需要说出一些关键点,然后再去延伸。


最终版本来了,我们可以把activity的启动看成四个进程,Launcher进程,System_Server进程,Zygote进程,App进程,这四个进程很好记住。用最简单的方式,讲述最复杂的流程。

在这里插入图片描述

四,事件分发

看到这四个字,能想到啥,能说出个啥。这又是一个面试常问,而又回答不全的一个问题。我们能想到的无非就是事件发送,拦截,处理等。因此还是需要系统的去整理,才能系统的去讲好。

1,View的事件体系

①,MontionEvent

手指触摸屏幕会产生以下事件类型,同时通过MotionEvent还可以拿到点击事件发生的坐标。

单指触摸

  • ACTION_DOWN:手指刚接触屏幕
  • ACTION_MOVE:手指在屏幕上移动
  • ACTION_UP:手指从屏幕上松开
  • ACTION_CANCEL:父View如果拦截事件了,子View会收到这个时间,并且不会再有move和up事件。

多指触摸

  • ACTION_POINTER_DOWN:有非主要的手指按下(即按下之前已经有手指在屏幕上)
  • ACTION_POINTER_UP:有非主要的手指抬起(即抬起之后仍然有手指在屏幕上)

②,TouchSlop

是系统所能识别出来的被认为滑动的最小的距离,是一个常量。

③,VelocityTracker

用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。

④,GestureDetector

手势检测,用于辅助检测用户的单击,滑动,长按,双击等。

④,Scroller

用于实现View的弹性滑动

2,View的事件分发机制

在这里插入图片描述

事件的分发可以理解为三个过程,分发,拦截,消费。

①,分发三阶段

  • dispatchTouchEvent():用来事件的分发,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响。
  • onInterceptTouchEvent():用来事件的拦截,在dispatchTouchEvent内部调用。
  • onTouchEvent():用来处理点击事件,在dispatchTouchEvent内部调用。

②,传递顺序

一个点击事件产生后,传递顺序 Activity->Window->View,具体过程如下:

由Activity的dispatchTouchEvent来派发,由Activity内部的Window来完成。Window会将事件传递给DecorView,DecorView是页面的底层容器。Window的实现类是PhoneWindow。DecorView负责向子View中去分发,这样一级一级的下发。

3,View事件的滑动

了解了View的事件传递后,可以合理的利用事件传递解决滑动问题

1,View的滑动

①,普通移动

scrollTo和scrollBy:只能移动View的内容,不能移动View本身。

还可以通过LayoutParams移动位置。

②,渐进式移动

可以使用Scroller,实现弹性动画。还可以使用动画,延时策略。

2,滑动冲突

由于各种View的互相嵌套,会出现滑动冲突问题。

解决办法有两种:

①,外部拦截法

在父容器中的onInterceptTouchEvent中拦截。

②,内部拦截法

在子元素中处理,重写dispatTouchEvent方法,配合requestDisallowInterceptTouchEvent方法使用。


最后还是上图,面试只要掌握一条,由易到难,先总述,再挑一个熟悉的细讲。

在这里插入图片描述

五,多线程

概念:线程是操作系统能够进行运算调度的最小单位

1,死锁的四个条件

①,互斥条件
②,请求与保持条件
③,不可剥夺条件
④,循环等待条件

2,线程锁

①,synchronized:同步代码块或方法,是可重入锁。

②,volatile:是一个特殊的修饰符,只有成员变量才能使用它。

3,Java线程池

  • newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
  • newFixedThreadPool :创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool :创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。
  • newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

4,Android中线程

①,AsyncTask:是一种轻量的异步任务类,可以在线程池中执行后台任务,执行结果可以交给主线程。
其中封装了Thread和Handler。

AsyncTask四个核心方法:

  • onPreExecute():在主线程中执行,在异步任务执行之前。
  • doInBackground():在线程池中执行,用于执行异步任务。
  • onProgressUpdate():在主线程中执行,当后台任务的执行进度发生改变时此方法会调用。
  • onPostExecute():在主线程中执行,在异步任务执行之后。

工作原理:

从它的execute方法开始,其中里边有Executor,这是一个串行线程池,其中SerialExecutor是Executor的一个实现。

②,HandlerThread:继承了Thread,它是一种可以使用Handler的Thread,内部通过Looper.prepare()来创建消息队列,Looper.loop()来开启消息循环。

③,IntentService:内部封装了HandlerThread和Handler。

六,进程通信

由于进程之间是不能互相访问的,因此就有了多进程通信的技术,android中特有的多进程通信是Binder。正常情况下,一个应用就是一个进程。但是android中一个应用也可以有多个进程,那就是给四大组件指定进程,通过android:process属性。其中以“:”开头的为私有进程,不以“:”开头的为共有进程。

多进程带来的问题:

  • 静态成员和单例模式完全失效
  • 线程同步机制完全失效
  • SharedPreferences的可靠性下降
  • Application会多次创建

1,序列化

①,Serializable

Serializable是Java提供的一个接口,使用时在类中指定serialVersionUID。

对象的序列化和反序列化用到了ObjectOutputStream和ObjectInputStream。

Serializable序列化是保存在磁盘的。

②,Parcelable

Parcelable也是一个接口,内部是由Parcel完成的。Parcelable序列化是保存在内存的。

2,Binder

Binder可大致分为:Binder驱动,Service Manager,Service,Client这几部分.。

Binder是作为一个特殊的字符型设备而存在,设备节点/dev/binder,遵循Linux设备驱动模型。主要通过binder_ioctl函数与用户空间进程交换数据。

Binder的实质就是要把对象从一个进程映射到另一个进程。Binder内部使用内存映射。

详细的讲解

3,AIDL

AIDL的实现主要分为两步:

服务端:创建Service监听客户端请求,创建AIDL将暴露给客户端的接口在AIDL中声明,最后在Service中实现这个AIDL接口

客户端:绑定服务端Service,将服务端返回的Binder对象转成AIDL接口所属类型。

①,服务端

单独创建个包名,为啥要单独创建包名呢。第一复制到别的应用方便,客户端和服务器端包名要一致。第二客户端需要反序列化服务端中的类。

创建三个文件:Book.java,Book.aidl,IBookManager.aidl

Book.java类实现了Parcelable接口

public class Book implements Parcelable {
    private String bookName;

    protected Book(Parcel in) {
        bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(bookName);
    }
}

看下Book.aidl中的内容,就是这么简单。

parcelable Book ;

在看IBookManager.aidl中的内容。其中看到了in关键字,这是aidl的数据类型。in表示输入型参数,out表示输出型参数,inout表示输入输出型参数

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

开始创建服务,这里的CopyOnWriteArrayList支持并发读写。注册服务,指定action。

public class BookManagerService extends Service {
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book("android"));
        mBookList.add(new Book("java"));
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };
}

②,客户端

需要注意的是,客户端的onServiceConnected和onServiceDisconnected,都是在UI线程中。

 Intent intent = new Intent();
 intent.setPackage("包名");
 intent.setAction("指定action");
 bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
private ServiceConnection mConnection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager= IBookManager.Stub.asInterface(service);
            try {
                List<Book> list=bookManager.getBookList();
                Log.e("MainActivity", "onServiceConnected: size="+list.size() );
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

③,RemoteCallbackList

删除跨进程接口类,实现进程间的接口传递删除。

④,服务以外挂掉重连

第一种:给Binder设置DeathRecipient监听,当Binder死亡,会受到binderDied回调,进行重连。
第二种:onServiceDisconnected重连远程服务。

⑤,安全验证

第一种:使用permission,在onBind中进行验证。
第二种:在onTransact方法中进行权限验证。

4,Messenger

Messenger底层实现是通过AIDL,一次处理一个请求。

服务端:

首先在服务端创建Service来处理客户端连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回一个Messenger对象底层的Binder.

客户端:

绑定服务端Service,通过服务端IBinder对象创建一个Messenger。

5,ContentProvider

跨进程通信数据共享,一般用于数据库访问,使用很简单,只需要继承ContentProvider就可以,然后在清单文件中注册。

   <provider
        android:authorities="唯一标识"
        android:name="类名"
        android:permission="权限"/>

android:authorities是唯一标识,通过这个属性外部应用可以访问,比如访问如下:

   Uri uri= Uri.parse("content://唯一标识");
   getContentResolver().query(uri,null,null,null,null);

如果数据改变,通知外界,可以使用ContentObserver的registerContentObserver方法注册,通过unRegisterContentObserver方法来解除。

如果是按照类型查询不同表数据,可以使用UriMatcher

启动过程

ContentProvider所在进程启动时,ContentProvider会同时启动并被发布到AMS中。ContentProvider的onCreate要先于Application的onCreate。

①,通过AMS的startProcessLocked先启动应用进程,进入ActivityThread的main方法。
②,创建ContextImpl和Instrumentation,创建Application。
③,ActivityThread类的acquireProvider()。

6,Socket

Socket分为流式套接字和用户套接字两种,分别对应网络传输层的TCP和UDP协议。TCP解析是面向连接的协议,提供稳定的双向通信功能,连接时需要三次握手。UDP是无连接的,提供不稳定的单向通信。

1,服务端设计,当Service启动时,建立通信。

关键代码如下:

ServerSocket serverSocket=new ServerSocket(8688);
Socket client=serverSocket.accept();

//用于接收客户端消息
BufferedReader in=new BufferedReader(new InputStreamReader(client.getInputStream()));
//用户向客户端发送消息
PrintWriter out=new PrintWriter(new BufferedWriter(
new OutputStreamWriter(client.getOutputStream())),true)

2,客户端

Socket socket=new Socket("localhost",8688);
//用于接收服务端消息
BufferedReader in=new BufferedReader(
new InputStreamReader(socket.getInputStream()));

七,性能优化1

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值