日期: 2021 年 5 月 22 日

Android多线程(Handler篇)

先放流程图:

%title插图%num

由于Android中的耗时操作不能放入主线程中,所以实现多线程是必须的。今天的主角是Handler,本文将从使用及源码来分析探索其奥秘。

使用
步骤:

创建Handler对象,实现handlMessage()方法
创建Runnable线程
此时产生一个Looper,并自动创建一个消息队列MessageQueue()
Looper轮询MessageQueue交给Handler
Handler做处理
其中1、2、5为使用步骤,其他在后面分析源码时会讲到
使用方法

public class MainActivity extends AppCompatActivity {
@SuppressLint(“HandlerLeak”)
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 1:
// 处理事件
break;
}
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
// 耗时操作
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}
}).start();
}
}

使用就写这么多,接下来重点分析Handler原理。

原理分析
首先列一下将会讲到的几个对象

ThreadLocal
Looper
MessageQueue
Handler
Message
ThreadLocal
关于他我看了几篇博客,很大一部分写的是错的,所以
注意:

他并不是解决共享对象的多线程访问问题的!!!
他并没有创建对象的拷贝或副本!!!
目的:他只是为了保证每个线程都拥有同一个类的不同对象
实质:每个线程里都new了同一个类的对象
作用:你或许觉得这样做很傻,但是如果使用全局变量就要考虑线程安全问题,而线程安全是以消耗性能为前提的,所以这种设计可以说很巧妙
场景:每个线程都需要相同类型的对象,又各自独立,并且与他相关的操作不少时,如,Looper,ActivityThread,AMS 等

private static final ThreadLocal tSession = new ThreadLocal();
public static Session getSession() throws Exception{
Session s = tSession.get();
try{
if(s==null){
s = getSessionFactory.openSession();
tSession.set(s);
}
}catch(Exception e){

}
return s;
}

我们看一下ThreadLocal的源码,主要分析get()和set()

public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings(“unchecked”)
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

set、get操作的都是ThreadLocalMap,key=当前线程,value=线程局部变量缓存值。可以看到get()实际上都是调用getMap(传入当前线程)来取得当前线程的ThreadLocalMap对象

set(),实际上是调用了ThreadLocalMap的set(),ThreadLocalMap的set()涉及到哈希散列算法,我会在之后博客里详细分析这里先不提算法的事。
get(),从当前线程中获取ThreadLocalMap,查询当前ThreadLocal变量实例对应的Entry,如果不为null,获取value,如果map为null,走初始化方法
由此看出不是如很多博主写的各线程用了同一个对象又相互独立那么神奇,只不过是用线程当做键在其中维护了一个私有变量而已。得到ThreadLocalMap后如何得到维护的变量呢,在这一句

ThreadLocalMap.Entry e = map.getEntry(this);
//this指代的ThreadLocal对象

所以过程就很清晰了,我们来总结一下:

声明一个全局公用的ThreadLocal实例作为key
在线程中new一个或取出已经存了的对象作为value
将此key-value放入ThreadLocalMap中
将当前线程作为key,ThreadLocalMap作为值放入ThreadLocal中
比较绕多读几遍就能明白了,再放一张图加深理解

%title插图%num
Looper
两件事

创建消息队列
在队列中循环取消息
两个方法

prepare()
loop()
两个作用

保证当前线程只有一个looper和一个MessageQueue
从MessageQueue中取消息交给target的dispatchMessage
下面我们分析源码

#####构造方法

private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}

很容易看出当Looper构造时创建了一个消息队列

#####prepare()方法

private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException(“Only one Looper may be created per thread”);
}
sThreadLocal.set(new Looper(quitAllowed));
}

通过前面ThreadLocal的分析可知sThreadLocal.get()得到了Looper对象,当Looper存在是报错,不存在是创建一个存入ThreadLocal中,保证线程里有且只有一个Looper对象。

#####loop()

/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);
}
final MessageQueue queue = me.mQueue;

// 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();

for (;;) {
Message msg = queue.next(); // might block
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
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(“>>>>> Dispatching to ” + msg.target + ” ” +
msg.callback + “: ” + msg.what);
}

final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (slowDispatchThresholdMs > 0) {
final long time = end – start;
if (time > slowDispatchThresholdMs) {
Slog.w(TAG, “Dispatch took ” + time + “ms on ”
+ Thread.currentThread().getName() + “, h=” +
msg.target + ” cb=” + msg.callback + ” msg=” + msg.what);
}
}

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);
}

msg.recycleUnchecked();
}
}

1)拿到ThreadLocal中的Looper,若没有则prepare()
2)拿到Looper的MessageQueue
3)进入死循环,调用msg.target.dispatchMessage(msg),发给Handler
4)释放资源

MessageQueue
一个单链表结构的消息队列

Handler
#####构造函数

public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, “The following Handler class should be static or leaks might occur: ” +
klass.getCanonicalName());
}
}

mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
“Can’t create handler inside thread that has not called Looper.prepare()”);
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}

构造时通过Looper.myLooper获取当前线程保存的Looper实例,再获取这个Looper的MessageQueue

#####发送消息

public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}

/**
* Sends a Message containing only the what value.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}

/**
* Sends a Message containing only the what value, to be delivered
* after the specified amount of time elapses.
* @see #sendMessageDelayed(android.os.Message, long)
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}

发送消息时所有方法都实际调用了sendMessageAtTime

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + ” sendMessageAtTime() called with no mQueue”);
Log.w(“Looper”, e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}

是获取MessageQueue调用enqueueMessage();
#####接下来看enqueueMessage中

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

将msg的target属性赋值为Handler自己,实现了Message与Handler的绑定,并调用了MessageQueue中的enqueueMessage()方法

boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException(“Message must have a target.”);
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + ” This message is already in use.”);
}

synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + ” sending message to a Handler on a dead thread”);
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}

msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don’t have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}

// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}

对前一个方法进行补充,把msg放入MessageQueue中,这时候轮询取消息(在前面Looper已经分析),调用dispatchMessage()

public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

*终调用handlerMessage()方法

/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}

一个空方法,在这里处理操作,end

Android常用的开启子线程的方法

Google在Android4.0之后,禁止主线程访问网络,是为了更好的用户体验。也就是主线程是为了界面的显示。如果主线程访问网络,就会造成“卡顿”。也就是对于网络状况的不可预见性,很有可能在网络访问的时候造成阻塞,那么这样一来我们的主线程UI线程就会出现假死的现象,产生很不好的用户体验。所以,默认的情况下如果直接在主线程中访问就报出了这个异常,名字是NetworkOnMainThreadException。主线程操作5s的相应时间,就会关闭该线程,所以将耗时很大的操作放在子线程。

在多线程编程这块,我们经常要使用Handler(处理),Thread(线程)和Runnable这三个类,那么他们之间的关系

你是否弄清楚了呢?

首先说明Android的CPU分配的*小单元是线程,Handler一般是在某个线程里创建的,因而Handler和Thread就是相互绑定的,一一对应。
而Runnable是一个接口,Thread是Runnable的子类。所以说,他俩都算一个进程。
HandlerThread顾名思义就是可以处理消息循环的线程,他是一个拥有Looper的线程,可以处理消息循环。

与其说Handler和一个线程绑定,不如说Handler是和Looper一一对应的。

Handler是沟通Activity 与Thread/runnable的桥梁。而Handler是运行在主UI线程中的,它与子线程可以通过Message对象来传递数据

1、*种:

new Thread(){
@Override
public void run() {
//需要在子线程中处理的逻辑
}
}.start();

2、第二种

new Thread(new Runnable() {
@Override
public void run() {
//需要在子线程中处理的逻辑
}
}).start();

3、睡眠

sleep阻塞

Thread.sleep(times)使当前线程从Running状态放弃处理器进入Block状态,休眠times毫秒,再返回Runnable状态。

Thread.sleep(1000);

 

Android 线程的正确使用姿势

进程优先级(Process Priority)

线程寄宿在进程当中,线程的生命周期直接被进程所影响,而进程的存活又和其优先级直接相关。在处理进程优先级的时候,大部分人靠直觉都能知道前台进程(Foreground Process)优先级要高于后台进程(Background Process)。但这种粗糙的划分无法满足操作系统高精度调度的需求。无论Android还是iOS,系统对于Foreground,Background进程有进一步的细化。

Foreground Process

Foreground一般意味着用户双眼可见,可见却不一定是active。在Android的世界里,一个Activity处于前台之时,如果能采集用户的input事件,就可以判定为active,如果中途弹出一个Dialog,Dialog变成新的active实体,直接面对用户的操作。被部分遮挡的activity尽管依然可见,但状态却变为inactive。不能正确的区分visible和active是很多初级程序员会犯的错误。

 

Background Process

后台进程同样有更细的划分。所谓的Background可以理解为不可见(invisible)。对于不可见的任务,Android也有重要性的区分。重要的后台任务定义为Service,如果一个进程包含Service(称为Service Process),那么在“重要性”上就会被系统区别对待,其优先级自然会高于不包含Service的进程(称为Background Process),*后还剩一类空进程(Empty Process)。Empty Process初看有些费解,一个Process如果什么都不做,还有什么存在的必要。其实Empty Process并不Empty,还存在不少的内存占用。

在iOS的世界里,Memory被分为Clean Memory和Dirty Memory,Clean Memory是App启动被加载到内存之后原始占用的那一部分内存,一般包括初始的stack, heap, text, data等segment,Dirty Memory是由于用户操作所改变的那部分内存,也就是App的状态值。系统在出现Low Memory Warning的时候会首先清掉Dirty Memory,对于用户来说,操作的进度就全部丢失了,即使再次点击App图标,也是一切从头开始。但由于Clean Memory没有被清除,避免了从磁盘重新读取app数据的io损耗,启动会变快。这也是为什么很多人会感觉手机重启后,app打开的速度都比较慢。

同理Android世界当中的Empty Process还保存有App相关的Clean Memory,这部分Memory对于提升App的启动速度大有帮助。显而易见Empty Process的优先级是*低的。

综上所述,我们可以把Android世界的Process按优先级分为如下几类:

%title插图%num

进程的优先级从高到低依次分为五类,越往下,在内存紧张的时候越有可能被系统杀掉。简而言之,越是容易被用户感知到的进程,其优先级必定更高。

 

线程调度(Thread Scheduling)

Android系统基于精简过后的linux内核,其线程的调度受时间片轮转和优先级控制等诸多因素影响。不少初学者会认为某个线程分配到的time slice多少是按照其优先级与其它线程优先级对比所决定的,这并不完全正确。

Linux系统的调度器在分配time slice的时候,采用的CFS(completely fair scheduler)策略。这种策略不但会参考单个线程的优先级,还会追踪每个线程已经获取到的time slice数量,如果高优先级的线程已经执行了很长时间,但低优先级的线程一直在等待,后续系统会保证低优先级的线程也能获取更多的CPU时间。显然使用这种调度策略的话,优先级高的线程并不一定能在争取time slice上有*对的优势,所以Android系统在线程调度上使用了cgroups的概念,cgroups能更好的凸显某些线程的重要性,使得优先级更高的线程明确的获取到更多的time slice。

Android将线程分为多个group,其中两类group尤其重要。一类是default group,UI线程属于这一类。另一类是background group,工作线程应该归属到这一类。background group当中所有的线程加起来总共也只能分配到5~10%的time slice,剩下的全部分配给default group,这样设计显然能保证UI线程绘制UI的流畅性。

%title插图%num

有不少人吐槽Android系统之所以不如iOS流畅,是因为UI线程的优先级和普通工作线程一致导致的。这其实是个误会,Android的设计者实际上提供了background group的概念来降低工作线程的CPU资源消耗,只不过与iOS不同的是,Android开发者需要显式的将工作线程归于background group。

所以在我们决定新启一个线程执行任务的时候,首先要问自己这个任务在完成时间上是否重要到要和UI线程争夺CPU资源。如果不是,降低线程优先级将其归于background group,如果是,则需要进一步的profile看这个线程是否造成UI线程的卡顿。

虽说Android系统在任务调度上是以线程为基础单位,设置单个thread的优先级也可以改变其所属的control groups,从而影响CPU time slice的分配。但进程的属性变化也会影响到线程的调度,当一个App进入后台的时候,该App所属的整个进程都将进入background group,以确保处于foreground,用户可见的新进程能获取到尽可能多的CPU资源。用adb可以查看不同进程的当前调度策略。

当你的App重新被用户切换到前台的时候,进程当中所属的线程又会回归的原来的group。在这些用户频繁切换的过程当中,thread的优先级并不会发生变化,但系统在time slice的分配上却在不停的调整。

是否真的需要新线程?

开线程并不是提升App性能,解决UI卡顿的万金油。每一个新启的线程会消耗至少64KB的内存,系统在不同的线程之间switch context也会带来额外的开销。如果随意开启新线程,随着业务的膨胀,很容易在App运行的某个时间点发现几十个线程同时在运行。后果是原本想解决UI流畅性,却反而导致了偶现的不可控的卡顿。

移动端App新启线程一般都是为了保证UI的流畅性,增加App用户操作的响应度。但是否需要将任务放入工作线程需要先了解任务的瓶颈在哪,是i/o,gpu还是cpu?UI出现卡顿并不一定是UI线程出现了费时的计算,有可能是其它原因,比如layout层级太深。

尽量重用已有的工作线程(使用线程池)可以避免出现大量同时活跃的线程,比如对HTTP请求设置*大并发数。或者将任务放入某个串行的队列(HandlerThread)按顺序执行,工作线程任务队列适合处理大量耗时较短的任务,避免出现单个任务阻塞整个队列的情况。

用什么姿势开线程?

new Thread()

这是Android系统里开线程*简单的方式,也只能应用于*简单的场景,简单的好处却伴随不少的隐患。

1
2
3
4
5
6
new Thread(new Runnable() {
            @Override
            public void run() {
            }
        }).start();

这种方式仅仅是起动了一个新的线程,没有任务的概念,不能做状态的管理。start之后,run当中的代码就一定会执行到底,无法中途取消。

Runnable作为匿名内部类还持有了外部类的引用,在线程退出之前,该引用会一直存在,阻碍外部类对象被GC回收,在一段时间内造成内存泄漏。

没有线程切换的接口,要传递处理结果到UI线程的话,需要写额外的线程切换代码。

如果从UI线程启动,则该线程优先级默认为Default,归于default cgroup,会平等的和UI线程争夺CPU资源。这一点尤其需要注意,在对UI性能要求高的场景下要记得

Android多线程方式

1、前言
在Android开发中经常会使用到多线程,这里主要是总结Android开发中常见的多线程实现方式,以及这些多线程实现方式的一些特点
多线程实现方式主要有:

实现Thread的run()方法或者实现Runable接口
HandlerThread
AsyncTask
LoaderManager
2、Thread方式
一般使用异步操作*常见的一种方式,我们可以继承Thread,并重写run()方法,如下所示:

Thread syncTask = new Thread() {
@Override
public void run() {
// 执行耗时操作
}
};

syncTask.start();

还有另外一种启动线程的方式,即在创建Thread对象时,传入一个实现了Runable接口的对象,如下所示:

Thread syncTask = new Thread(new Runnable() {
@Override
public void run() {
// 执行耗时操作
}
});

syncTask.start();

Thread类中有几个方法的作用有些模糊,这里给出说明:

interrupt( ):
我们一般会使用该方法中断线程的执行,但该方法并不会中断线程,它的作用只是设置一个中断标志位,我们还得在run( )方法中判断这个标志位,并决定是否继续执行,通过这样的方式来达到中断的效果。 但该方法根据线程状态的不同,会有不同的结果。结果如下:
当线程由于调用wait( )、join( )、sleep( )而阻塞时,中断标志位将会被清空,并接收到InterruptedException异常。
当线程由于正在进行InterruptibleChannel类型的I/O操作而阻塞时,中断标志位将会置位,并接收到ClosedByInterruptException异常(I/O流也会自动关闭)
当线程由于进行Selector操作而阻塞时,中断标志位将会置位,但不会接收到异常
interrupted( )和isInterrupted( )的区别:
两个方法都是判断当前线程的中断标志位是否被置位,但调用interrupted( )方法后,中断标志位将会重置,而isInterrupted()不会被重置。

join( )和sleep( )的区别:两个方法都会让线程暂停执行
join()方法是让出执行资源(如:CPU时间片),使得其它线程可以获得执行的资源。所以调用join()方法会使进入阻塞状态,该线程被唤醒后会进入runable状态,等待下一个时间片的到来才能再次执行。
sleep( )不会让出资源,只是处于睡眠状态(类似只执行空操作)。调用sleep()方法会使进入等待状态,当等待时间到后,如果还在时间片内,则直接进入运行状态,否则进入runable状态,等待下个时间片。

3、HandlerThread
有些需求需要子线程不断的从一个消息队列中取出消息,并进行处理,处理完毕以后继续取出下一个处理。对于这个需求我们可以使用*种方式,实现一个Thread对象,并创建一个消息队列,在Thread对象的run方法中不断的从消息队列中取出消息进行处理。多以该线程的这些特点有点像一个Looper线程,我们可复用Handler机制提供的消息队列MessageQueue,而无需自己重新创建。
HandlerThread的内部实现机制很简单,在创建新的线程后,使该线程成为一个Looper线程,让该线程不断的从MessageQueue取出消息并处理。我们看一下HandlerThread的实现:

public class HandlerThread extends Thread {
int mPriority;
Looper mLooper;

/**
* Constructs a HandlerThread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}

@Override
public void run() {
// 要想让某个线程成为Looper线程,先调用Looper.prepare()为该线程创建一个Looper对象,并初始化MessageQueue对象
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
// 调用Looper.loop(),让该线程的Looper实例循环从MessageQueue中取出Message进行处理
Looper.loop();
}
}

4、AsyncTask
这是我们*经常使用的一种异步方式,在前面的两种多线程方式中,如果在子线程中进行了耗时的处理操作(如:网络请求、读写数据库等),当操作完毕后,我们需要更新UI上的显示状态,但在Android开发中我们是不能在子线程中更新UI界面的,所以还得在子线程中发送一个通知到主线程,让主线程去更新UI。这样的操作流程有些复杂,且都是重复性的工作。所以Android sdk中为我们抽象出AsyncTask这个类。

public class CustomAsyncTask extends AsyncTask<String, Integer, String> {
@Override
protected void onPreExecute() {
// 在开始执行异步操作前回调,该方法在主线程中执行
}

@Override
protected String doInBackground(String… strings) {
// 在该方法中进行异步操作,参数strings是在启动异步任务时execute(…)传递进来的
// 该异步任务放回的结果类型为String
return null;
}

@Override
protected void onProgressUpdate(Integer… values) {
// 该方法用户通知用户doInBackground()方法的处理进度,在主线程中被回调,所以可在该方法中更新UI
// 参数values用于指示处理进度
}

@Override
protected void onPostExecute(String result) {
// 该方法是在异步操作doInBackground()处理完毕后回调,参数result是doInBackground()的处理结果
// 该方法在主线程中被回调,可直接更新UI
}

@Override
protected void onCancelled(String result) {
super.onCancelled(result);

// 当调用cancel(boolean), 则在doInBackground()完成后回调该方法
// 注意: 参数result可能为null,
}
}

AsyncTask的内部使用了两个线程池,我们大概看一下AsyncTask的内部实现

// 顺序执行任务的线程池,注意这个线程池是静态的,每个AsyncTask对象共用这个线程池
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

// 我们启动异步任务的三个方法,都是向SerialExecutor.execute(runable)传递一个runable对象
public final AsyncTask<Params, Progress, Result> execute(Params… params) {
return executeOnExecutor(sDefaultExecutor, params);
}

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params… params) {

exec.execute(mFuture);

return this;
}

public static void execute(Runnable runnable) {
sDefaultExecutor.execute(runnable);
}

看一下SerialExecutor的实现

private static class SerialExecutor implements Executor {
// 存储待执行的异步任务
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;

public synchronized void execute(final Runnable r) {
// 其实并没有马上执行,而是添加到队列mTasks中, 进行一个排队
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
// 一个任务执行完后,再执行下一个
scheduleNext();
}
}
});

// 当前没有异步任务执行时,启动开始执行
if (mActive == null) {
scheduleNext();
}
}

protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
// 使用另外一个线程池分配线程,并执行任务
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}

所以在使用AsyncTask执行异步操作时,会先在SerialExecutor进行一个顺序排队, 后再用ThreadPoolExcutor线程池为你分配一个线程并执行。而整个应用的AsyncTask任务都在排同一条队,有可能等待排队的任务很多,所以一般不会使用AsyncTask执行一些优先级比较高的异步任务。
当然我们是可以跳过不需要进行排队,直接就通过线程池分配一个线程并执行异步任务,但需要注意同时执行太多的异步任务,会影响用户体验,我想Google就是为了限制同时创建太多的线程才会采用一个排队机制的

/** @hide */
public static void setDefaultExecutor(Executor exec) {
sDefaultExecutor = exec;
}

该方法是隐藏,但可使用反射,设置一个线程池。

5、Loader&LoaderManager
上面三种异步方式都可以用来加载一些耗时的数据,但有时我们加载数据的过程与Activity、Fragment的生命息息相关的。所以在使用上面说的那几种异步方式进行异步数据加载时,是需要去考虑Activity(Fragment)的生命周期是处于哪个阶段的。于是Android在Android 3.0以后引入了LoaderManager,主要用于执行一些耗时的异步数据加载操作,并根据Activity生命周期对异步处理进行调整,LoaderManager可以解决的问题包括:

加载的数据有变化时,会自动通知我们,而不自己监控数据的变化情况,如:用CursorLoader来加载数据库数据,当数据库数据有变化时,可是个展示变化的数据
数据的请求处理时机会结合Activity和Fragment的生命周期进行调整,如:若Acivity销毁了,那就不会再去请求新的数据
使用该方法加载数据涉及到两个类重要的类,Loader和LoaderManager:

Loader:该类用于数据的加载 ,类型参数D用于指定Loader加载的数据类型

public class Loader<D> {
}

一般我们不直接继承Loader,而是继承AsyncTaskLoader,因为Loader的加载工作并不是在异步线程中。而AsyncTaskLoader实现了异步线程,加载流程在子线程中执行。注意:对该类的调用应该在主线程中完成。

LoaderManager:
LoaderManager用于管理与Activity和Fragment关联的Loader实例,LoaderManager负责根据的Activity的生命周期对Loader的数据加载器进行调度,所以这里分工明确,Loader负责数据加载逻辑,LoaderManager
负责Loader的调度,开发者只需要自定义自己的Loader,实现数据的加载逻辑,而不再关注数据加载时由于Activity销毁引发的问题。

注意:其实AsyncTaskLoader内部实现异步的方式是使用AsyncTask完成的,上面我们说过AsyncTask的内部是有一个排队机制,但AsyncTaskLoader内部使用AsyncTask进行数据异步加载时,异步任务并不进行排队。而直接又线程池分配新线程来执行。

6、总结
我们来总结一下异步处理的方式,以及每种处理方式适合什么样的场景

直接使用Thread实现方式,这种方式简单,但不是很优雅。适合数量很少(偶尔一两次)的异步任务,但要处理的异步任务很多的话,使用该方式会导致创建大量的线程,这会影响用户交互。
HandlerThread,这种方式适合子线程有序的执行异步操作,异步任务的执行一个接着一个。
AsyncTask, 通常用于耗时的异步处理,且时效性要求不是非常高的那种异步操作。如果时效性要求非常高的操作,不建议使用这个方式,因为AsyncTask的默认实现是有内部排队机制,且是整个应用的AsyncTask的任务进行排队,所以不能保证异步任务能很快的被执行。
LoaderManager,当请求处理时机需要根据Activity的生命周期进行调整,或需要时刻监测数据的变化,那LoaderManager是很不错的解决方案。

谈谈你对android多线程的理解

在android中,需要处理一些耗时的操作,这些操作不能运行在UI线程中,不然会造成线程的阻塞,所以就需要多线程操作

在日常项目中比较常用的多线程操作主要有:

1.Handler

2.AsyncTask

3.IntentService

一:handler的理解

优点:对于对后台任务时,简单清晰

缺点:对于操作单个后台任务,代码过于繁琐

具体操作:

在主线程中创建Handler对象并实现handlmessage()方法,
创建runnable线程,先在线程中执行耗时操作,
开启一个线程会相应的产生一个looper,在初始化looper的时候会创建一个消息队列MessageQueue();
执行完耗时操作,通过handler将消息发送到消息队列中、、looper轮询消息队列将消息取出来交给Handler,
Handler接收到取出来的消息,并根据消息类型做出相应的处理

二:AsyncTask的理解

优点:操作简单方便,过程可控

缺点:对于多异步操作更新UI会变得很繁琐

具体操作:

onPreExecute()运行在主线程中,开启线程前的准备操作,
doInBackground()运行在子线程中,
onPreExecute()之后的操作,用于处理耗时操作,通过调用publishProcess()向 onProcessUpdata()推送消息
onProcessUpdata()运行在主线程中,当调用 publishProcess()方法时就会开启此方法,接收到推送过来的数据,更新UI进度页面
onPostExecute()运行在主线程中,当子线程耗时操作执行完毕后会调用此方法, doInBackground()返回的参数传递到这里来用于更新UI
调用execute()方法开启AsyncTask,类似runnable的start()方法

三:IntentService的理解

IntentService和普通的Service区别在于,IntentService在oncreate()方法中单独开启一个线程用于耗时操作
通过onHandleIntent(Intent intent)方法来处理耗时操作
在耗时操作执行完毕之后,会自动关闭service不用手动关闭
如果同时new出多个IntentService对象进行耗时操作,oncreate()和ondestory()方法会执行一次,onstart()、onstartcommand()、onHandleIntent()会执行多次。
执行完毕自动关闭service

Android 中的线程

一、Android线程的基本介绍
线程在Android中是一个很重要的概念,从用途上来说,Android中的线程可以分为主线程和子线程,主线程主要用来处理和界面相关的事,比如界面绘制和响应用户的操作,为了确保用户体验,主线程必须确保其响应速度,所有任何时候我们都不应该在主线程中处理非常耗时的任务,否则会造成界面卡顿甚至ANR;而子线程的作用就是完成耗时的操作,确保主线程的响应速度。主线程和子线程之间的通信是基于Handler机制,对于Handler机制可以参考:https://blog.csdn.net/xiao_nian/article/details/81011361。

1、Android中常见的线程形态基本介绍
除了Thread本身,在Android中可以扮演线程角色的还有很多,比如AsyncTask、IntentSevice、HandlerThread等,尽管他们的表现形式有别于传统的线程,但他们的本质还是Thread,只不过结合了一些其他的功能,让它们适用于不同的应用场景。AsyncTask封装了线程池和Handler,它主要是为了开发者在使用子线程中能够方便的更新UI;HandlerThread是一种具有消息循环的线程,在它的内部可以使用Handler。IntentService内部采用HanderThread来执行任务,当任务执行完毕后IntentService会自动退出。

2、Android线程池的基本介绍
在操作系统中,线程是操作系统调度的*小单元,同时线程又是一种受限的系统资源,即线程不可能无限制的产生,并且线程的创建和销毁都会有相应的开销。如果一个进程需要频繁的创建子线程来执行任务,而每执行一次任务都需要重新创建和销毁线程,这显然不是高效的做法。正确的做法是采用线程池,一个线程池中会缓存一定数量的线程,当我们创建线程后会将线程存入到线程池中,那么当下次在需要使用线程时,就不用重复创建线程,而是直接从线程池中取出,这样就节省了频繁创建线程和销毁线程的开销。

二、Android中常见的线程形态
1、AsyncTask
AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把任务的执行进度和*终执行结果传递给主线程并在主线程中更新UI。从实现上来说,AsyncTask封装了Thread和Hander,并且采用了线程池的机制。

1.1 AyncTask的基本用法
public class AsyncTaskTestActivity extends AppCompatActivity {
private ImageView mImageView;
private Button mButton;
private ProgressDialog mProgressDialog;
private DownloadImageAsyncTask mDownloadImageAsyncTask; // 不能声明为AsyncTask,否则会报类型转换错误

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_async_task_test);
initView();
}

private void initView() {
mImageView = (ImageView) findViewById(R.id.imageView);
mButton = (Button) findViewById(R.id.button);

mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String imagePath = “https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1641460231,985790943&fm=27&gp=0.jpg”;
startAsyncTask(imagePath);
}
});
}

private void startAsyncTask(String imagePath) {
if (mDownloadImageAsyncTask != null && !mDownloadImageAsyncTask.isCancelled()) {
mDownloadImageAsyncTask.cancel(true);
}
mDownloadImageAsyncTask = new DownloadImageAsyncTask();
mDownloadImageAsyncTask.execute(imagePath);
}

@Override
protected void onDestroy() {
super.onDestroy();
// 在界面销毁后取消异步任务,以免发生内存泄露
if (mDownloadImageAsyncTask != null && !mDownloadImageAsyncTask.isCancelled()) {
mDownloadImageAsyncTask.cancel(true);
mDownloadImageAsyncTask = null;
}
}

public void showProgressDialog() {
if (mProgressDialog == null) {
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setTitle(“提示信息”);
mProgressDialog.setMessage(“正在下载中,请稍后……”);
// 设置setCancelable(false); 表示我们不能取消这个弹出框,等下载完成之后再让弹出框消失
mProgressDialog.setCancelable(false);
// 设置ProgressDialog样式为水平的样式
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
}
mProgressDialog.show();
}

public void setLoadProgress(int progress) {
if (mProgressDialog != null) {
mProgressDialog.setProgress(progress);
}
}

public void dimissProgressDialog() {
if (mProgressDialog != null) {
mProgressDialog.dismiss();
}
}

public void downloadImageSuccessCallBack(byte[] bytes) {
if (bytes != null && mImageView != null) {
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
mImageView.setImageBitmap(bitmap);
}
dimissProgressDialog();
}

/**
* 下载任务
*/
public class DownloadImageAsyncTask extends AsyncTask<String, Integer, byte[]> {
// 在UI线程中执行,任务开始前需要进行的UI操作,比如弹出加载框
@Override
protected void onPreExecute() {
super.onPreExecute();
showProgressDialog();
}

// 在异步线程中执行耗时任务,比如请求网络数据
@Override
protected byte[] doInBackground(String… params) {
// 通过Apache的HttpClient来访问请求网络中的一张图片
HttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(params[0]);
byte[] image = new byte[]{};
try
{
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
InputStream inputStream = null;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
if(httpEntity != null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK)
{
// 得到文件的总长度
long file_length = httpEntity.getContentLength();
// 每次读取后累加的长度
long total_length = 0;
int length = 0;
// 每次读取1024个字节
byte[] data = new byte[1024];
inputStream = httpEntity.getContent();
while(-1 != (length = inputStream.read(data)))
{
// 每读一次,就将total_length累加起来
total_length += length;
// 边读边写到ByteArrayOutputStream当中
byteArrayOutputStream.write(data, 0, length);
// 得到当前图片下载的进度
int progress = ((int)(total_length/(float)file_length) * 100);
// 时刻将当前进度更新给onProgressUpdate方法
publishProgress(progress);
if (isCancelled()) {
break;
}
}
}
image = byteArrayOutputStream.toByteArray();
inputStream.close();
byteArrayOutputStream.close();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
httpClient.getConnectionManager().shutdown();
}
return image;
}

// 任务取消时调用
@Override
protected void onCancelled() {
super.onCancelled();
dimissProgressDialog();
}

// 在UI线程中执行,在异步线程中调用publishProgress(Progress)后会立即回调onPregressUpdate方法,可以在该方法中更新UI界面上的任务执行进度
@Override
protected void onProgressUpdate(Integer… values) {
super.onProgressUpdate(values);
setLoadProgress(values[0]);
}

// 任务执行完毕调用该方法,在UI线程中执行,更新数据到UI界面
@Override
protected void onPostExecute(byte[] bytes) {
super.onPostExecute(bytes);
downloadImageSuccessCallBack(bytes);
}
}
}
上面的代码是AsyncTask典型的用法,通过AsyncTask下载了一张图片,下载完成后将图片显示在界面上;AsyncTask是一个抽象的泛型类,它提供了Params、Progress和Result三个泛型参数,分别表示传入参数的类型、任务执行进度的类型和任务返回结果的类型,如果不需要传递参数,那么上面三个泛型参数可以用void代替;AsyncTask提供了四个核心的方法,它们的含义如下:

onPreExecute():在主线程中执行,异步任务开始之前会执行该方法,一般用来做一些准备工作,比如界面上弹出加载框;

doInBackground(Params… params):在线程池中的线程中执行,该方法用来执行异步任务,params表示异步任务的输入参数,在该方法中可以通过调用publicProgress(Progress… values)来更新任务的执行进度,publicProgress(Progress… values)方法会触发调用onProgressUpdate(Progress… values)方法;

onProgressUpdate(Progress… values):在主线程中执行,用来更新任务的执行进度;

onPostExecute(Result result):在主线程中执行,该方法是异步任务执行完毕后的回调方法,result参数表示异步任务的返回结果。

除了上面四个方法外,还有一个比较重要的方法是onCancelled()方法,它同样是在主线程中执行,当异步任务被取消时,onCancelled()方法会被调用,这个时候onPostExecute方法不会被调用。

1.2 AsyncTask源码分析
上面介绍了AsyncTask的基本用法,接下来我们分析一下AsyncTask的源码:

我们通过调用execute方法来让AsyncTask开始工作,代码如下:

@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params… params) {
return executeOnExecutor(sDefaultExecutor, params);
}

@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params… params) {
if (mStatus != Status.PENDING) { // 每个AsyncTask只能执行一次
switch (mStatus) {
case RUNNING:
throw new IllegalStateException(“Cannot execute task:”
+ ” the task is already running.”);
case FINISHED:
throw new IllegalStateException(“Cannot execute task:”
+ ” the task has already been executed ”
+ “(a task can be executed only once)”);
}
}

mStatus = Status.RUNNING;

onPreExecute(); // 调用onPreExecute()方法

mWorker.mParams = params;
exec.execute(mFuture); // 执行异步任务

return this;
}
在上面的代码中,我们可以知道AsyncTask的execute必须在UI线程中执行,并且每个AsyncTask只能执行一次,在执行异步任务之前,首先会先调用onPreExecute()方法,然后将参数保存在“mWorker”的参数中,*终执行异步任务的代码是“exec.execute(mFuture);”,其中“exec”如果没有指定,默认是“mDefaultExecutor”,“mDefaultExecutor”的定义如下:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;

public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() { // 将任务存入到队列中
public void run() {
try {
r.run(); // 执行任务
} finally {
scheduleNext(); // 任务执行完毕后继续执行下一个任务
}
}
});
if (mActive == null) { // 通过这个判断来确保任务是串行执行的
scheduleNext(); // 从队列中取出任务并执行
}
}

protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) { // 从队列中取出任务并执行,如果队列为空,则mActive也会为空
THREAD_POOL_EXECUTOR.execute(mActive); // 真正执行任务的线程池
}
}
}
“exec.execute(mFuture);”*终会调用SerialExecutor的execute方法,将任务存入到队列中,然后会触发线程池执行任务,从上面也可以看出在一个进程中所有AysncTask的异步任务默认是串行执行的,当*次调用execute方法时,mActive默认为空,所以会执行scheduleNext()方法,开始执行异步任务,这之后再次调用execute方法时,只要还有任务在执行,mActive就不为空了,也就不会调用scheduleNext,而只会将任务加入到队列中,当当前任务执行完毕后,会再次调用scheduleNext()方法执行下一个任务,如果此时队列中没有了任务,会将mActive置为空,当下一个任务来临后,也就会执行scheduleNext()方法继续执行任务。

那么有没有什么办法可以让AsyncTask的任务并行执行呢?答案是肯定的,AysncTask提供了executeOnExecutor(Executor exec, Params… params)接口,可以指定执行异步任务的Executor,并且AysncTask提供了一个public static类型的线程池对象,定义如下:

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT – 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;

public static final Executor THREAD_POOL_EXECUTOR;

static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
根据代码,我们知道THREAD_POOL_EXECUTOR是一个线程池对象,其根据CPU的个数配置了线程池的核心线程数和*大线程数,我们可以指定异步任务的执行线程池为THREAD_POOL_EXECUTOR,这样就能做到并行执行了(当然,如果任务很多,超过了线程池的*大线程数,任务还是会排队执行)。

我们再来看一下传入的参数“mFuture”,它是在AsyncTask的构造函数中初始化的:

mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams); // 调用doInBackground方法执行异步任务
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result); // 将异步任务的返回结果发送给主线程
}
return result;
}
};

mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException(“An error occurred while executing doInBackground()”,
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
FutureTask类是Future 的一个实现,并实现了Runnable,所以可通过Excutor(线程池) 来执行,下面看一下它的核心方法run方法:

public void run() {
if (state != NEW ||
!U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable; // 获得callable对象
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call(); // 调用callable对象的call方法执行任务并获得返回结果
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
可以看到FutureTask的run方法*终是调用了其Callable对象的call()方法来执行任务,由此也可以看出,AsyncTask的异步任务的执行是在其WorkerRunnable对象的call()方法中执行的,而call()方法中又调用了AsyncTask的doInBackground方法来执行异步任务,执行完毕后会调用postResult方法将返回结果发送给主线程,下面我们来看一下postResult的源码:

private Result postResult(Result result) {
@SuppressWarnings(“unchecked”)
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result)); // 构造一个Message,将消息发送给主线程
message.sendToTarget();
return result;
}
上面的代码会构造一个Message,消息类型为MESSAGE_POST_RESULT,并且包含了一个AsyncTaskResult对象,这个对象中封装了当前AsyncTask对象和返回结果对象。继续看getHandler的代码:

private static Handler getHandler() {
synchronized (AsyncTask.class) { // 使用线程锁的单例模式构造Handler对象
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}
getHandler使用线程锁的单例模式构造了一个InternalHander对象,InternalHander定义如下:

private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper()); // 使用主线程的Looper来创建Handler,也就是说这个Hander的所有消息都会在主线程中处理
}

@SuppressWarnings({“unchecked”, “RawUseOfParameterizedType”})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj; // 取出AsyncTaskResult对象
switch (msg.what) {
case MESSAGE_POST_RESULT: // 返回结果的消息类型
// There is only one result
result.mTask.finish(result.mData[0]); // 调用AsyncTask的finish方法处理返回结果
break;
case MESSAGE_POST_PROGRESS: // 更新进度的消息类型
result.mTask.onProgressUpdate(result.mData); // 调用AsyncTask的onProgressUpdate更新进度
break;
}
}
}
InternalHandler使用主线程的Looper来创建Handler,这就确保了InternalHandler的所有消息都会在主线程中处理,在handlerMessage中处理了两种消息,一种是返回结果,一种是更新任务进度,而更新任务进度调用的是AsyncTask的onProgressUpdate方法,所有我们在自定义的AsyncTask中通过重写onProgressUpdate方法来告知用户任务进度;我们再来看一下AsyncTask的finish方法:

private void finish(Result result) {
if (isCancelled()) {
onCancelled(result); // 如果任务已经取消,则调用onCancelled方法
} else {
onPostExecute(result); // 任务没取消,则调用onPostExecute方法处理任务返回结果
}
mStatus = Status.FINISHED;
}
可以看到,如果任务取消了,AsyncTask会调用onCancelled方法处理任务;如果没有取消,则会调用onPostExecute方法来处理任务的返回结果。

之前我们说过,我们会在doInBackground方法中调用publishProgress方法来触发更新任务进度,我们可以猜测一下,publicProgress方法应该是利用InternalHandler向主线程发送一个MESSAGE_POST_PROGRESS的消息,然后在主线程中就会调用AsyncTask的onProgressUpdate方法来更新任务进度了,查看publicProgress的源码:

@WorkerThread
protected final void publishProgress(Progress… values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
上面的代码也验证了我们的猜想。

到此,AsyncTask的代码也就分析完了,总结一下,AsyncTask是一个用来执行异步任务的类,通过它可以轻松的在子线程中执行异步任务,执行完之后将处理返回结果的代码在主线程中执行;AsyncTask内部的原理主要使用到了Handler和线程池,Handler用来切换任务执行的线程,而线程池用来执行异步任务;其中还有线程池方面的知识,我们后面再分析。

2、 HandlerThread
HandlerThread继承了Thread,它是一种可以使用Handler的Thread,其实现原理很简单,主要是其内部维护了自己的Looper对象,这个Looper对象在run方法中会开启循环,下面来看一下其基本用法:

HandlerThread handlerThread = new HandlerThread(“HandlerThreadTest”); // 构造一个HandlerThread对象
handlerThread.start(); // 启动线程
Handler handler = new Handler(handlerThread.getLooper()) { // 用HandlerThread的Looper构造一个Handler对象,也就是说这个Handler的所有消息都会通过HandlerThread的Looper循环处理
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
Log.i(“aaaaaaa”, “HandlerThread handlerMessage”);
}
}
};
Message message = Message.obtain();
message.what = 1;
handler.sendMessage(message); // 发送一个消息
首先我们构造了一个HandlerThread的对象,并且调用其start方法启动线程,然后利用HandlerThread的Looper构造了一个Handler对象,之后就可以用这个Handler对象向HandlerThread发送消息了,消息的都会在HandlerThread线程中处理。HandlerThread的源码如下:

public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper; // HandlerThread内部维护的Looper对象

public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}

/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}

/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() { // 正式开始Looper循环之前的回调方法,可以在该方法中做一些准备工作
}

@Override
public void run() {
mTid = Process.myTid();
Looper.prepare(); // 为当前线程创建Looper对象
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared(); // 在正式开始循环之前调用onLooperPrepared方法
Looper.loop(); // 开启Looper循环
mTid = -1;
}

/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason is isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() { // 获得当前线程的Looper对象
if (!isAlive()) {
return null;
}

// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}

/**
* Quits the handler thread’s looper.
* <p>
* Causes the handler thread’s looper to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class=”note”>
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*
* @see #quitSafely
*/
public boolean quit() { // 调用Looper的quit来退出Looper循环,一旦Looper循环退出,run方法也就结束了,当前线程也就结束了
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}

/**
* Quits the handler thread’s looper safely.
* <p>
* Causes the handler thread’s looper to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* Pending delayed messages with due times in the future will not be delivered.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p>
* If the thread has not been started or has finished (that is if
* {@link #getLooper} returns null), then false is returned.
* Otherwise the looper is asked to quit and true is returned.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*/
public boolean quitSafely() {// 调用Looper的quitSafely来退出Looper循环,一旦Looper循环退出,run方法也就结束了,当前线程也就结束了
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}

/**
* Returns the identifier of this thread. See Process.myTid().
*/
public int getThreadId() {
return mTid;
}
}
HandlerThread的源码很简单,相比于普通的线程,其内部维护了一个Looper对象,并且会在run方法中开启循环,其他线程可以通过它的Looper对象来构造Handler,通过Handler向HandlerThread发送消息,这样就将任务切换到了HandlerThread线程中处理;同时,HandlerThread也提供了一下退出的方法,其退出方法其实就是调用Looper对象的退出方法来结束Looper循环,一旦Looper循环结束,run方法也就结束了,线程退出。

3、 IntentService
IntentService是一种特殊的Service,它继承了Service并且它是一个抽象类,因此必须创建它的子类才能使用IntentService。IntentService可用于执行后台耗时的任务,当任务执行完后它会自动停止,同时由于IntentService是服务的原因,这就导致它的优先级比单纯的线程要高很多,所以IntentService比较适合执行一些高优先级的后台任务,因为它高优先级不容易被系统杀死。IntentService相比于传统的Service,其任务的执行是在子线程中执行的,而传统的Service是运行在主线程中的,当然,我们也可以在Service中开启一个子线程来执行任务,但是如果使用IntentService,我们就省去了维护子线程的麻烦,而且IntentService会在任务结束后自动停止服务;从实现上来说,IntentService封装了HandlerThread和Handler。我们先来看一下它的基本用法:

和普通的Service一样,首先要在AndroidManifest文件中配置服务:

<service android:name=”.asynctasktest.MyIntentService”/>
然后定义MyIntentService类:

public class MyIntentService extends IntentService {
public MyIntentService() {
super(“MyIntentService”);
}

@Override
protected void onHandleIntent(@Nullable Intent intent) { // IntentService的任务处理方法
Log.i(“liunianprint:”, “run thread ” + Thread.currentThread().getId()); // 打印运行的这段代码的线程
}
}
*后启动IntentService即可:

Intent intent = new Intent(IntentServiceActivity.this, MyIntentService.class);
startService(intent);
启动方式和启动普通Service一样。

运行程序,我们发现onHandleIntent方法的处理确实是在子线程中处理的。下面我们分析一下IntentService的源码实现:

public abstract class IntentService extends Service { // 继承与Service,这也确定了IntentService首先是一个Service
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;

private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}

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

public IntentService(String name) {
super();
mName = name;
}

public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}

@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.

super.onCreate();
HandlerThread thread = new HandlerThread(“IntentService[” + mName + “]”);
thread.start();

mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}

@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}

@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

@Override
public void onDestroy() {
mServiceLooper.quit();
}

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

@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
}
代码不多,首先IntentService继承了Service,说明其是一个服务,在启动服务后,首先会调用服务的onCreate方法,我们来分析一下IntentService的onCreate方法:

@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.

super.onCreate();
HandlerThread thread = new HandlerThread(“IntentService[” + mName + “]”); // 创建一个HandlerThread线程
thread.start(); // 启动HandlerThread线程,等待消息处理

mServiceLooper = thread.getLooper(); // 获得HandlerThread的Looper对象
mServiceHandler = new ServiceHandler(mServiceLooper); // 用HandlerThread的Looper对象来创建ServiceHandler对象
}
在onCreate方法中,首先会创建一个HandlerThread线程并启动,然后在利用HandlerThread的Looper来构造ServiceHandler对象,从之前HandlerThread的源码中,我们知道HandlerThread线程一旦启动就会启动Looper循环,等待消息的到来并处理,我们再来看一下ServiceHandler的源码:

private final class ServiceHandler extends Handler { // 继承Handler
public ServiceHandler(Looper looper) {
super(looper);
}

@Override
public void handleMessage(Message msg) { // 处理消息方法,该方法会在HandlerThread线程中调用
onHandleIntent((Intent)msg.obj); // 调用onHandleIntent方法处理消息
stopSelf(msg.arg1); // 停止服务,这里会根据传过来的startId来判断是否停止服务
}
}
ServiceHandler继承与Handler,由于它是使用上面HandlerThread的Looper对象构造的,所有其handleMessage方法也会在HandlerThread线程中调用,在handlerMessage方法中,首先会调用IntentService的onHandleIntent方法处理消息,消息处理完毕后会调用stopSelf来停止IntentService。我们知道,在onCreate方法调用完成后,Service会调用onStartCommand方法来运行服务,IntentService的onStartCommand定义如下:

@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage(); // 构造一个消息
msg.arg1 = startId; // 传入startId值
msg.obj = intent; // 传入intent对象
mServiceHandler.sendMessage(msg); // 发送消息
}

@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId); // 调用onStart方法
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
从上面的代码可以看出,onStart方法其实就是构造了一个消息,消息里面包含了Intent对象和当前调用onStart的startId,然后利用ServiceHandler对象向之前创建的HandlerThread线程发送消息,这样,在HandlerThread线程中就可以出来消息了,也就是会调用到ServiceHandler的handleMessage方法。这里注意一点,在handleMessage中调用stopSelf方法停止服务时,传入了startId,每次调用startService时,系统都会调用onStart方法,但是startId会递增,调用stopSelf(int startId)方法来停止服务时,会以*近的请求的startId为标准,也就是说,系统在回调stopSelf(startId)时,会用传入的startId和*近的请求的startId相比较,如果相同,则退出Service,否则,Service不会被停止,这样确保了IntentService的*近任务是肯定能够执行的。

好了,IntentService的源码也分析完了,总结一下,IntentService本质上是一个Service,其目的是为了减去普通Service对子线程的维护;IntentService适合执行在后台的耗时任务,因为它本质上是Service,相比于普通线程,所有不容易被系统杀死。IntentService的内部实现原理是Service、HandlerThread和Handler,其中,继承Service让其有了Service的功能,HandlerThread用来执行异步的耗时任务,Handler用来发送消息给HandlerThread,触发HandlerThread执行任务。

三、线程池
这部分我们来了解一下线程池的知识,AsyncTask*终就是通过线程池来执行异步任务的,提到线程池,我们必须先说一下使用线程池的好处:

1、重用线程池中的线程,避免了频繁创建和销毁线程所带来的开销;

2、能够有效的控制线程的*大并发数,避免了大量的线程之间因为相互抢占系统资源而导致的阻塞;

3、能够简单的对线程进行管理,并提供定时执行以及指定间隔循环执行等功能。

总的来说,线程池是系统为我们提供的管理线程的模块,通过它我们可以方便的控制项目中的线程。Android中的线程池的概念来源于Java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor。ThreadPoolExecutor提供了一系列参数来配置线程池。通过配置参数我们可以获得不同功能的线程池。

3.1 ThreadPoolExecutor
ThreadPoolExecutor是线程池的真正实现,它的构造方法提供了一系列参数来配置线程池,下面我们来看一下ThreadPoolExecutor的构造方法中各个参数的含义,这些参数将直接影响到线程池的功能特性:

public ThreadPoolExecutor(int corePoolSize, // 线程池核心线程个数
int maximumPoolSize, // 线程池*大线程个数
long keepAliveTime, // 非核心线程闲置时的超时时长
TimeUnit unit, // 非核心线程闲置的超时时长的时间单位
BlockingQueue<Runnable> workQueue, // 线程池中的任务队列
ThreadFactory threadFactory, // 线程工厂,为线程池提供创建新线程的功能
RejectedExecutionHandler handler) { // 当线程池无法执行新任务时的处理对象
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize:线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使它们处于闲置状态;如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的线程在等待新任务到来时也会有超时策略,这个时间有kiipAliveTime所指定,当等待时间超过keepAliveTime所指定的时长后,核心线程就会被终止。

maximumPoolSize:线程池所能容纳的*大线程个数,当任务队列放不下后,会立刻启动非核心线程执行任务,当线程数量已经达到了线程池规定的*大个数,那么就拒*执行此任务,线程池会调用RejectedExecutionHandler的rejectedExecution方法来处理异常情况。

keepAliveTime:非核心线程闲置时的超时时长,超过这个时长后,非核心线程将会被回收;当allowCoreThreadTimeOut设置为true后,这个参数对核心线程也同样有效。

unit:用于指定keepAliveTime的时间单位。

workQueue:线程池中的任务队列,用来存储线程池将要执行的任务。

threadFactory:线程工厂,为线程池提供创建线程的功能;ThreadFactory是一个接口,它只有一个方法:Thread newThread(Runnable r)。

handler:任务异常处理,当线程池无法执行新任务时,就会调用这个参数来处理异常情况,RejectedExecutionHandler是一个接口,它只有一个方法:void rejectedExecution(Runnable r, ThreadPoolExecutor executor);导致无法处理新任务的额原因可能是由于任务队列已满并且线程数量也达到了线程池规定的*大个数,新的任务过来后无法存入到任务队列中,这个时候线程池会调用handler的rejectedExecution方法来通知调用者。

 

ThreadPoolExecutor执行任务时大致遵循如下规则:

(1)如果线程池中的线程数量未达到核心线程的数量,那么会直接创建一个核心线程来执行任务;

(2)如果线程池中的线程数量已经超过核心线程的数量,那么会将任务插入到任务队列中排队等待执行;

(3)如果在步骤2中无法将任务插入到任务队列中,这往往是因为任务队列已满,这个时候如果线程数量没有达到线程池规定的*大个数,则会立即启动一个非核心线程来处理任务;

(4)如果在步骤3中线程数量已经达到了线程池规定的*大个数,那么就拒*执行此任务,线程池会调用RejectedExecutionHandler的rejectedExecution方法来处理异常情况。

 

下面我们看一下AsyncTask中使用的线程池的配置

AsyncTask中异步任务的执行代码如下:

THREAD_POOL_EXECUTOR.execute(mActive);
THREAD_POOL_EXECUTOR的定义:

public static final Executor THREAD_POOL_EXECUTOR;

static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true); // 设置线程池的核心线程也有超时机制
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
可以看到,AsyncTask是在静态代码块初始化线程池的,其中参数的定义如下:

private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT – 1, 4)); // 核心线程个数和CPU的核数相关
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; // *大线程数为CPU核数的2倍+1
private static final int KEEP_ALIVE_SECONDS = 30; // 线程的超时时间为30秒
private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128); // 线程池的任务队列,容量为128
private static final ThreadFactory sThreadFactory = new ThreadFactory() { // 创建线程的工厂
private final AtomicInteger mCount = new AtomicInteger(1);

public Thread newThread(Runnable r) {
return new Thread(r, “AsyncTask #” + mCount.getAndIncrement());
}
};
3.2 线程池的分类
前面我们已经提到,根据对ThreadPoolExecutor配置不同的参数,其生成的线程池的功能特性也不一样。下面介绍Android中四类*常见的线程池,它们都直接或者间接的通过配置ThreadPoolExecutor的参数来实现自己的功能特性,这四类线程池分别是FixedThreadPool、CachedThreadPool、ScheduledThreadPool以及SingleThreadExecutor。

FixedThreadPool

FixedThreadPool是一种线程数量固定的线程池,当线程处于空闲状态时,它们并不会被回收,除非线程池关闭了。它可以通过Executors的newFixedThreadPool来创建:

public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
通过观察上面的参数,我们可以知道,FixedThreadPool的*大核心线程数等于*大线程数,这也就意味着其没有非核心线程,任务队列没有设置*大容量,表示任务队列没有大小限制。由于FixedThreadPool只有核心线程并且这些核心线程不会被回收,这就意味着它能够更加快的响应外界的请求,当核心线程都处于活动状态时,任务将会保存在任务队列中等待处理。

CachedThreadPool

CachedThreadPool是一种线程数量不定的线程池,它只有非核心线程,并且其*大线程数为Integer.MAX_VALUE,也就是不限制线程的*大个数。它可以通过Executors的newCachedThreadPool来创建:

public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
观察上面的参数,我们发现CachedThreadPool的*大核心线程数为0,并且不限制*大线程数,线程空闲超时时间为60秒,任务队列为SynchronousQueue,SynchronousQueue是一个非常特殊的队列,它其实是无法存储任务的,当然,CachedThreadPool的*大线程数是不限的,所以每个任务都会立即执行。从CachedThreadPool的特性来看,这类线程池比较适合执行大量的耗时较少的任务。

ScheduledThreadPool

ScheduledThreadPool的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会立刻被回收。它可以通过Executors的newScheduledThreadPool方法来创建:

public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue(), threadFactory);
}
ScheduledThreadPool主要用于执行定时任务和具有固定周期的重复任务。

SingleThreadExecutor

SingleThreadExecutor内部只有一个核心线程,它确保了所有任务都在同一个线程中按顺序执行。它可以通过Executors的newSingleThreadExecutor来创建:

public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
当有任务到来时,如果唯一的一个核心线程处于工作状态,则任务将会存入到队列中等待执行;SingleThreadExecutor的意义在于统一所有的外界任务到一个线程中,使这些任务之间不需要处理线程同步的问题。

除了上面的四种常见的线程池,我们还可以来配置符合自己需求的线程池。

android之对不同的页面管理

一、界面状态有哪些

在Android中,不管是activity或者fragment,在加载视图的时候都有可能会出现多种不同的状态页面View。比较多的有下面几种:

1、内容界面,也就是正常有数据页面

2、加载数据中,加载loading

3、加载数据错误,请求数据异常

4、加载后没有数据,请求数据为空

5、没有网络,网络异常

场景:

a、加载网络数据时,需要用户等待的场景,显示一个加载的Loading动画可以让用户知道App正在加载数据,而不是程序卡死,给用这样可以给户较好的使用体验。

b、当加载的数据为空时显示一个数据为空的视图、在数据加载失败时显示加载失败对应的UI并支持点击重试会比白屏的用户体验更好一些。

c、加载中、加载失败、空数据等不同状态页面风格,一般来说在App内的所有页面中需要保持一致,也就是需要做到全局统一。

二、采用include方式管理

直接把这些界面include到main界面中,然后动态去切换界面,具体一点的做法如下所示。

在布局中,会存放多个状态的布局。然后在页面中根据逻辑将对应的布局给显示或者隐藏,但存在诸多问题。

存在的问题分析

1、后来发现这样处理不容易复用到其他项目中,代码复用性很低

2、在activity中处理这些状态的显示和隐藏比较乱

3、调用setContentView方法时,是将所有的布局给加载绘制出来。其实没有必要

4、如果将逻辑写在BaseActivity中,利用子类继承父类特性,在父类中写切换状态,但有些界面如果没有继承父类,又该如何处理

三、在Base类中处理逻辑

1、定义一个自定义的控件,比如把它命名成LoadingView,然后在这个里面include一个布局,该布局包含一些不同状态的视图。代码思路如下所示:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout
xmlns:android=”http://schemas.android.com/apk/res/android”
android:id=”@+id/activity_main”
android:orientation=”vertical”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<!–正常时布局–>
<include layout=”@layout/activity_content”/>
<!–加载loading布局–>
<include layout=”@layout/activity_loading”/>
<!–异常时布局–>
<include layout=”@layout/activity_error”/>
<!–空数据时布局–>
<include layout=”@layout/activity_emptydata”/>

</LinearLayout>
布局初始化

public class LoadingView extends LinearLayout implements View.OnClickListener {

public static final int LOADING = 0;
public static final int STOP_LOADING = 1;
public static final int NO_DATA = 2;
public static final int NO_NETWORK = 3;
public static final int GONE = 4;
public static final int LOADING_DIALOG = 5;

private TextView mNoDataTextView;
private ProgressBar mLoadingProgressBar;
private RelativeLayout mRlError;
private LinearLayout mLlLoading;
private View mView;

private OnRefreshListener mListener;

public void setRefrechListener(OnRefreshListener mListener) {
this.mListener = mListener;
}

public interface OnRefreshListener {
void refresh();
}

public LoadingView(Context context) {
super(context);
init(context);
}

public LoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

private void init(Context context) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mView = inflater.inflate(R.layout.common_loading_get, this);
mLoadingProgressBar = (ProgressBar) mView.findViewById(R.id.mLoadingProgressBar);
mNoDataTextView  = (TextView) mView.findViewById(R.id.mNoDataTextView);
mLlLoading = (LinearLayout) mView.findViewById(R.id.ll_loading);
mRlError = (RelativeLayout) mView.findViewById(R.id.rl_error);
mRlError.setOnClickListener(this);
setStatue(GONE);
}

public void setStatue(int status) {
setVisibility(View.VISIBLE);
try {
if (status == LOADING) {//更新
mRlError.setVisibility(View.GONE);
mLlLoading.setVisibility(View.VISIBLE);
} else if (status == STOP_LOADING) {
setVisibility(View.GONE);
} else if (status == NO_DATA) {//无数据情况
mRlError.setVisibility(View.VISIBLE);
mLlLoading.setVisibility(View.GONE);
mNoDataTextView.setText(“暂无数据”);
} else if (status == NO_NETWORK) {//无网络情况
mRlError.setVisibility(View.VISIBLE);
mLlLoading.setVisibility(View.GONE);
mNoDataTextView.setText(“网络加载失败,点击重新加载”);
} else {
setVisibility(View.GONE);
}
} catch (OutOfMemoryError e) {
}
}

@Override
public void onClick(View v) {
mListener.refresh();
setStatue(LOADING);
}
}
然后在BaseActivity/BaseFragment中封装LoadingView的初始化逻辑,并封装加载状态切换时的UI显示逻辑,暴露给子类以下方法:void showLoading(); //调用此方法显示加载中的动画

void showLoadFailed(); //调用此方法显示加载失败界面
void showEmpty(); //调用此方法显示空页面
void onClickRetry(); //子类中实现,点击重试的回调方法
2、在BaseActivity/BaseFragment的子类中可通过上一步的封装比较方便地使用加载状态显示功能。这种使用方式耦合度太高,每个页面的布局文件中都需要添加LoadingView,使用起来不方便而且维护成本较高,比如说有时候异常状态的布局各个页面不同,那么难以自定义处理,修改起来成本较高。

同时如果是要用这种状态管理工具,则需要在需要的页面布局中添加该LoadingView视图。这样也能够完成需求,但是感觉有点麻烦。

3、具体如何使用它进行状态管理呢?可以看到在对应的布局中需要写上LoadingView

<?xml version=”1.0″ encoding=”utf-8″?>
<RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<com.cheoo.app.view.recyclerview.TypeRecyclerView
android:id=”@+id/mRecyclerView”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:overScrollMode=”never”
android:scrollbars=”none”>

</com.cheoo.app.view.recyclerview.TypeRecyclerView>

<com.cheoo.app.view.LoadingView
android:id=”@+id/mLoadingView”
android:layout_width=”match_parent”
android:layout_height=”match_parent” />

</RelativeLayout>
那么,如果某个子类不想继承BaseActivity类,如何使用该状态管理器呢?代码中可以这种使用。

mLoadingView  = (LoadingView)findViewById(R.id.mLoadingView);
mLoadingView.setStatue(LoadingView.LOADING);
mLoadingView.setStatue(LoadingView.STOP_LOADING);
mLoadingView.setStatue(LoadingView.NO_NETWORK);
mLoadingView.setStatue(LoadingView.NO_DATA);
四、如何降低偶性和入侵性

1、让View状态的切换和Activity彻底分离开,必须把这些状态View都封装到一个管理类中,然后暴露出几个方法来实现View之间的切换。

2、在不同的项目中可以需要的View也不一样,所以考虑把管理类设计成builder模式来自由的添加需要的状态View。

3、那么如何降低耦合性,让代码入侵性低。方便维护和修改,且移植性强呢?大概具备这样的条件……

1)可以运用在activity或者fragment中

2)不需要在布局中添加LoadingView,而是统一管理不同状态视图,同时暴露对外设置自定义状态视图方法,方便UI特定页面定制

3)支持设置自定义不同状态视图,即使在BaseActivity统一处理状态视图管理,也支持单个页面定制

4)在加载视图的时候像异常和空页面能否用ViewStub代替,这样减少绘制,只有等到出现异常和空页面时,才将视图给inflate出来

5)当页面出现网络异常页面,空页面等,页面会有交互事件,这时候可以设置点击设置网络或者点击重新加载等等

五、封装低入侵性状态库

1、自定义帧布局

首先需要自定义一个状态StateFrameLayout布局,它是继承FrameLayout。在这个类中,目前是设置五种不同状态的视图布局,主要的功能操作是显示或者隐藏布局。为了后期代码维护性,根据面向对象的思想,类尽量保证单一职责,所以关于状态切换,以及设置自定义状态布局,把这个功能分离处理,放到一个StateLayoutManager中处理。

这个类的功能非常明确,就是隐藏或者展示视图作用。

/**
*
*     time  : 2019/71/9
*     desc  : 自定义帧布局
*     revise:
*
*/
public class StateFrameLayout extends FrameLayout {

/**
*  loading 加载id
*/
public static final int LAYOUT_LOADING_ID = 1;

/**
*  内容id
*/
public static final int LAYOUT_CONTENT_ID = 2;

/**
*  异常id
*/
public static final int LAYOUT_ERROR_ID = 3;

/**
*  网络异常id
*/
public static final int LAYOUT_NETWORK_ERROR_ID = 4;

/**
*  空数据id
*/
public static final int LAYOUT_EMPTY_DATA_ID = 5;

/**
*  存放布局集合
*/
private SparseArray<View> layoutSparseArray = new SparseArray<>();

//private HashMap<Integer,View> map = new HashMap<>();

/**
*  布局管理器
*/
private StateLayoutManager mStatusLayoutManager;

public StateFrameLayout(Context context) {
super(context);
}

public StateFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}

public StateFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

public void setStatusLayoutManager(StateLayoutManager statusLayoutManager) {
mStatusLayoutManager = statusLayoutManager;
//添加所有的布局到帧布局
addAllLayoutToRootLayout();
}

private void addAllLayoutToRootLayout() {
if (mStatusLayoutManager.contentLayoutResId != 0) {
addLayoutResId(mStatusLayoutManager.contentLayoutResId, StateFrameLayout.LAYOUT_CONTENT_ID);
}
if (mStatusLayoutManager.loadingLayoutResId != 0) {
addLayoutResId(mStatusLayoutManager.loadingLayoutResId, StateFrameLayout.LAYOUT_LOADING_ID);
}

if (mStatusLayoutManager.emptyDataVs != null) {
addView(mStatusLayoutManager.emptyDataVs);
}
if (mStatusLayoutManager.errorVs != null) {
addView(mStatusLayoutManager.errorVs);
}
if (mStatusLayoutManager.netWorkErrorVs != null) {
addView(mStatusLayoutManager.netWorkErrorVs);
}
}

private void addLayoutResId(@LayoutRes int layoutResId, int id) {
View resView = LayoutInflater.from(mStatusLayoutManager.context).inflate(layoutResId, null);
layoutSparseArray.put(id, resView);
addView(resView);
}

/**
*  显示loading
*/
public void showLoading() {
if (layoutSparseArray.get(LAYOUT_LOADING_ID) != null) {
showHideViewById(LAYOUT_LOADING_ID);
}
}

/**
*  显示内容
*/
public void showContent() {
if (layoutSparseArray.get(LAYOUT_CONTENT_ID) != null) {
showHideViewById(LAYOUT_CONTENT_ID);
}
}

/**
*  显示空数据
*/
public void showEmptyData(int iconImage, String textTip) {
if (inflateLayout(LAYOUT_EMPTY_DATA_ID)) {
showHideViewById(LAYOUT_EMPTY_DATA_ID);
emptyDataViewAddData(iconImage, textTip);
}
}

/**
* 根据ID显示隐藏布局
* @param id                        id值
*/
private void showHideViewById(int id) {
for (int i = 0; i < layoutSparseArray.size(); i++) {
int key = layoutSparseArray.keyAt(i);
View valueView = layoutSparseArray.valueAt(i);
//显示该view
if(key == id) {
valueView.setVisibility(View.VISIBLE);
if(mStatusLayoutManager.onShowHideViewListener != null) {
mStatusLayoutManager.onShowHideViewListener.onShowView(valueView, key);
}
} else {
if(valueView.getVisibility() != View.GONE) {
valueView.setVisibility(View.GONE);
if(mStatusLayoutManager.onShowHideViewListener != null) {
mStatusLayoutManager.onShowHideViewListener.onHideView(valueView, key);
}
}
}
}
}

/**
* 这个是处理ViewStub的逻辑,主要有网络异常布局,加载异常布局,空数据布局
* @param id                        布局id
* @return                          布尔值
*/
private boolean inflateLayout(int id) {
boolean isShow = true;
//如果为null,则直接返回false
if (layoutSparseArray.get(id) == null) {
return false;
}
switch (id) {
case LAYOUT_NETWORK_ERROR_ID:
if (mStatusLayoutManager.netWorkErrorVs != null) {
View view = mStatusLayoutManager.netWorkErrorVs.inflate();
retryLoad(view, mStatusLayoutManager.netWorkErrorRetryViewId);
layoutSparseArray.put(id, view);
isShow = true;
} else {
isShow = false;
}
break;
case LAYOUT_ERROR_ID:
if (mStatusLayoutManager.errorVs != null) {
View view = mStatusLayoutManager.errorVs.inflate();
if (mStatusLayoutManager.errorLayout != null) {
mStatusLayoutManager.errorLayout.setView(view);
}
retryLoad(view, mStatusLayoutManager.errorRetryViewId);
layoutSparseArray.put(id, view);
isShow = true;
} else {
isShow = false;
}
break;
case LAYOUT_EMPTY_DATA_ID:
if (mStatusLayoutManager.emptyDataVs != null) {
View view = mStatusLayoutManager.emptyDataVs.inflate();
if (mStatusLayoutManager.emptyDataLayout != null) {
mStatusLayoutManager.emptyDataLayout.setView(view);
}
retryLoad(view, mStatusLayoutManager.emptyDataRetryViewId);
layoutSparseArray.put(id, view);
isShow = true;
} else {
isShow = false;
}
break;
default:
break;
}
return isShow;
}
}
2、自定义状态管理器

上面状态的自定义布局创建出来了,且隐藏和展示都做了。

1、loadingLayoutResId和contentLayoutResId代表等待加载和显示内容的xml文件

2、几种异常状态要用ViewStub,因为在界面状态切换中loading和内容View都是一直需要加载显示的,但是其他的3个只有在没数据或者网络异常的情况下才会加载显示,所以用ViewStub来加载他们可以提高性能。

3、采用builder模式,十分简单,代码如下所示。创建StateFrameLayout对象,然后再设置setStatusLayoutManager,这一步操作是传递一个Manager对象到StateFrameLayout,建立连接。

public final class StateLayoutManager {

final Context context;

final int netWorkErrorRetryViewId;
final int emptyDataRetryViewId;
final int errorRetryViewId;
final int loadingLayoutResId;
final int contentLayoutResId;
final int retryViewId;
final int emptyDataIconImageId;
final int emptyDataTextTipId;
final int errorIconImageId;
final int errorTextTipId;

final ViewStub emptyDataVs;
final ViewStub netWorkErrorVs;
final ViewStub errorVs;
final AbsViewStubLayout errorLayout;
final AbsViewStubLayout emptyDataLayout;

private final StateFrameLayout rootFrameLayout;
final OnShowHideViewListener onShowHideViewListener;
final OnRetryListener onRetryListener;

public static Builder newBuilder(Context context) {
return new Builder(context);
}

private StateLayoutManager(Builder builder) {
this.context = builder.context;
this.loadingLayoutResId = builder.loadingLayoutResId;
this.netWorkErrorVs = builder.netWorkErrorVs;
this.netWorkErrorRetryViewId = builder.netWorkErrorRetryViewId;
this.emptyDataVs = builder.emptyDataVs;
this.emptyDataRetryViewId = builder.emptyDataRetryViewId;
this.errorVs = builder.errorVs;
this.errorRetryViewId = builder.errorRetryViewId;
this.contentLayoutResId = builder.contentLayoutResId;
this.onShowHideViewListener = builder.onShowHideViewListener;
this.retryViewId = builder.retryViewId;
this.onRetryListener = builder.onRetryListener;
this.emptyDataIconImageId = builder.emptyDataIconImageId;
this.emptyDataTextTipId = builder.emptyDataTextTipId;
this.errorIconImageId = builder.errorIconImageId;
this.errorTextTipId = builder.errorTextTipId;
this.errorLayout = builder.errorLayout;
this.emptyDataLayout = builder.emptyDataLayout;

//创建帧布局
rootFrameLayout = new StateFrameLayout(this.context);
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
rootFrameLayout.setLayoutParams(layoutParams);

//设置状态管理器
rootFrameLayout.setStatusLayoutManager(this);
}

/**
* 显示loading
*/
public void showLoading() {
rootFrameLayout.showLoading();
}

/**
* 显示内容
*/
public void showContent() {
rootFrameLayout.showContent();
}

/**
* 显示空数据
*/
public void showEmptyData(int iconImage, String textTip) {
rootFrameLayout.showEmptyData(iconImage, textTip);
}

/**
* 显示空数据
*/
public void showEmptyData() {
showEmptyData(0, “”);
}

/**
* 显示空数据
*/
public void showLayoutEmptyData(Object… objects) {
rootFrameLayout.showLayoutEmptyData(objects);
}

/**
* 显示网络异常
*/
public void showNetWorkError() {
rootFrameLayout.showNetWorkError();
}

/**
* 显示异常
*/
public void showError(int iconImage, String textTip) {
rootFrameLayout.showError(iconImage, textTip);
}

/**
* 显示异常
*/
public void showError() {
showError(0, “”);
}

public void showLayoutError(Object… objects) {
rootFrameLayout.showLayoutError(objects);
}

/**
* 得到root 布局
*/
public View getRootLayout() {
return rootFrameLayout;
}
Builder类

public static final class Builder {

private Context context;
private int loadingLayoutResId;
private int contentLayoutResId;
private ViewStub netWorkErrorVs;
private int netWorkErrorRetryViewId;
private ViewStub emptyDataVs;
private int emptyDataRetryViewId;
private ViewStub errorVs;
private int errorRetryViewId;
private int retryViewId;
private int emptyDataIconImageId;
private int emptyDataTextTipId;
private int errorIconImageId;
private int errorTextTipId;
private AbsViewStubLayout errorLayout;
private AbsViewStubLayout emptyDataLayout;
private OnShowHideViewListener onShowHideViewListener;
private OnRetryListener onRetryListener;

Builder(Context context) {
this.context = context;
}

/**
* 自定义加载布局
*/
public Builder loadingView(@LayoutRes int loadingLayoutResId) {
this.loadingLayoutResId = loadingLayoutResId;
return this;
}

/**
* 自定义网络错误布局
*/
public Builder netWorkErrorView(@LayoutRes int newWorkErrorId) {
netWorkErrorVs = new ViewStub(context);
netWorkErrorVs.setLayoutResource(newWorkErrorId);
return this;
}

/**
* 自定义加载空数据布局
*/
public Builder emptyDataView(@LayoutRes int noDataViewId) {
emptyDataVs = new ViewStub(context);
emptyDataVs.setLayoutResource(noDataViewId);
return this;
}

/**
* 自定义加载错误布局
*/
public Builder errorView(@LayoutRes int errorViewId) {
errorVs = new ViewStub(context);
errorVs.setLayoutResource(errorViewId);
return this;
}

/**
* 自定义加载内容正常布局
*/
public Builder contentView(@LayoutRes int contentLayoutResId) {
this.contentLayoutResId = contentLayoutResId;
return this;
}

public Builder errorLayout(AbsViewStubLayout errorLayout) {
this.errorLayout = errorLayout;
this.errorVs = errorLayout.getLayoutVs();
return this;
}

public Builder emptyDataLayout(AbsViewStubLayout emptyDataLayout) {
this.emptyDataLayout = emptyDataLayout;
this.emptyDataVs = emptyDataLayout.getLayoutVs();
return this;
}

public Builder netWorkErrorRetryViewId(@LayoutRes int netWorkErrorRetryViewId) {
this.netWorkErrorRetryViewId = netWorkErrorRetryViewId;
return this;
}

public Builder emptyDataRetryViewId(@LayoutRes int emptyDataRetryViewId) {
this.emptyDataRetryViewId = emptyDataRetryViewId;
return this;
}

public Builder errorRetryViewId(@LayoutRes int errorRetryViewId) {
this.errorRetryViewId = errorRetryViewId;
return this;
}

public Builder retryViewId(@LayoutRes int retryViewId) {
this.retryViewId = retryViewId;
return this;
}

public Builder emptyDataIconImageId(@LayoutRes int emptyDataIconImageId) {
this.emptyDataIconImageId = emptyDataIconImageId;
return this;
}

public Builder emptyDataTextTipId(@LayoutRes int emptyDataTextTipId) {
this.emptyDataTextTipId = emptyDataTextTipId;
return this;
}

public Builder errorIconImageId(@LayoutRes int errorIconImageId) {
this.errorIconImageId = errorIconImageId;
return this;
}

public Builder errorTextTipId(@LayoutRes int errorTextTipId) {
this.errorTextTipId = errorTextTipId;
return this;
}

/**
* 为状态View显示隐藏监听事件
* @param listener                  listener
* @return
*/
public Builder onShowHideViewListener(OnShowHideViewListener listener) {
this.onShowHideViewListener = listener;
return this;
}

/**
* 为重试加载按钮的监听事件
* @param onRetryListener           listener
* @return
*/
public Builder onRetryListener(OnRetryListener onRetryListener) {
this.onRetryListener = onRetryListener;
return this;
}

/**
* 创建对象
* @return
*/
public StateLayoutManager build() {
return new StateLayoutManager(this);
}
}

}
3、如何管理多种状态

大约5种状态,如何管理这些状态?添加到集合中,Android中选用SparseArray比HashMap更省内存,在某些条件下性能更好,主要是因为它避免了对key的自动装箱(int转为Integer类型),它内部则是通过两个数组来进行数据存储的,一个存储key,另外一个存储value,为了优化性能,它内部对数据还采取了压缩的方式来表示稀疏数组的数据,从而节约内存空间

/**存放布局集合 */
private SparseArray<View> layoutSparseArray = new SparseArray();

/**将布局添加到集合 */
private void addLayoutResId(@LayoutRes int layoutResId, int id) {
View resView = LayoutInflater.from(mStatusLayoutManager.context).inflate(layoutResId, null);
layoutSparseArray.put(id, resView);
addView(resView);
}

//那么哪里从集合中取数据呢
public void showContent() {
if (layoutSparseArray.get(LAYOUT_CONTENT_ID) != null) {
showHideViewById(LAYOUT_CONTENT_ID);
}
}
-END

————————————————
版权声明:本文为CSDN博主「generallizhong」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/generallizhong/article/details/103894884

android之事件分发机制

一、    public booleandispatchTouchEvent(MotionEvent ev)

用来进行事件的分发。如果当前事件能够传递给当前view,那么此方法一定会被调用,返回结果受当前View的onTochEvent和下级view的dispatchTouchEvent方法的影响,表示是否消耗当前事件。

public booleanonInterceptTouchEvent(MotionEvent ev)

在上面方法内部调用,用来判断是否拦截某个事件,如果当前view拦截了某个事件,

那么在同一个事件序列当中,此方法不会再次调用,返回结果表示是否拦截当前事件。

public booleanonTouchEvent(MotionEvent ev)

在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前view无法再次接受到事件。

二、 对于一个根viewgroup来说,点击事件产生后,首先会传递给它,这时它的dispatchTouchEvent就会被调用,如果这个viewgroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件,接着事件就会交给这个viewgroup处理,即它的onTouchEvent方法就会被调用;如果这个viewgroup的onInterceptTouchEvent返回false就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直到事件被*终处理。

三、但我们需要考虑一种情况,如果一个view的onTouchEvent返回false,那么它的父容器的onTouchEvent将会被调用。eg.上级分给了一个程序员去处理(这是事件分发的过程),结果这个程序员搞不定,就会交给上一级进行处理。上级处理不了,会交给上上级,这样一层一层往上抛。

默认:往下分发

True:拦截处理

False:往上抛(执行上级的onTouchEvent)

注:只有viewgroup才有拦截方法

Viewgroup(相对布局,线性布局,帧布局)

Activity return false时执行onTouchEvent

return true时不执行onTouchEvent

四、 如果view需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被调用。这时事件如何要看onTouch的返回值,如果返回false则调用当前view的onTouchEvent方法会被 调用,如果返回true则不会调用onTouchEvent。View的OnTouchListener其优先级比onTouchEvent要高。在onTouchEvent中设置OnClickListener,那么它的onClick方法会被调用。OnTouchListener的优先级*低,即处于事件传递的尾端。

Activity存储在DecorView中,DecorView继承于FrameLayout,*终为viewgroup, 顶级view叫根view,基本来说都是viewgroup

-END

android 动态添加布局并且实例控件

android动态加载布局:

一、很多时候我们加载布局都是直接通过set:

setContentView(R.layout.unityvideo2);
方式来加载布局。

二、我们还可以通过动态方式加载布局

//提供一个(布局)这个布局是一个xml,控件在xml中
View llHomeMember = inflater.inflate(R.layout.unityvideo2, null);

tv_home_member_view = (TextView) llHomeMember.findViewById(R.id.tv_home_member_view);
//把数据set进控件,
//加载到容器中
ll_home_member.addView(llHomeMember);
三、android动态加载布局,那么再来一个android,动态布局:
FrameLayout fl = new FrameLayout(MainActivity.this);
setContentView(fl);
RelativeLayout.LayoutParams rl_params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

customVideoView = new CustomVideoView(MainActivity.this);
RelativeLayout.LayoutParams customVideoView_params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

LinearLayout layout_skip = new LinearLayout(MainActivity.this);
RelativeLayout.LayoutParams layout_skip_params1 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layout_skip_params1.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);

TextView time_txt = new TextView(MainActivity.this);
time_txt.setText(“关闭”);
RelativeLayout.LayoutParams time_txt_params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);

time_txt_params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
layout_skip.addView(time_txt, time_txt_params);

LinearLayout mid_layout = new LinearLayout(MainActivity.this);
LinearLayout.LayoutParams mid_layout_params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);

mid_layout.setOrientation(LinearLayout.HORIZONTAL);
mid_layout.setGravity(Gravity.BOTTOM);

RelativeLayout re = new RelativeLayout(MainActivity.this);
RelativeLayout.LayoutParams re_params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);

ImageView ico_img = new ImageView(MainActivity.this);
RelativeLayout.LayoutParams ico_img_params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ico_img.setScaleType(ImageView.ScaleType.FIT_XY);
ico_img.setId(2);

LinearLayout mid_Ad_layout = new LinearLayout(MainActivity.this);
RelativeLayout.LayoutParams mid_Ad_layout_params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);

TextView mid_title_txt = new TextView(MainActivity.this);
time_txt.setText(“广告title”);
RelativeLayout.LayoutParams mid_title_txt_params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mid_title_txt_params.addRule(RelativeLayout.CENTER_IN_PARENT);

TextView mid_title_content_txt = new TextView(MainActivity.this);
time_txt.setText(“广告测试内容”);
RelativeLayout.LayoutParams mid_title_content_txt_params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);

mid_Ad_layout.addView(mid_title_txt, mid_title_txt_params);
mid_Ad_layout.addView(mid_title_content_txt, mid_title_content_txt_params);

Button mid_click_btn = new Button(MainActivity.this);
mid_click_btn.setText(“查看详情 “);
RelativeLayout.LayoutParams mid_click_btn_params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);

re.addView(ico_img, ico_img_params);
re.addView(mid_Ad_layout, mid_Ad_layout_params);
re.addView(mid_click_btn, mid_click_btn_params);

mid_layout.addView(re, re_params);

fl.addView(customVideoView, customVideoView_params);
fl.addView(layout_skip, layout_skip_params1);
fl.addView(mid_layout, mid_layout_params);
这是一个相对复杂的动态布局。供参考。

-end

android调用unity

上一篇说到了unity调用android,这里说说android调用unity:

可以直接看代码没有多少需要解释的:

一、先 写一个android调用unity的方法

/**
* 调用Unity的方法
*
* @param gameObjectName 调用的GameObject的名称
* @param functionName 方法名
* @param args 参数
* @return 调用是否成功
*/
boolean callUnity(String gameObjectName, String functionName, String args) {
try {
Class<?> classtype = Class.forName(“com.unity3d.player.UnityPlayer”);
Method method = classtype.getMethod(“UnitySendMessage”, String.class, String.class, String.class);
method.invoke(classtype, gameObjectName, functionName, args);
return true;
} catch (ClassNotFoundException e) {

} catch (NoSuchMethodException e) {

} catch (IllegalAccessException e) {

} catch (InvocationTargetException e) {

}
return false;
}
这里说一下“gameObjectName”这个参数,就是下图中,红色圈中的的名字,这里需要将调用的方法所在类添加到里面,名字自定义

%title插图%num

unity端调用:

Boolean bl = callUnity(“GameObject”, “AndroidCallUnity”, “来自android”);
二、unity端提供的方法:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class aarTest : MonoBehaviour
{

public void AndroidCallUnity(string msg) {

Debug.Log(“AndroidCallUnity callback onSuccess———: ” + msg);
}

}
以上就是android调用unity

 

 

-END

 

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速