Android知识点

2017-09-08  本文已影响0人  李成新

1、Android 线程间通信有哪几种方式(重要)

共享内存(变量);文件,数据库、Handler;Java 里的 wait(),notify(),notifyAll()

2、请介绍下 AsyncTask的内部实现,适用的场景是

AsyncTask 内部也是 Handler 机制来完成的,只不过 Android 提供了执行框架来提供线程池来

执行相应地任务,因为线程池的大小问题,所以 AsyncTask 只应该用来执行耗时时间较短的任务,

比如 HTTP 请求,大规模的下载和数据库的更改不适用于 AsyncTask,因为会导致线程池堵塞,没有

3、Activity生命周期

-启动Activity: onCreate()—>onStart()—>onResume(),Activity进入运行状态。

-Activity退居后台: 当前Activity转到新的Activity界面或按Home键回到主屏: onPause()—>onStop(),进入停滞状态。

-Activity返回前台: onRestart()—>onStart()—>onResume(),再次回到运行状态。

-Activity退居后台,且系统内存不足, 系统会杀死这个后台状态的Activity(此时这个Activity引用仍然处在任务栈中,只是这个时候引用指向的对象已经为null),若再次回到这个Activity,则会走onCreate()–>onStart()—>onResume()(将重新走一次Activity的初始化生命周期)

-锁屏:onPause()->onStop()

-解锁:onStart()->onResume()

4、通过Acitivty的xml标签来改变任务栈的默认行为

-使用android:launchMode="standard|singleInstance|singleTask|singleTop"来控制Acivity任务栈。

任务栈是一种后进先出的结构。位于栈顶的Activity处于焦点状态,当按下back按钮的时候,栈内的Activity会一个一个的出栈,并且调用其onDestory()方法。如果栈内没有Activity,那么系统就会回收这个栈,每个APP默认只有一个栈,以APP的包名来命名.

-standard : 标准模式,每次启动Activity都会创建一个新的Activity实例,并且将其压入任务栈栈顶,而不管这个Activity是否已经存在。Activity的启动三回调(onCreate()->onStart()->onResume())都会执行。standard : 标准模式,每次启动Activity都会创建一个新的Activity实例,并且将其压入任务栈栈顶,而不管这个Activity是否已经存在。Activity的启动三回调(onCreate()->onStart()->onResume())都会执行。

-singleTop : 栈顶复用模式.这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,所以它的启动三回调就不会执行,同时Activity的onNewIntent()方法会被回调.如果Activity已经存在但是不在栈顶,那么作用与standard模式一样.

-singleTask: 栈内复用模式.创建这样的Activity的时候,系统会先确认它所需任务栈已经创建,否则先创建任务栈.然后放入Activity,如果栈中已经有一个Activity实例,那么这个Activity就会被调到栈顶,onNewIntent(),并且singleTask会清理在当前Activity上面的所有Activity.(clear top)

-singleInstance : 加强版的singleTask模式,这种模式的Activity只能单独位于一个任务栈内,由于栈内复用的特性,后续请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了

Activity的堆栈管理以ActivityRecord为单位,所有的ActivityRecord都放在一个List里面.可以认为一个ActivityRecord就是一个Activity栈

5、Fragment的生命周期和activity如何的一个关系。

6、为什么在Service中创建子线程而不是Activity中

-Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例

-在一个Activity中创建的子线程,另一个Activity无法对其进行操作

-Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例

7、Service的两种启动方法,有什么区别

-.在Context中通过public boolean bindService(Intent service,ServiceConnection conn,int flags) 方法来进行Service与Context的关联并启动,并且Service的生命周期依附于Context(不求同时同分同秒生!但求同时同分同秒屎!!)。

-通过public ComponentName startService(Intent service)方法去启动一个Service,此时Service的生命周期与启动它的Context无关。

-.要注意的是,whatever,都需要在xml里注册你的Service,就像这样:

android:name=".packnameName.youServiceName"

android:enabled="true" />

8、广播(Broadcast Receiver)的两种动态注册和静态注册有什么区别。

-静态注册:在AndroidManifest.xml文件中进行注册,当App退出后,Receiver仍然可以接收到广播并且进行相应的处理

-动态注册:在代码中动态注册,当App退出后,也就没办法再接受广播了

9、ContentProvider使用方法

http://blog.csdn.net/juetion/article/details/17481039

10、目前能否保证service不被杀死

-Service设置成START_STICKY

-提升service优先级

-在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。

-【结论】目前看来,priority这个属性貌似只适用于broadcast,对于Service来说可能无效

-提升service进程优先级

-onDestroy方法里重启service

11、Android 内存泄漏总结

Java 内存分配策略

java 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、栈区和堆区。

-静态存储区(方法区):主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。

-栈区 :当方法被执行时,方法体内的局部变量(其中包括基础数据类型、对象的引用)都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

-堆区 : 又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存,也就是对象的实例。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。

栈与堆的区别:

在方法体内定义的(局部变量)一些基本类型的变量和对象的引用变量都是在方法的栈内存中分配的。当在一段方法块中定义一个变量时,Java 就会在栈中为该变量分配内存空间,当超过该变量的作用域后,该变量也就无效了,分配给它的内存空间也将被释放掉,该内存空间可以被重新使用。

堆内存用来存放所有由 new 创建的对象(包括该对象其中的所有成员变量)和数组。在堆中分配的内存,将由 Java 垃圾回收器来自动管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,这个特殊的变量就是我们上面说的引用变量。我们可以通过这个引用变量来访问堆中的对象或者数组。

结论:

局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。—— 因为它们属于方法中的变量,生命周期随方法而结束。

成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体)—— 因为它们属于类,类对象终究是要被new出来使用的。

12线程通信基础流程分析

Looper(先分析这个是因为能够引出四者的关系) 在Looper中,维持一个Thread对象以及MessageQueue,通过Looper的构造函数我们可以知道:

private Looper(boolean quitAllowed) {

mQueue = new MessageQueue(quitAllowed);//传入的参数代表这个Queue是否能够被退出

mThread = Thread.currentThread();

}

Looper在构造函数里干了两件事情:

-将线程对象指向了创建Looper的线程

-创建了一个新的MessageQueue

分析完构造函数之后,接下来我们主要分析两个方法:

-looper.loop()

-looper.prepare()

looper.loop()(在当前线程启动一个Message loop机制,此段代码将直接分析出Looper、Handler、Message、MessageQueue的关系)

public static void loop() {

final Looper me = myLooper();//获得当前线程绑定的Looper

if (me == null) {

throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

}

final MessageQueue queue = me.mQueue;//获得与Looper绑定的MessageQueue

// Make sure the identity of this thread is that of the local process,

// and keep track of what that identity token actually is.

Binder.clearCallingIdentity();

final long ident = Binder.clearCallingIdentity();

//进入死循环,不断地去取对象,分发对象到Handler中消费

for (;;) {

Message msg = queue.next(); // 不断的取下一个Message对象,在这里可能会造成堵塞。

if (msg == null) {

// No message indicates that the message queue is quitting.

return;

}

// This must be in a local variable, in case a UI event sets the logger

Printer logging = me.mLogging;

if (logging != null) {

logging.println(">>>>> Dispatching to " + msg.target + " " +

msg.callback + ": " + msg.what);

}

//在这里,开始分发Message了

//至于这个target是神马?什么时候被赋值的?

//我们一会分析Handler的时候就会讲到

msg.target.dispatchMessage(msg);

if (logging != null) {

logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);

}

// Make sure that during the course of dispatching the

// identity of the thread wasn't corrupted.

final long newIdent = Binder.clearCallingIdentity();

if (ident != newIdent) {

Log.wtf(TAG, "Thread identity changed from 0x"

+Long.toHexString(ident) + " to 0x"

+Long.toHexString(newIdent) + " while dispatching to "

+msg.target.getClass().getName() + " "

+msg.callback + " what=" + msg.what);

}

//当分发完Message之后,当然要标记将该Message标记为 正在使用

msg.recycleUnchecked();

}

}

分析了上面的源代码,我们可以意识到,最重要的方法是:

-queue.next()

-msg.target.dispatchMessage(msg)

-msg.recycleUnchecked()

其实Looper中最重要的部分都是由Message、MessageQueue组成的有木有!这段最重要的代码中涉及到了四个对象,他们与彼此的关系如下:

-MessageQueue:装食物的容器

-Message:被装的食物

-Handler(msg.target实际上就是Handler):食物的消费者

-Looper:负责分发食物的人

looper.prepare()(在当前线程关联一个Looper对象) ####\

private static void prepare(boolean quitAllowed) {

if (sThreadLocal.get() != null) {

throw new RuntimeException("Only one Looper may be created per thread");

}

//在当前线程绑定一个Looper

sThreadLocal.set(new Looper(quitAllowed));

}

以上代码只做了两件事情:

-判断当前线程有木有Looper,如果有则抛出异常(在这里我们就可以知道,Android规定一个线程只能够拥有一个与自己关联的Looper)。

-如果没有的话,那么就设置一个新的Looper到当前线程。

Handler 由于我们使用Handler的通常性的第一步是:

Handler handler = new Handler(){

//你们有没有很好奇这个方法是在哪里被回调的?

//我也是!所以接下来会分析到哟!

@Override

public void handleMessage(Message msg) {

//Handler your Message

}

};

/空参数的构造方法与之对应,这里只给出主要的代码,具体大家可以到源码中查看

public Handler(Callback callback, boolean async) {

//打印内存泄露提醒log

....

//获取与创建Handler线程绑定的Looper

mLooper = Looper.myLooper();

if (mLooper == null) {

throw new RuntimeException(

"Can't create handler inside thread that has not called Looper.prepare()");

}

//获取与Looper绑定的MessageQueue

//因为一个Looper就只有一个MessageQueue,也就是与当前线程绑定的MessageQueue

mQueue = mLooper.mQueue;

mCallback = callback;

mAsynchronous = async;

}

带上问题:

-Looper.loop()死循环中的msg.target是什么时候被赋值的?

-handler.handleMessage(msg)在什么时候被回调的?

A1:Looper.loop()死循环中的msg.target是什么时候被赋值的? 要分析这个问题,很自然的我们想到从发送消息开始,无论是handler.sendMessage(msg)还是handler.sendEmptyMessage(what),我们最终都可以追溯到以下方法

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {

//引用Handler中的MessageQueue

//这个MessageQueue就是创建Looper时被创建的MessageQueue

MessageQueue queue = mQueue;

if (queue == null) {

RuntimeException e = new RuntimeException(

this + " sendMessageAtTime() called with no mQueue");

Log.w("Looper", e.getMessage(), e);

return false;

}

//将新来的Message加入到MessageQueue中

return enqueueMessage(queue, msg, uptimeMillis);

}

我们接下来分析enqueueMessage(queue, msg, uptimeMillis):

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

//显而易见,大写加粗的赋值啊!

msg.target = this;

if (mAsynchronous) {

msg.setAsynchronous(true);

}

return queue.enqueueMessage(msg, uptimeMillis);

}

A2:handler.handleMessage(msg)在什么时候被回调的? 通过以上的分析,我们很明确的知道Message中的target是在什么时候被赋值的了,我们先来分析在Looper.loop()中出现过的过的dispatchMessage(msg)方法

public void dispatchMessage(Message msg) {

if (msg.callback != null) {

handleCallback(msg);

} else {

if (mCallback != null) {

if (mCallback.handleMessage(msg)) {

return;

}

}

//看到这个大写加粗的方法调用没!

handleMessage(msg);

}

}

加上以上分析,我们将之前分析结果串起来,就可以知道了某些东西: Looper.loop()不断地获取MessageQueue中的Message,然后调用与Message绑定的Handler对象的dispatchMessage方法,最后,我们看到了handleMessage就在dispatchMessage方法里被调用的。

通过以上的分析,我们可以很清晰的知道Handler、Looper、Message、MessageQueue这四者的关系以及如何合作的了。

总结: 当我们调用handler.sendMessage(msg)方法发送一个Message时,实际上这个Message是发送到与当前线程绑定的一个MessageQueue中,然后与当前线程绑定的Looper将会不断的从MessageQueue中取出新的Message,调用msg.target.dispathMessage(msg)方法将消息分发到与Message绑定的handler.handleMessage()方法中。

一个Thread对应多个Handler 一个Thread对应一个Looper和MessageQueue,Handler与Thread共享Looper和MessageQueue。 Message只是消息的载体,将会被发送到与线程绑定的唯一的MessageQueue中,并且被与线程绑定的唯一的Looper分发,被与其自身绑定的Handler消费。

13 HandlerThread面试

一、handlerThread是什么,有哪些特点

handler+thread+looper

-HandlerThread本质上就是一个线程类,继承thread

-HandlerThread有自己的内部Looper对象,可以进行looper循环;

-通过获取HandlerThread的looper对象传递给Handler对象。可以在handlerMessage执行异步任务

-优点是不会有堵塞,减少对性能的消耗,缺点是不能进行多任务处理,需要等待,处理效率较低。

-与线程池注重并发不同,HandlerThrad是一个串行队列,它只有一个线程。

14 Webview面试详解

Webview常见的一些坑

1、webview 安全漏洞,在android api 16及以前没有正确的使用webView.addJavaScriptIterface方法,远程攻击者可以通过使用java 的反射机制利用该漏洞执行java对象的方法

2、webView在内存中使用造成内存泄漏,要先在w外部容器中移除webView再调用webView的destroy

3、jsbridge

4、webViewClient.onPageFinished--->WebViewChromClient.onProgressChanged方法

5、后台耗电:开启进程没有销毁

5、硬件加速不要开启

15 AsyncTask面试

1、什么是AyncTask?

它本质上就是一个封装了线程池和handler的异步框架,执行异步任务,因为内部封装了handler可以在主线程和任务线程进行切换

2、AysncTask三个参数五个方法?

3、AnsycTask的机制原理

-AysncTask的本质是一个静态的线程池,AyncTask派生出的子类可以实现不同的异步任务,这些异步任务都是提交到静态的线程池中执行

-线程池的工作线程执行doinbackBackground(mParams)方法执行异步任务

-当任务状态改变后,工作线程会向UI线程发送消息,ASyncTask的内部的InternalHandler响应这些消息,并调用相关的回调函数

4、AsyncTask注意事件

16 LocalBroadcastManager详解

1、localbradcastManager高效的原因是因为内部通过Handler实现,她的sendBroadcast()方法含义并非和我们平时所用的一样,它其实是通过handler发送一个message实现的。

2、他是通过handler实现广播发送,那么相比系统广播通过Binder更高效,同时使用Handler来实现,别的应用也无法像我们的应用发送该广播。我们的广播也不会离开我们的应用。

3、LoacalBradcastManager内部协作主要靠两个Map集合:mReceives和mActions,还有一个List集合mPendingBroadcasts,这个是存储待接受的广播对象

17、 Android中进程间通信的几种方式

broadcast:用于发送和接收广播!实现信息的发送和接收! 该方式只是接收到一个广播消息,主要的业务逻辑代码是在自己的onReceive()方法里面实现的。

那么它的优点是:注册了这个广播接收器的应用都能够收到广播,范围广。缺点是:速度慢点,而且必须在一定时间内把事情处理完(onReceive执行必须在几秒之内),否则的话系统给出ANR。

aidl:用于不同程序将服务的相互调用!实现了一个程序为另一个程序服务的功能!业务逻辑代码在远程的服务中,通过aidl来调用。

优点是:速度快(系统底层直接是共享内存),性能稳,效率高,一般进程间通信就用它。

Content Provider:用于将程序的数据库人为地暴露出来!实现一个程序可以对另个程序的数据库进行相对用的操作!主要是用来将自己的数据库共享出来供别的程序调用或者修改。

因为只是把自己的数据库暴露出去,其他程序都可以来获取数据,数据本身不是实时的,不像前两者,只是启个数据供应作用。一般是某个成熟的应用来暴露自己的数据用的。 你要是为了进程间通信,还是别用这个了,这个又不是实时数据。

上一篇下一篇

猜你喜欢

热点阅读