Android Native Thread

Android native thread

本文使用android版本为:Android pie_9.0.0_r3

在Android的Framework层,使用线程时都是Thread类,究其根本还是POSIX标准的pthread类实现,只不过加入了一些android中特有的特性,比如:继承RefBase,将其纳入只能指针体系。不过因为对pthread进行了二次包装,其具体的线程相关API已和pthread大相径庭。在深入Framework代码时,难免会遇到Thread对象,了解它,对相关代码的理解将会更加顺利。

Thread定义

但只推荐理解,不推荐使用。在开发自己程序时,依然只建议使用pthread,因为在Thread类定义的地方,开发者已经做了相关不推荐使用的注释(如下一段代码所示)。至于深层次的原因,暂不得而知。

// /system/core/include/utils/Thread.h
namespace android {
// DO NOT USE: please use std::thread
class Thread : virtual public RefBase {
public:
    // 创建线程对象,但并不创建或启动关联线程。
    explicit            Thread(bool canCallJava = true); 
    virtual             ~Thread();
    // 在threadLoop函数中启动线程,threadLoop函数必须被实现
    virtual status_t    run(const char* name, int32_t priority = PRIORITY_DEFAULT, size_t stack = 0);
    // 请求对象的线程退出。 这个函数是异步的,当该函数结束后,线程可能还在运行。
    virtual void        requestExit();
    // 线程运行前执行,可以用于执行一些初始化函数
    virtual status_t    readyToRun();
    // 调用requestExit函数,并且等待对象的线程退出。可能造成死锁
            status_t    requestExitAndWait();
    // 等待直到线程退出。如果线程退出,则该函数立刻return,注意别再对象线程中调用该函数,这样容易导致死锁。
            status_t    join();
    // 用来查看线程是否正在运行
            bool        isRunning() const;

#if defined(__ANDROID__)
    // 和线程调用gettid()函数的调用是一致的,将会返回线程在内核中的ID。如果返回-1则表示线程并未运行。
            pid_t       getTid() const;
#endif

protected:
    // 如果requestExit()函数被调用,则该函数将返回true
            bool        exitPending() const;
private:
    // 子类必须实现的函数。是线程生命周期的开始,在Thread对象中,该函数有两种运行模式
    // 1、loop:如果threadLoop() return true, 该函数还将继续被执行,直到requestExit函数被调用
    // 2、once:如果threadLoop() return false, 线程将在该函数return时退出。
    virtual bool        threadLoop() = 0;
    
private:
    Thread& operator=(const Thread&);
    static  int             _threadLoop(void* user);
    const   bool            mCanCallJava;
    // 读或写时总是持有mLock
            thread_id_t     mThread; // 由创建线程时,pthread_t值的指针类型转换而来
    mutable Mutex           mLock;
            Condition       mThreadExitedCondition;
            status_t        mStatus;
    // 请注意,所有对mExitPending和running的访问都需要持有mLock
    volatile bool           mExitPending;
    volatile bool           mRunning;
            sp<Thread>      mHoldSelf;
#if defined(__ANDROID__)
    // legacy for debugging, not used by getTid() as it is set by the child thread
    // and so is not initialized until the child reaches that point
            pid_t           mTid;
#endif
};
}; // namespace android

上述是Thread类头文件中对于Thread的定义,我们重点关注构造函数和虚函数实现,即如下函数:

  • `Thread()与~Thread() :构造函数与析构函数
  • onFirstRef():从RefBase处即成的智能指针第一次创建的函数。
  • run:创建线程的函数,一般在threadLoop()函数中被调用
  • readyToRun():线程准备函数
  • _threadLoop():线程循环函数
  • requestExit():请求退出函数

接下来也会以此为序,对各个重要函数实现作详细解读。

构造函数与析构函数

Thread::Thread(bool canCallJava) :   
		mCanCallJava(canCallJava), mThread(thread_id_t(-1)), mLock("Thread::mLock"),
        mStatus(NO_ERROR), mExitPending(false), mRunning(false)
#if defined(__ANDROID__)
        , mTid(-1)
#endif
{
}

Thread::~Thread()
{
}

可以看到,构造函数非常简单,都只是初始化一些私有变量,简单了解一下这些变量的作用:

  • mCanCallJava :
  • mStatus :标志着线程运行时的状态
  • mExitPending:标志着线程是否调用过requestExit(),调用过则返回true
  • mRunning:线程是否正在运行

析构函数更简单,直接啥也没做。相信一些释放资源的工作都分配到其它函数中了。

onFirstRef()

当Thread对象创建,并被智能指针(如sp<Thread>)第一次引用时,就会调用该函数。该函数没有默认实现,但在FrameWork的很多代码中,都会通过重写该函数来初始化一些数据,阅读代码时应该引起足够的重视。

readyToRun():线程执行准备

该函数通常是用于使用者在线程运行之前,做一些准备工作的。所以,在默认实现中,直接返回了NO_ERROR,如果子线程有需要可以直接重写,以达到对应目的。

// /system/core/libutils/Threads.cpp
status_t Thread::readyToRun()
{
    return NO_ERROR;
}

run():创建线程

真正创建线程的函数,线程也是从这个函数调用之后开始正式运行的,通常子类会将该函数包装到一个类似start()的函数中,以示线程启动。

status_t Thread::run(const char* name, int32_t priority, size_t stack) {
    // 当线程发生异常,需要重新尝试时,需要将线程相关状态初始化到最开始的状态。
    mStatus = NO_ERROR;
    mExitPending = false;
    mThread = thread_id_t(-1);
    // 让Thread对象拥有自己的强指针,防止被销毁
    mHoldSelf = this;
    mRunning = true; // 标志线程已经运行,不一定正在运行。自己体会

    bool res;
    if (mCanCallJava) {
        res = createThreadEtc(_threadLoop, this, name, priority, stack, &mThread);
    } else { // 将_threadLoop作为线程函数,创建线程
        res = androidCreateRawThreadEtc(_threadLoop, this, name, priority, stack, &mThread);
    }

    if (res == false) { // 如果线程创建失败,设置相关的失败状态
        mStatus = UNKNOWN_ERROR;   // something happened!
        mRunning = false;
        mThread = thread_id_t(-1);
        mHoldSelf.clear();  // "this" 引用减一
        return UNKNOWN_ERROR;
    }
    return NO_ERROR;
}

可以创建一个新线程的入口有两个:createThreadEtc()androidCreateRawThreadEtc()。决定变量在于mCanCallJava,从变量的字面意思来说,就是如果变量为true,创建的线程可以被java代码调用。如果为false则在本地使用。

其实createThreadEtc()函数创建线程,最终也会使用 androidCreateRawThreadEtc()函数,只不过createThreadEtc()会在调用 androidCreateRawThreadEtc()函数之前,会去初始化JNI部分,使得java代码可以直接调用该线程。至于如何初始化,这主要是JNI部分的内容,就不在本文中展开了。

androidCreateRawThreadEtc

int androidCreateRawThreadEtc(android_thread_func_t entryFunction, // 线程函数
                               void *userData, // 线程函数对应的参数
                               const char* threadName __android_unused, // 线程名称
                               int32_t threadPriority,
                               size_t threadStackSize, // 线程栈大小
                               android_thread_id_t *threadId)
{
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 意味着子线程结束后自行释放资源,不需要主线程等待

#if defined(__ANDROID__)
    if (threadPriority != PRIORITY_DEFAULT || threadName != NULL) {
        thread_data_t* t = new thread_data_t;
        t->priority = threadPriority;
        t->threadName = threadName ? strdup(threadName) : NULL;
        t->entryFunction = entryFunction;
        t->userData = userData;
        entryFunction = (android_thread_func_t)&thread_data_t::trampoline;
        userData = t;
    }
#endif

    if (threadStackSize) { // 如果有指定栈大小,将其设置为线程栈大小
        pthread_attr_setstacksize(&attr, threadStackSize);
    }

    errno = 0;
    pthread_t thread; 
    int result = pthread_create(&thread, &attr, (android_pthread_entry)entryFunction, userData);
    pthread_attr_destroy(&attr);
	// ...
    if (threadId != NULL) {
        *threadId = (android_thread_id_t)thread; // XXX: this is not portable
    }
    return 1;
}

总结一下:

androidCreateRawThreadEtc()函数的作用是:封装了创建线程的操作。

run()函数通常调用androidCreateRawThreadEtc(),并为其指定_threadLoop函数指针作为线程函数,最终创建出了一个真正的线程,并通过参数将线程ID存储在mThread变量中。此时,如果一切顺利,mRunning = true

_threadLoop() : 线程函数

该函数是真正的线程函数,通过在run()函数中调用androidCreateRawThreadEtc()将该函数设置给pthread线程。

int Thread::_threadLoop(void* user) { // 线程函数创建时,传递的线程参数就是Thread对象的this指针
    Thread* const self = static_cast<Thread*>(user);
    // ...
    bool first = true;
    do {
        bool result;
        if (first) { // 第一次运行
            first = false; // 只执行一次
            self->mStatus = self->readyToRun(); // 调用Thread对象的readyToRun()函数
            result = (self->mStatus == NO_ERROR);
			// 如果状态正常,并且没有调用过退出相关函数,则一直循环执行threadLoop()函数。
            if (result && !self->exitPending()) { 
                result = self->threadLoop();
            }
        } else { // 后续运行,都全走这里
            result = self->threadLoop();
        }
        { // 建立mLock作用域
            Mutex::Autolock _l(self->mLock);
            if (result == false || self->mExitPending) { // 1. 条件满足线程退出
                self->mExitPending = true;
                self->mRunning = false;
                self->mThread = thread_id_t(-1);
                self->mThreadExitedCondition.broadcast();
                break;
            }
        }
        // Release our strong reference, to let a chance to the thread
        // to die a peaceful death.
        strong.clear();
        // And immediately, re-acquire a strong reference for the next loop
        strong = weak.promote();
    } while(strong != 0);
    return 0;
}

代码中,标记为1处,是线程退出的两种情况:

  • 主动退出:当threadLoop()函数的返回值为false时,线程主动结束。
  • 被动退出:当mExitPending为true时,由外界影响被动退出。mExitPending可以通过requestExit()函数设置为true。

这其中,threadLoop()函数是一个未被默认实现的函数,必须由子线程实现。通常子线程重写该函数后,会在其中实现主要的线程逻辑,当子线程想要结束线程时,只要该函数返回false即可,如果该函数返回true,并且外界没有调用requteExit()requestExitAndWait()函数,那么threadLoop()函数将会一直循环执行下去。

requestExit() 线程退出函数

该函数非常简单,直接看。

void Thread::requestExit()
{
    Mutex::Autolock _l(mLock);
    mExitPending = true;
}

Framework线程示例:StartPropertySetThread

该类为Thread子类,主要用于修改属性,具体作用就不展开,主要查看一下一个最简单的Thread线程,如何实现。

头文件:

// \frameworks\native\services\surfaceflinger\StartPropertySetThread.h
#include <stddef.h>

#include <utils/Mutex.h>
#include <utils/Thread.h>
namespace android {

class StartPropertySetThread : public Thread { // 继承自Thread
public:
    explicit StartPropertySetThread(bool timestampPropertyValue);
    status_t Start();
private:
    virtual bool threadLoop();
    static constexpr const char* kTimestampProperty = "service.sf.present_timestamp";
    const bool mTimestampPropertyValue;
};
}

具体实现:

#include <cutils/properties.h>
#include "StartPropertySetThread.h"

namespace android {

StartPropertySetThread::StartPropertySetThread(bool timestampPropertyValue):
        Thread(false), mTimestampPropertyValue(timestampPropertyValue) {}

status_t StartPropertySetThread::Start() { // 在start函数中,封装了run函数,以创建和运行真正的线程
    return run("SurfaceFlinger::StartPropertySetThread", PRIORITY_NORMAL);
}

bool StartPropertySetThread::threadLoop() { //实现主要逻辑
    property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");
    property_set("service.bootanim.exit", "0");
    property_set("service.bootanim.progress", "0");
    property_set("ctl.start", "bootanim");
    return false; // 返回fanlse表示 执行一次完后,线程就会等待退出。
}
} // namespace android

本文相对简单,强调了几个重要函数及其实现。总结一下

总结一下

  • Android framework层大量使用的线程类为Thread。但是不知是何原因,作者不推荐使用。
  • Thread类其实是对POSIX中pthread线程的一个包装,增强了其功能。
  • 在FrameWork中,Thread类,并不被直接使用。通常的做法是通过继承的方式,子类应该重写必要的函数。重要的函数有:
    • onFirstRef() :继承子RefBase,通常在这里初始化一些变量,并不限于线程。
    • readyToRun() :线程第一次运行时会调用的函数,通常在这里初始化一些线程相关的变量或数据。
    • threadLoop() :线程函数,在其中实现主要的线程逻辑,函数返回true时,还会重新运行,直到函数返回false,或者requestExit()类型的函数被调用
    • requestExit():线程被动退出函数,由其它线程调用,调用后线程可能不会立刻退出。
  • Android Framework中,继承Thread类,实现threadLoop()函数(其中包含主要线程逻辑)。需要创建和启动线程时,调用父类的run()函数即可实现最简单的线程。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Android 应用程序的 Java 层中捕获 Native 异常可以通过以下两种方式来实现: 1. 使用 try-catch 块捕获异常 在调用 Native 方法时,放置 try-catch 块来捕获异常。用于抓住 Native 层的异常,然后通过 logcat 输出日志信息。 例如: ``` try { // 调用 Native 方法 } catch (Throwable e) { Log.e(TAG, "Native method threw an exception: " + e.getMessage()); e.printStackTrace(); } ``` 在 catch 块中,使用 Log.e 输出一个错误日志,并使用 printStackTrace() 打印堆栈跟踪信息。 2. 通过设置 UncaughtExceptionHandler 在应用程序的 Application 类中设置 UncaughtExceptionHandler,从而捕获 Native 异常。这个方法将捕获所有未经捕获和处理的异常。 例如: ``` public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable ex) { Log.e(TAG, "Uncaught Exception: " + ex.getMessage()); ex.printStackTrace(); // 在这里执行你的处理逻辑 // 例如重启应用程序等等 } }); } } ``` 在 UncaughtExceptionHandler 的 uncaughtException() 方法中,使用 Log.e 输出一个错误日志,并使用 printStackTrace() 打印堆栈跟踪信息。在这个方法中,还可以执行一些处理逻辑,例如重启应用程序等等。 这两种方式可以结合使用,以便完全捕获和处理 Native 异常。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值