目录
- 算法
- 冒泡排序
- 网络通信协议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;i<arr.length-1;i++){ //外层控制排序趟数
for(int j=0;j<arr.length-1-i;j++){ //内层控制每趟排多少次
if(arr[j]>arr[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){
//接受到广播后的处理逻辑
}
}
<reciver andorid:name=".MyReciver">
<intent-filter>
<action android:name="andorid.intent.action.BOOT_COMPLETED"
</intent-filter>
</reciver>
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:单实例模式,该种模式下活动会启用一个新的活动栈来管理这个活动。
如上图所示,栈中有四个活动,想问怎样使A跳转到D,然后按返回键直接回到主界面,不回到A?
答案:A,B,C都是默认模式,D设置singleTask模式
注意:A,B,C都是默认模式,D设置singleInstance模式,这种方法是不行的
7.ArrayList与LinkedList和Vector的区别
ArrayList:基于数组结构,有角标索引,所以通过索引查询比较快,因为增删元素相当于底层重新创建数组,所以增删慢,线程不安全
LinkedList:基于双向循环链表结构,增删元素只是改变了该元素节点的引用,所以增删比较快,查询慢,线程不安全
Vector:基于数组结构,查询快,增删慢,线程安全
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.说说线程池管理机制
首先,线程池有一下优点:
- 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
- 能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占资源而导致的阻塞现象。
- 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。
线程池主要包括线程池和任务队列,如下图所示:
ThreadPoolExecutor的构造方法:
public ThreadPoolExecutor(
int corePoolSize, //核心线程数
int maxPoolSize, //最大线程数
long keepAliveTime, //非核心线程闲置时的超时时长
TimeUnit unit, //制定keepAliveTime参数的时间单位,它是一个枚举
BlockingDeque<Runnable> 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,可以切换显示,如下图所以。
最开始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.写下单例模式
14、Android中常用的五种布局
LinearLayout 线性布局
RelativeLayout 相对布局
FrameLayout 帧布局
TableLayout 表格布局
AbsoluteLayout 绝对布局
15、Android里的动画有几种,它们的区别是什么?
Android3.0后有3中动画,分别是帧动画(FrameAnimation)、补间动画(TweenAnimation)、属性动画(PropertyAnimation)。
区别:
- 帧动画类似于幻灯片,由多张图片顺序播放而成
- 补间动画望文生义,就是在两点之间插入渐变值来平滑过渡。它包括透明动画、平移动画、缩放动画、旋转动画。补间动画只是在视觉上View在改变,实际位置并没有改变
- 属性动画和补间动画类似,区别是属性动画是真实改变位移。
16、简述Android垃圾回收机制,垃圾回收算法有几种?
垃圾回收意义:
它使java程序员不需要时时刻刻关注内存管理分配的工作,垃圾回收机制会自动管理jvm的内存空间,将那些不再会用到的“垃圾对象”清理掉,释放出更多的内存空间。
17、说说Android 对象四大引用?
- 强引用:java中最普遍的,只要强引用还在,垃圾回收器就永远不会回收掉被引用的对象。
- 软引用:描述一些非必须的对象,在系统内存不足时,这类对象会被垃圾回收器回收掉。
- 弱引用:描述一些非必须对象,如果GC发生了,无论系统内存是否足够,都会回收掉这类对象。
- 虚引用:持有虚引用的对象,就像没有任何引用一样,时刻都有可能被垃圾回收器回收掉。
18、在Activity的onCreate方法中,开启一个线程并更新UI元素,如下代码,会报错吗?为什么,如果报错报什么错误?
@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就创建好了。