Android Handle,Looper,Message消息机制
尊重原创,转载请标明出处 http://blog.csdn.net/abcdef314159
我们知道在Android中更新UI都是在主线程中,而操作一些耗时的任务则须要在子线程中。假设存在多个线程共同更新UI,可能会导致页面显示混乱,所以在Android中不同意多线程来共同操作UI。仅仅同意在主线程中更新,以下我们就分析一下Android的消息机制,我们首先要了解这几个类:Handler。Message。Looper。MessageQueue。
除了Handler以外,其它的都是final类型,我们来先看一下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;
}
我们看到上面2-9行。打印的是一个警告。我们知道在Android中假设是匿名内部类,或者是一个内部成员类,且不是静态的,可能会出现内存泄漏。由于默认情况下它是持有外部类的引用的。在Handler中通常会伴随着一些耗时的操作,假设外部类退出,可能会导致一直持有外部引用不能被回收。导致内存泄漏,内存泄漏不是ANR,但会影响性能。我们能够这样改动,当持有的外部类被回收之后就不在处理。
static class MyHandler extends Handler {
WeakReference<Activity> mActivityReference; MyHandler(Activity activity) {
mActivityReference = new WeakReference<Activity>(activity);
} @Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
// do something
}
}
}
我们继续看上面Handler初始化的第11行的代码。获取looper对象,然后再在looper中获取MessageQueue的实例mQueue,MessageQueue是消息队列,存储消息的。我们还看到上面有一个异常RuntimeException,提示没有调用Looper.prepare()方法,这个异常可能非常多人都见过比較熟悉,由于假设我们在子线程中处理Handler消息的时候不调用这种方法。Looper.myLooper()就会返回为空。就会报上面的异常,可是在主线程中我们不须要调上面的方法,由于在主线程中已经默认的为我们调用了。我们在前面Android
setContentView方法解析(一)中大致提到过,他是在ActivityThread的main方法中调用的。我们看一下
public static void main(String[] args) {
SamplingProfilerIntegration.start(); // CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false); Environment.initForCurrentUser(); // Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter()); Security.addProvider(new AndroidKeyStoreProvider()); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread();
thread.attach(false); if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
} AsyncTask.init(); if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
} Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited");
}
我们看到上面18和34行,系统已经默认的为我们调用了,所以我们在主线程中处理消息的时候是不须要再调用的,我们看一下prepareMainLooper的源代码
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
我们再来看一下Looper.myLooper()这种方法
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); public static Looper myLooper() {
return sThreadLocal.get();
}
ThreadLocal是存储数据的一个类,专门存储到当前线程,且各个线程之间互不影响,这个以后再分析,在这里存储的是Looper对象,这个Looper就是在prepare方法中初始化的。我们看一下
public static void prepare() {
prepare(true);
} 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中,且ThreadLocal中之前不能存在Looper,否则会报上面的异常,由于要保证每一个thread中仅仅有一个Looper。假设一个thread存在多个looper就会创建多个MessageQueue。消息发送的时候可能就会出现不知道该发送到哪个消息队列的情况。所以他仅仅能有一个。ThreadLocal保存在当前的线程中,以后取的时候就从当前的线程中取。上面我们还看到prepare方法和prepareMainLooper是有差别的,主要在传的參数quitAllowed,我们看一下
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在Looper中初始化了一个MessageQueue对象
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
mQuitAllowed推断能否够退出消息循环,我们看一下MessageQueue的quit方法
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit.");
} synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true; if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
} // We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
看到没,子线程的looper和主线程的looper主要差别就在这,子线程是能够退出消息循环的,但主线程不能够。假设退出会报上面的异常,我们在看一下ActivityThread的main方法的最后一行throw new RuntimeException("Main thread loop unexpectedly exited");假设不明原因退出,也会报上面异常。
我们再来看一下消息的发送,在Handler中消息的发送有两种方式。一种是send一种是post。这两种方式被重构了好多。通过Message.obtain()方法获取Message对象。在Android中获取Message对象推荐的是使用obtain,不推荐使用new。我们来看一下Message的obtain的源代码。
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
}
我们看到在Message中维护了一个消息池。全部的Message对象都是从消息池中取的。假设没有就new一个,由于Message中有一个Message类型的next对象,是以一种单链表的形式维护的,它的最大容量是50。是在消息发送完回收的时候存的,我们来看一下
private static final int MAX_POOL_SIZE = 50;
…………………………
public void recycle() {
clearForRecycle(); synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
所以假设我们获取Message实例的时候最好调用它的obtain方法而不是new一个。我们接着看上面的,在上面的两种发送消息的时候终于调用的都是同一个方法,我们看一下
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);
}
这个消息队列mQueue就是刚才Handler初始化的时候从Looper中获取的,我们上面分析过。Looper是从当前的线程中的ThreadLocal获取的,所以这个消息队列也是属于当前线程的,我们再来看一下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 = this。由于Message有一个Handler类型target,所以在这里赋值给了他。待会取出消息处理的时候也是通过target来发送的,我们详细来看一下MessageQueue中的enqueueMessage方法的源代码,
boolean enqueueMessage(Message msg, long when) {
if (msg.isInUse()) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null) {
throw new AndroidRuntimeException("Message must have a target.");
} synchronized (this) {
if (mQuitting) {
RuntimeException e = new RuntimeException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} 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;
}
我们看一下20-24行。假设消息队列中没有消息。或者时间为0。或者时间小于当前的消息队列头的时间。就会把它增加到消息队列的前面,这里的消息是按时间存放的,由于在发送消息的时候有好几个重构的方法。当中就有延迟发送,时间越往后的越排在后面。
在发送的时候另一个sendMessageAtFrontOfQueue方法是把消息放到队列的最前面的。我们接着再看30-39行,通过不断的循环找到合适的位置。找的时候一般推断最后的是否为空,假设为空就表示到了队列的最后了,就是最后的位置,假设不为空就和当前Message的时间对照,假设时间比后一个短,说明要比他先运行,位置就在他前面,然后在41-42行,把消息存进去。
然后在最后调用Looper的loop方法运行循环,我们看一下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
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
} 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);
} msg.recycle();
}
}
我们看第14行,通过消息队列取出消息,这个取出消息调用的是MessageQueue的next()方法。这个我们待会再分析,我们知道他取出消息就好了。我们在看27行。就是消息的处理。再看44行,就是消息的回收。在上面我们提过,回收之后增加到消息池中,假设下次用到直接从里面取就是了,可是增加的时候把数据都清空了。我们看到Message的recycle方法中调用了这样一个方法clearForRecycle,我们看一下
/*package*/ void clearForRecycle() {
flags = 0;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
when = 0;
target = null;
callback = null;
data = null;
}
就是把数据所有清空之后然后增加到消息池中。我们还看上面的27行的代码msg.target.dispatchMessage(msg);我们刚才在上面提过在发送消息的时候把Handler赋给了Message的target。所以这里再从Message中取出target,调用它的dispatchMessage方法来处理消息,我们看一下
public interface Callback {
public boolean handleMessage(Message msg);
} /**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
} /**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
我们看到Handler中有个handleMessage方法,返回类型为void。另一个接口Callback,里面也有这样一个方法。返回类型为boolean。在调用dispatchMessage方法处理消息的时候首先调用的是Message的Callback,由于Message的Callback事实上就是一个Runnable,我们来看一下handleCallback方法。
private static void handleCallback(Message message) {
message.callback.run();
}
非常easy。就一行代码它所以在上面的处理消息的时候首先推断是否是post发送,假设是就调用它的run方法。假设不是,说明是send方式发送,就推断他自己的mCallback是否为空,假设不为空就调用他自己的接口,假设返回true就表示事件被消耗。就不在往下运行,否则就调用自己的handleMessage方法。好了,Android的消息机制到这来已经分析完了,上面还遗留了一个问题,就是从消息队列中获取消息的方法,我们来看一下
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
} // We can assume mPtr != 0 because the loop is obviously still running.
// The looper will not call this method after the loop quits.
nativePollOnce(mPtr, nextPollTimeoutMillis); synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (false) Log.v("MessageQueue", "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
} // Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
} // If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
} if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
} // Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf("MessageQueue", "IdleHandler threw exception", t);
} if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
} // Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
代码量比較多,我们找关键的看, 通过不断的循环来取出消息,第11行是延迟等待的时间, 28行假设运行的时间没到就计算延迟的时间nextPollTimeoutMillis。30到40行假设取出消息则返回。并把它置为markInUse,表示在使用。48行假设退出就返回为null,否则可能就会一直等待消息的到来,上面的pendingIdleHandlerCount假设小于等于0就表示消息已经被处理完了。在等待很多其它的消息。
OK。眼下为止Android的消息机制就分析完了,在上面我们说假设在主线程中处理消息的时候在ActivityThread的main方法中已经为我们初始化了looper,我们不需要再初始化,但假设在子线程中处理消息的时候就必需要手动初始化looper。由于子线程默认是没有初始化的。在主线程处理消息大家早就已经非常熟悉了,在这里就不在演示,以下我们就为大家演示一下在子线程处理消息要注意的事项,我们把子线程写在一个click事件中。
_ll.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
public void run() {
new Handler() {
public void handleMessage(Message msg) {
Log.d("wld", "Android源代码");
}
}.sendEmptyMessage(0);
}
}.start();
}
});
我们点击直接crash。我们看一下打印的log
这个就是最上面第一段代码的那个异常。由于默认情况下子线程是没有初始化looper的。所以直接报错,我们再改动一下
_ll.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
public void run() {
Looper.prepare();
new Handler() {
public void handleMessage(Message msg) {
Log.d("wld", "Android源代码");
}
}.sendEmptyMessage(0);
Looper.loop();
}
}.start();
}
});
我们再执行一下,发现没有问题,而且log也能正常打印,由于在子线程中我们已经初始化了looper。所以就没有报错。
假设我们不想在子线程中调用Looper的方法,我们也能够改动一下Handler
public class MyHandler extends Handler { public MyHandler() {
super(getMyLooper());
} private static Looper getMyLooper() {
/*从子线程中获取looper,假设我们在子线程中没有调用Looper.prepare();则会返回为
* null,假设为空我们就获取主线程的looper。主线程的looper肯定不会为null的,因
* 为主线程的looper在程序启动的时候就已经在ActivityThread的main方法中初始化了。*/
Looper mLooper = Looper.myLooper();
if (mLooper == null)
mLooper = Looper.getMainLooper();
return mLooper;
}
}
通过改动我们直接把looper传进去了,就不须要再创建了,我们再来改动一下之前的代码
_ll.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
public void run() {
new MyHandler() {
public void handleMessage(Message msg) {
Log.d("wld", "Android源代码");
}
}.sendEmptyMessage(0);
}
}.start();
}
});
我们看到这次并没有报错。而且还正常的打印了log。
以下我就画一个流程图来具体说明一下Android的消息机制。
可能大家会对上面的图感到有点困惑,Handler既然能发送Message。为什么不直接发送到Handler的dispatchMessage方法中自己处理,假设真的这样做是没有不论什么意义的。假设这样我们还不如直接在子线程直接处理,由于我们一般都是在子线程中发送消息,而在主线程中处理消息,由于这样做能够操作UI,像上面那种直接在子线程中发送消息又在子线程中处理消息的毕竟不多,但无论如何在子线程中是不能操作UI的,但Toast是个奇葩,在子线程中通过改动是能够正常弹出Toast的,这个我们以后讲到Toast源代码的时候在为大家分析
OK。Android的消息机制到如今已经分析完成。
Android Handle,Looper,Message消息机制的更多相关文章
- Android消息传递之Handler消息机制
前言: 无论是现在所做的项目还是以前的项目中,都会遇见线程之间通信.组件之间通信,目前统一采用EventBus来做处理,在总结学习EventBus之前,觉得还是需要学习总结一下最初的实现方式,也算是不 ...
- android开发系列之消息机制
最近接触到一个比较有挑战性的项目,我发现里面使用大量的消息机制,现在这篇博客我想具体分析一下:android里面的消息到底是什么东西,消息机制到底有什么好处呢? 其实说到android消息机制,我们可 ...
- Android Framework 分析---2消息机制Native层
在Android的消息机制中.不仅提供了供Application 开发使用的java的消息循环.事实上java的机制终于还是靠native来实现的.在native不仅提供一套消息传递和处理的机制,还提 ...
- Android学习笔记之消息机制
Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程. 1.为什么要使用Handler? Android规定访问UI只 ...
- android handler ,message消息发送方式
1.Message msg = Message.obtain(mainHandler) msg.obj=obj;//添加你需要附加上去的内容 msg.what = what;//what消息处理的类 ...
- Android架构:用消息机制获取网络数据
网络请求,不管是什么协议,是长连接还是短连接,总是一个异步的请求,过程包括:加请求参数->发起请求->接收响应->解析数据->获得业务数据. 最挫的做法是,业务代码包揽所有这些 ...
- Android 进阶14:源码解读 Android 消息机制( Message MessageQueue Handler Looper)
不要心急,一点一点的进步才是最靠谱的. 读完本文你将了解: 前言 Message 如何获取一个消息 Messageobtain 消息的回收利用 MessageQueue MessageQueue 的属 ...
- Android异步消息机制
Android中的异步消息机制分为四个部分:Message.Handler.MessageQueue和Looper. 其中,Message是线程之间传递的消息,其what.arg1.arg2字段可以携 ...
- 第十章:Android消息机制
Android的消息机制主要是指Handler的云心机制,Handler的运行需要底层的MessageQueue和Looper支持. Handler是Android消息机制的上层接口. 通过Handl ...
随机推荐
- GridControl CardView ShowCardExpandButton or GridCardExpandButton
关于DevExpress.XtraGrid.v13.1.dll和DevExpress.XtraGrid.v12.2.dll中ShowCardExpandButton 或者 GridCardExpan ...
- 重复造轮子之RSA算法(一) 大素数生成
出于无聊, 打算从头实现一遍RSA算法 第一步, 大素数生成 Java的BigInteger里, 有个现成的方法 public static BigInteger probablePrime(int ...
- VMware虚拟机直连物理网络的两种方式
VMware虚拟机直连物理网络的两种方式 使用VMware构建虚拟机,通常虚拟机都使用NAT模式.这时,虚拟机有独立的网段.使用NAT模式,虚拟机之间数据都通过虚拟网络传输,不会影响实体机所在的实 ...
- systemtap-oracle
https://savvinov.com/2015/12/21/non-intrusive-tracing/ https://mahmoudhatem.wordpress.com/2016/01/11 ...
- python获取对象的信息
Types 判断基本数据类型可以直接写int,str等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量. >>> import types >> ...
- jquery_ajax 入门实例
序:本文通过几个小样例,简单介绍怎样使用jqueryAjax异步载入. 1. $(selector).load(url,[data],[callback]) :加载远程HTML文件代码并插入DOM中. ...
- NYOJ 353 3D dungeon 【bfs】
题意:给你一个高L长R宽C的图形.每个坐标都能够视为一个方格.你一次能够向上.下.左,右,前,后任一方向移动一个方格, 可是不能向有#标记的方格移动. 问:从S出发能不能到达E,假设能请输出最少的移动 ...
- Jstl indexOf 参考
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> < ...
- 音频单元组件服务参考(Audio Unit Component Services Reference)
目录 了解Audio Unit体系结构 文档结构预览 结构单元介绍 本文主要介绍AudioUnit的组成 本文由自己理解而成,如有错误,请欢迎网友们指出校正. 了解Audio Unit体系结构 开始前 ...
- linux基础(5)- nginx服务、nfs服务
一.nginx服务 源码安装: yum install gcc-* glibc-* openssl openssl-devel pcre pcre-devel zlib zlib-devel -yls ...