chromium
base
java
做者: 易旭昕 (@roger2yi)node
Chromium 提供了一個相似 WTF 的基礎庫,甚至包含了更多的內容。這個基礎庫在 Blink 以外被普遍使用(Blink 裏面仍然使用的是 WTF),瞭解它的使用對咱們實際的代碼編寫是十分重要的。本文主要介紹 Chromium 基礎庫包括的主要內容,並詳細說明一些重要類型的使用方式。若是須要了解某個特定目錄或者文件的內容概要,學輝的這篇文檔能夠提供一個不錯的全面索引,另外 Chromium 爲全部的基礎庫類型都提供了完整的單元測試,經過閱讀單元測試代碼瞭解這些類型的使用也是很好的方式。android
Chromium 基礎庫包括的內容十分繁雜,我把其中的主要部分大體分爲如下幾類:web
容器類型數組
Chromium 的代碼主要使用 STL 容器類型,好比 std::vector,std::list,另外 GCC 和 MSVC 提供的 STL 擴展容器類型 hash_map 和 hash_set 也在 Chromium 中使用,不過統一放在 base 名字空間裏面,經過 base::hash_map,base_hash_set 使用。安全
在 STL 外,Chromium 基礎庫還提供了一些額外的容器類型好比 base::LinkedList,base::MRUCache 等。cookie
容器類型代碼位於 containers 子目錄下。多線程
智能指針閉包
Chromium 提供了一篇官方的文檔 Smart Pointer Guidelines 講解了在 Chromium 裏面常見的幾種智能指針,最多見的包括 base::scoped_ptr,base::ScopedVector,base::WeakPtr 和 base::scoped_refptr。app
智能指針代碼位於 memory 子目錄下。
回調函數
Chromium 基礎庫提供了 base::Bind 機制,能夠將全局函數和成員函數,跟它的調用上下文綁定在一塊兒,構成一個回調函數對象。這個回調函數對象能夠被傳遞,被保存,被當作消息發送到線程的消息 循環裏面,最後咱們能夠經過這個回調函數對象的 Run 方法調用跟它關聯的函數。
回調函數代碼位於基礎庫的根目錄下。
線程相關
Chromium 基礎庫提供了大量跟線程相關的設施,包括平臺線程的封裝類型 base::Thread,線程本地存儲 base::ThreadLocalPointer,消息循環 base::MessageLoop,線程同步設施 base::Lock,base::WaitableEvent 等等,還有原子操做和內存屏障的支持。
線程相關的代碼位於 threading,message_loop,synchronization 子目錄下,原子操做和內存屏障位於根目錄的 atomicops.h。
字串處理
Chromium 使用 std::string 做爲字串容器,官方文檔 Chromium String usage 提供了在 Chromium 裏面字串使用的一些說明。另外 strings 子目錄下提供了一些針對字串的輔助操做設施。
文件操做
Chromium 基礎庫的 base::File 提供了文件相關的操做,相關的代碼位於根目錄和 files 子目錄下;
計時器
Chromium 基礎庫的 base::Timer 提供了計時器相關的操做,相關的代碼位於 timer 子目錄下;
日誌和調試
Chromium 基礎庫提供了通用的日誌輸出和各類調試輔助等機制,相關的代碼位於根目錄, debug 和 profile 子目錄下;
系統監控
包括系統狀態監控,電池狀態監控和內存監控,分別位於 system_monitor,power_monitor,和 memory 子目錄下;
Android 相關
基礎庫的 android 子目錄下是 Android 平臺相關的代碼,除了包括其它基礎類型的 Android 適配代碼外,還有一些 Android 平臺特有的類型,像一些用於 JNI 支持的輔助類型。
除了上面列舉的部分外,基礎庫還包括的一些設施有進程,內存分配器,國際化支持,隨機數生成,Base64編碼,Sha1編碼等等,還有一些難以歸類的工具類型。
base::LinkedList 是 std::list 的一個替代品,優勢是當你擁有一個節點對象時,要刪除這個節點只須要 O(1) 的複雜度,而且插入節點不須要新增分配內存。可以作到這一點是由於 LinkedList 要求節點類型必須以 LinkNode 做爲基類,而 LinkNode 自己已經包含了指向前/後節點的指針。下面的代碼演示了 LinkedList 的常見用法:
class MyNodeType : public LinkNode<MyNodeType> { ... }; LinkedList<MyNodeType> list; LinkNode<MyNodeType>* n1 = ...; LinkNode<MyNodeType>* n2 = ...; LinkNode<MyNodeType>* n3 = ...; list.Append(n1); list.Append(n3); n2->InsertBefore(n3); for (LinkNode<MyNodeType>* node = list.head(); node != list.end(); node = node->next()) { MyNodeType* value = node->value(); ... }
MRU 是 most recently used 的縮寫,MRUCache 提供了一個相似 Map 的容器類型,主要的區別是能夠設定容器的最大容納個數,若是超過則自動移除最久不被使用的那個對象。
MRUCache 實際上還存在幾種不一樣的變種:
MRUCache 是最經常使用的,它假設自身不擁有對象,當移除對象時不執行刪除操做;
OwningMRUCache 假設本身擁有對象,並要求存儲對象是使用指針類型,在移除對象時會執行刪除操做;
HashingMRUCache 跟 MRUCache 的區別是,它內部使用 base::hash_map 而不是 std::map 存儲對象,因此也要求鍵值對象支持 hash 操做;
按照官方文檔的說明,何時咱們應該使用什麼類型的智能指針:
擁有對象的時候
使用 scoped_ptr 或者 ScopedVector,它們可使用來管理所擁有的非引用計數的堆分配對象。
不擁有對象的時候
使用 raw pointer 或者 WeakPtr。若是其它代碼擁有對象,可是你須要知道這個對象是否已經被銷燬,就使用 WeakPtr,當所關聯的對象被銷燬的時候 WeakPtr 會自動被置空。你能夠經過 WeakPtr.get 方法得到關聯對象的指針,若是返回值爲空則說明對象已經被銷燬。
使用引用計數對象的時候
使用 scoped_refptr,不過 Chromium 不鼓勵使用引用計數對象,特別是在多線程場景下,引用計數對象會使對象的擁有權難以肯定和對象銷燬的順序和時機難以肯定。
base::scoped_ptr 是 Chromium 裏面最經常使用的智能指針,一些常見的用法:
// We put a pointer into a smart pointer at construction time.scoped_ptr<base::Value> value(base::JSONReader::Read(data));scoped_ptr<Foo> foo_ptr(new Foo(...));// ...Or by using reset().scoped_ptr<Bar> bar_ptr; // Like "Bar* bar_ptr = NULL;".bar_ptr.reset(new Bar(...)); // Now |bar_ptr| is non-NULL and owns the object.// We can test the smart pointer directly or use get() to see the raw pointer underneath.if (!value) return false;Foo* raw_ptr = foo_ptr.get();// We can call through the smart pointer as if it were a pointer.DictionaryValue* dict = NULL;if (!value->GetAsDictionary(&dict)) return false;
當 scoped_ptr 做爲函數參數使用時,這意味着函數的代碼會得到參數對象的全部權,函數的調用者若是不是使用一個臨時的 scoped_ptr 的話,它須要使用 Pass() 方法來放棄本身的 scoped_ptr 對對象的全部權,例程以下:
// Foo() takes ownership of |bar|.void Foo(scoped_ptr<Bar> bar);...scoped_ptr<Bar> bar_ptr(new Bar());Foo(bar_ptr.Pass()); // Pass() makes |bar_ptr| NULL.Foo(scoped_ptr<Bar>(new Bar())); // No need to use Pass() on temporaries.
若是函數返回一個 scoped_ptr,這意味着函數的調用者得到返回對象的全部權,例程以下:
// Foo takes ownership of |bar|, and the caller takes ownership of the returned// object.scoped_ptr<Bar> Foo(scoped_ptr<Bar> bar) { if (cond) { return bar.Pass(); // Transfers ownership of |bar| back to // the caller. } return scoped_ptr<Bar>(new Bar())); // No Pass() necessary on temporaries. // Note that on this codepath, |bar| gets deleted here.}
最後須要注意的是不該該在函數的參數和返回值中使用 scoped_ptr 的指針或者引用形式(scoped_ptr<>* scoped_ptr<>&
),它會模糊全部權的轉移,使最終誰擁有對象的全部權難以理解。
在 STL 容器裏面存儲 scoped_ptr, 相似 std::vector<scoped_ptr<T> >
這樣的用法可能會有問題,好比下面的代碼:
std::vector<scoped_ptr<T> > vec;...// 對象的全部權會從 vec 轉移到 scoped_ptr p,並隨着 p 被銷燬而銷燬!!!scoped_ptr<T> p = vec[0];
由於上述代碼的危險性,因此 Chromium 不支持經過 STL 容器存儲 scoped_ptr,它提供了 base::ScopedVector 來知足大部分這種需求,ScopedVector 擁有存儲在它內部的對象,並在移除對象的時候負責銷燬對象,若是 ScopedVector 自己被銷燬,它會銷燬它所存儲的全部對象。由於 ScopedVector 內部存儲的是 raw pointer,就不存在像 std::vector<scoped_ptr<T> >
這樣容易誤用的危險性。
base::ScopedVector<T> vec;...// 經過 raw pointer p 使用對象,不會有全部權的轉移T* p = vec[0];
若是須要在其它 STL 容器裏面使用智能指針,但願在容器被銷燬或者移除元素時自動銷燬容器存儲的對象,能夠考慮使用 linked_ptr。
base::WeakPtr 是所謂的弱指針,Chromium 鼓勵更多使用 WeakPtr 而不是濫用須要引用計數的 scoped_refptr,由於 WeakPtr 明確不會擁有對象的全部權,也不會影響對象的銷燬順序。
base::WeakPtr 須要經過 base::WeakPtrFactory 建立,通常狀況下它們使用的方式是這樣的:
class Controller { public: void SpawnWorker() { Worker::StartNew(weak_factory_.GetWeakPtr()); } void WorkComplete(const Result& result) { ... } private: // Member variables should appear before the WeakPtrFactory, to ensure // that any WeakPtrs to Controller are invalidated before its members // variable's destructors are executed, rendering them invalid. WeakPtrFactory<Controller> weak_factory_; }; class Worker { public: static void StartNew(const WeakPtr<Controller>& controller) { Worker* worker = new Worker(controller); // Kick off asynchronous processing... } private: Worker(const WeakPtr<Controller>& controller) : controller_(controller) {} void DidCompleteAsynchronousProcessing(const Result& result) { if (controller_) controller_->WorkComplete(result); } WeakPtr<Controller> controller_; };
須要支持 WeakPtr 的類型 Controller 擁有一個 WeakPtrFactory 的成員變量,外部獲取的 WeakPtr 都是經過這個 WeakPtrFactory 建立的;
當 Controller 對象被銷燬時,它的 WeakPtrFactory 成員變量也會同時被銷燬,WeakPtrFactory 被銷燬的同時會將全部經過它建立的 WeakPtr 置空;
Controller 的 WeakPtrFactory 的成員變量通常放在最後面,這樣它就是第一個被銷燬的成員變量,彷佛沒有太大意義,不過 Chromium 習慣使用這樣的方式;
在多線程環境下使用 WeakPtr 和 WeakPtrFactory 須要注意,它們只支持這樣的方式:
WeakPtrFactory 和 WeakPtr 屬於建立它們的線程,也只能在建立它們的線程將 WeakPtr 置空,檢查一個 WeakPtr 是否爲空,和訪問 WeakPtr 指向的對象;
屬於線程 A 的 WeakPtr 能夠傳遞給 線程 B,線程 B 不能直接使用這個 WeakPtr,這不是線程安全的,可是它可使用這個 WeakPtr 往線程 A 發送任務(PostTask),由於任務是在線程 A 執行的,因此任務執行代碼自己可使用這個 WeakPtr;
用於支持引用計數對象的智能指針,要求對象類型繼承至 RefCounted 或者 RefCountedThreadSafe,後者是線程安全的。Chromium 由於歷史遺留的緣故,當前的代碼中使用 scoped_refptr 的地方還比較多,可是目前官方已經不鼓勵 scoped_refptr 的使用,認爲它會致使對象的全部權,和銷燬的順序和時機難以肯定,並認爲絕大部分狀況下 scoped_refptr 均可以使用 scoped_ptr 和 WeakPtr 來取代,設計自己也不該該過多依賴多個線程共享對象這種方式。
下面是一些簡單的使用例程:
class MyFoo : public RefCounted<MyFoo> { ... }; void some_function() { scoped_refptr<MyFoo> foo = new MyFoo(); foo->Method(param); // |foo| is released when this function returns } void some_other_function() { scoped_refptr<MyFoo> foo = new MyFoo(); ... foo = NULL; // explicitly releases |foo| ... if (foo) foo->Method(param); } { scoped_refptr<MyFoo> a = new MyFoo(); scoped_refptr<MyFoo> b; b.swap(a); // now, |b| references the MyFoo object, and |a| references NULL. } { scoped_refptr<MyFoo> a = new MyFoo(); scoped_refptr<MyFoo> b; b = a; // now, |a| and |b| each own a reference to the same MyFoo object. }
linked_ptr 行爲上有些相似 scoped_refptr,可是不須要對象自己支持引用計數,它是經過將全部指向同一個對象的 linked_ptr 連接成一條鏈來實現引用計數的,當一個 linked_ptr 從另一個 linked_ptr 拷貝時,它會把自身加入這條鏈,而這個 linked_ptr 被銷燬時,它會把自身從這條鏈移除,若是它是最後一個,則同時銷燬指向的對象。
linked_ptr 實際上有可能比 scoped_refptr 更危險,它使得對象的持有者和銷燬時機變得更不明確,同時也不是線程安全的。因此 linked_ptr 通常只是用在 STL 容器上面,容器持有這些對象,而且在容器自己被銷燬時銷燬對象,這樣就不會產生太多混亂。
base::SupportsUserData 的實現裏面使用了 linked_ptr,用來在一個 std::map 裏面存儲 User Data。
typedef std::map<const void*, linked_ptr<Data> > DataMap; // Externally-defined data accessible by key. DataMap user_data_;SupportsUserData::Data* SupportsUserData::GetUserData(const void* key) const { DataMap::const_iterator found = user_data_.find(key); if (found != user_data_.end()) return found->second.get(); return NULL;}void SupportsUserData::SetUserData(const void* key, Data* data) { user_data_[key] = linked_ptr<Data>(data);}
Chromium 提供了 base::Bind 和模版類型 base::Callback 對函數回調提供了支持,下面是一個簡單的使用例程,將一個全局函數綁定到一個 Callback 對象,並經過 Callback.Run 調用這個函數:
int Return5() { return 5; } base::Callback<int(void)> func_cb = base::Bind(&Return5); LOG(INFO) << func_cb.Run(); // Prints 5.
若是要綁定一個類的成員函數,咱們須要爲 Bind 方法提供這個類的一個實例對象,把它跟 Callback 對象綁定,爲了保證這個對象在 Callback 對象被執行時仍然存活,或者 Callback 對象可以知道這個對象已經被銷燬,咱們須要提供一個 scoped_refptr 或者 WeakPtr,經過 base::Unretained(ptr) 用 raw pointer 也能夠,不事後果自負... 早期 Chromium 的代碼使用 scoped_refptr 比較多,如今 Chromium 更傾向於使用 WeakPtr,固然使用 WeakPtr 時咱們要注意這個 Callback 只能在 WeakPtr 所屬的線程中被調用,由於它是非線程安全的,下面是一個使用 scoped_refptr 的例子:
class Ref : public base::RefCountedThreadSafe<Ref> { public: int Foo() { return 3; } void PrintBye() { LOG(INFO) << "bye."; } }; scoped_refptr<Ref> ref = new Ref(); base::Callback<void(void)> ref_cb = base::Bind(&Ref::Foo, ref); LOG(INFO) << ref_cb.Run(); // Prints out 3.
若是綁定的函數須要參數,咱們能夠事先綁定全部參數對象到 Callback 裏面,也能夠事先不綁定參數,甚至能夠事先只綁定一部分參數,事先綁定全部參數的 Callback 在 Chromium 裏面稱爲閉包 Closure:
void MyFunc(int i, const std::string& str) {} base::Callback<void(int, const std::string&)> cb = base::Bind(&MyFunc); cb.Run(23, "hello, world"); void MyFunc(int i, const std::string& str) {} base::Callback<void(void)> cb = base::Bind(&MyFunc, 23, "hello world"); cb.Run(); base::Closure cb = base::Bind(&MyClass::MyFunc, this, 23, "hello world");
若是想讓 Callback 對象擁有跟它綁定的類對象或者參數對象,也可使用 base::Owned 或者 base::Passed 方法,分別針對 raw pointer 和 scoped_ptr,若是是 scoped_ptr 類型參數的話,在調用時 Callback 就會將這個參數對象的全部權轉移給被回調的函數,最後 Callback 對象被銷燬時會自動銷燬綁定的類對象和參數對象(若是還擁有這個參數對象的話):
MyClass* myclass = new MyClass; base::Bind(&MyClass::Foo, base::Owned(myclass)); void TakesOwnership(scoped_ptr<Foo> arg) {} scoped_ptr<Foo> f(new Foo); // f becomes null during the following call. base::Closure cb = base::Bind(&TakesOwnership, base::Passed(&f));
總而言之,在使用 Chromium 的回調函數機制時,必定要很是清楚跟 Callback 對象綁定的類對象和參數對象的全部權和生命週期,避免在 Callback 被調用時,訪問到已經被銷燬的對象。
base::Thread 是 Chromium 提供的對平臺線程的封裝,並自帶了消息循環 base::MessageLoop,若是須要一個不用消息循環的線程,能夠考慮使用 base::SimpleThread。
一個繼承 base::Thread 的本身的線程類,可能須要複寫 Init 和 Cleanup 方法,它們在這個線程中被調用,分別位於消息循環啓動和中止的時候。
class InProcessRendererThread : public base::Thread { public: ... protected: virtual void Init() override; virtual void CleanUp() override; ...};
咱們能夠經過 Thread.message_loop 或者 Thread.message_loop_proxy 方法獲取這個線程的消息循環,後者返回的是 MessageLoopProxy,在 Chromium 裏面使用 MessageLoopProxy 比直接使用 MessageLoop 要更廣泛,而且經過 scoped_refptr 的方式使用 MessageLoopProxy 比經過 raw pointer 的方式使用 MessageLoop 也更安全,經過下面的兩種方式能夠得到當前運行線程的 MessageLoopProxy。
MessageLoop::current()->message_loop_proxy() MessageLoopProxy::current()
MessageLoopProxy 繼承了接口 SequencedTaskRunner,後者又繼承了接口 TaskRunner,因此 MessageLoopProxy 實現了一系列的 PostXXXTask 的方法。一個 Task 實際上就是一個 Closure,如前所述 Closure 就是一個預先綁定了全部參數對象的 Callback 對象。 經過 MessageLoopProxy PostTask 就至關於發送一個消息給這個 MessageLoopProxy 所屬的線程,這個被髮送的 Callback 對象將會在 MessageLoopProxy 所屬的線程執行,跟 Callback 對象綁定的函數將會被調用。
PostXXXTask 有若干變種,包括延遲的時間,是不是 Non-Nestable。延遲時間比較容易理解,不須要延遲則爲 0,而 Non-Nestable 的意思是 - 如 果 Task T1 在執行過程當中 Post Task T2 到當前線程的 MessageLoop,而且 T1 接着直接調用 MessageLoop 的 Run,或者 RunLoop 的 Run 方法,至關於要求 MessageLoop 在當前消息循環中進入一個子循環,立刻執行其它等待中的任務,在這種情況下 MessageLoop 進入了 Nested 狀態,若是 T2 是 Non-Nestable,Chromium 將會保證 T2 在這種狀況下絕對不會被執行,若是 T2 不是 Non-Nestable,就有可能在會被執行。
下面是一個簡單使用例程:
scoped_refptr<base::MessageLoopProxy> ui_loop_;base::WeakPtr<SharedRendererState> ui_thread_weak_ptr_;void SharedRendererState::PostExternalDrawConstraintsToChildCompositor( const ParentCompositorDrawConstraints& parent_draw_constraints) { if (UpdateDrawConstraints(parent_draw_constraints)) { // No need to hold the lock_ during the post task. ui_loop_->PostTask( FROM_HERE, base::Bind(&SharedRendererState::UpdateParentDrawConstraintsOnUIThread, ui_thread_weak_ptr_)); }}
若是須要任務執行後原線程得到通知,可使用 PostTaskAndReply 方法,參考下面的例程,task 執行後,reply 會在調用 PostTaskAndReplay 的原線程被調用,而且 task 和 reply 對象都保證在原線程被銷燬,這樣咱們能夠在 task 和 reply 上綁定必需要在原線程銷燬的對象。另一些須要注意的地方:
task 綁定的類對象會做爲參數傳遞給 reply 的回調函數;
reply 綁定的類對象 DataLoder 不是線程安全的,它經過 WeakPtr 跟 reply 綁定,能夠提早被銷燬,reply 會被自動取消;
bool PostTaskAndReply(const tracked_objects::Location& from_here, const Closure& task, const Closure& reply); class DataBuffer : public RefCountedThreadSafe<DataBuffer> { public: // Called to add data into a buffer. void AddData(void* buf, size_t length); ... }; class DataLoader : public SupportsWeakPtr<DataLoader> { public: void GetData() { scoped_refptr<DataBuffer> buffer = new DataBuffer(); target_thread_.message_loop_proxy()->PostTaskAndReply( FROM_HERE, base::Bind(&DataBuffer::AddData, buffer), base::Bind(&DataLoader::OnDataReceived, AsWeakPtr(), buffer)); } private: void OnDataReceived(scoped_refptr<DataBuffer> buffer) { // Do something with buffer. } };
若是 PostTask 以後,咱們又但願取消它,可使用 base::CancelableTaskTracker 來 PostTask,CancelableTaskTracker 自己不是線程安全的,它的建立,銷燬,PostTask,Cancel 都必須在同一個線程。下面是一個簡單的使用例子:
Thread worker_thread("worker thread"); worker_thread.Start(); CancelableTaskTracker::TaskId task_id = task_tracker_.PostTaskAndReply(worker_thread.message_loop_proxy().get(), FROM_HERE, Bind(&DoNothing), Bind(&DoNothing)); task_tracker_.TryCancel(task_id);
Chromium 提供了 ThreadLocalPointer,它是平臺相關的線程本地存儲機制的封裝,能夠存放一個 raw pointer,若是須要的是 bool 類型的變量,ThreadLocalBoolean 提供了更簡單的使用方式。ThreadLocalPointer 的一個簡單例程:
// My class is logically attached to a single thread. We cache a pointer // on the thread it was created on, so we can implement current(). MyClass::MyClass() { DCHECK(Singleton<ThreadLocalPointer<MyClass> >::get()->Get() == NULL); Singleton<ThreadLocalPointer<MyClass> >::get()->Set(this); } MyClass::~MyClass() { DCHECK(Singleton<ThreadLocalPointer<MyClass> >::get()->Get() != NULL); Singleton<ThreadLocalPointer<MyClass> >::get()->Set(NULL); } // Return the current MyClass associated with the calling thread, can be // NULL if there isn't a MyClass associated. MyClass* MyClass::current() { return Singleton<ThreadLocalPointer<MyClass> >::get()->Get(); }
base::Lock 是平臺相關鎖的封裝,base::AutoLock 提供了一個自動加鎖/解鎖的輔助類,這部分都比較容易理解。base::ConditionVariable 是平臺相關的條件量的封裝,跟其它庫的 Condition 類型有些不一樣的是,它須要在構造時就指定對應的鎖,而不是在 Wait 的時候才指定。
爲了方便實現線程同步消息,Chromium 還提供了 base::WaitableEvent (若是是位於 cc 模塊的代碼,也可使用 cc::CompletionEvent,它是 base::WaitableEvent 的封裝),WaitableEvent 構造函數的第一個參數 manual_reset 的含義是,若是它爲 false,一個已經 signaled 的 WaitableEvent 在被查詢 IsSignaled 後會自動恢復到 unsignaled 的狀態,因此通常沒有特殊須要第一個參數都應該爲 true。下面是一個簡單的線程同步消息處理的例子:
template <typename T>static void RunTaskWithResult(base::Callback<T(void)> task, T* result, base::WaitableEvent* completion) { *result = task.Run(); completion->Signal();} base::WaitableEvent completion(true, false); bool result = false; QueueTask( base::Bind(&RunTaskWithResult<bool>, init_task, &result, &completion)); completion.Wait();
base::WaitableEventWatcher 提供了 WaitableEvent 異步響應的使用方式,請看下面的例程,咱們能夠經過 WaitableEventWatcher 監控某個 WaitableEvent,並在它被 Signal 的時候觸發事先設定的回調函數,實際內部實現是當 WaitableEvent 被 Signal 時,WaitableEventWatcher 事先設定的 Callback 對象會被髮送到 StartWatching 的調用線程的消息循環裏面:
class MyClass { public: void DoStuffWhenSignaled(WaitableEvent *waitable_event) { watcher_.StartWatching(waitable_event, base::Bind(&MyClass::OnWaitableEventSignaled, this); } private: void OnWaitableEventSignaled(WaitableEvent* waitable_event) { // OK, time to do stuff! } base::WaitableEventWatcher watcher_; };
Chromium 主要使用 std::string 做爲字串類型,std::string 的一個主要問題是它自己不包含編碼信息,因此 Chromium 約定 std::string 使用 UTF-8 編碼,基礎庫裏面還提供了 base::string16,string16 使用 UTF-16 編碼。
Chromium 另外還有一個 base::StringPiece 類型,它相似 WTF 裏面的 CString,基本上就是 C 風格字串的一個簡單封裝,StringPiece 一般只是用來傳遞一塊 string data 或者 raw data,它自己並不擁有這些數據,銷燬時也不會釋放數據。
下面是一些使用時的注意事項:
使用 string.empty() 作空串檢查;
字串常量使用 char[] 而不是 std::string,好比 const char kFoo[] = 「foo」;
在函數輸入參數中使用 std::string,最好使用引用常量的方式避免拷貝;
在循環的 inner loop 裏面,通常應該避免臨時 std::string 對象建立;
Chromium 提供的一些字串處理的輔助方法,好比字串格式化,分割,數值字串類型轉換,比較,替換等等,位於 strings 子目錄下,都比較簡單,這裏就再也不詳細說明了。
base::PathService 提供了一種設定和獲取一些預約義用途目錄的機制,在 Android 上,咱們須要的目錄定義在 base_path_android.h 和 ui_base_path.h 裏面,另外 PathService.java 提供了在 Java 端設定路徑的功能。
enum { PATH_ANDROID_START = 300, DIR_ANDROID_APP_DATA, // Directory where to put Android app's data. DIR_ANDROID_EXTERNAL_STORAGE, // Android external storage directory. PATH_ANDROID_END};
PathService.override(PathService.DIR_MODULE, "/system/lib/"); final int DIR_RESOURCE_PAKS_ANDROID = 3003; PathService.override(DIR_RESOURCE_PAKS_ANDROID, "/system/framework/webview/paks");
base::File 提供了平臺相關的文件對象的封裝,能夠經過它對文件和目錄進行操做,包括建立,讀寫文件等等。base::FilePath 提供了一個文件或者目錄路徑的封裝。
base::FileProxy 提供了一種異步文件操做的方法,你能夠爲 FileProxy 設置一個 TaskRunner,好比某個線程的 MessageLoopProxy,而後在 FileProxy 上執行的操做實際上都是由這個 TaskRunner 所屬的線程異步執行,FileProxy 提供的方法跟 File 基本一致,通常後面會增長一個用於響應操做結果的 Callback 對象,這個 Callback 對象會在原調用線程執行。FileProxy 有一個限制是不能同時 Proxy 多個操做,只有完成一個操做後才能執行下一個操做。
下面是一個簡單的例程,咱們在另一個 file_thread_ 線程建立或者打開一個文件,當文件建立或者打開後,原線程會執行 DidCreateOrOpen 函數處理操做結果:
TaskRunner* file_task_runner() const { return file_thread_.message_loop_proxy().get(); } void DidCreateOrOpen(File::Error error) { error_ = error; MessageLoop::current()->QuitWhenIdle(); } FileProxy proxy(file_task_runner()); proxy.CreateOrOpen( test_path(), File::FLAG_CREATE | File::FLAG_READ, Bind(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr())); MessageLoop::current()->Run(); EXPECT_EQ(File::FILE_OK, error_); EXPECT_TRUE(proxy.IsValid()); EXPECT_TRUE(proxy.created()); EXPECT_TRUE(PathExists(test_path()));
Chromium 還提供不少文件相關的輔助類:
base::FileEnumerator 提供了枚舉某個 FilePath 下面的子文件的功能;
base::FilePathWatcher 提供了監控某個文件或者目錄變化的功能;
base::ImportantFileWriter 提供了另一種文件寫入方式,避免應用崩潰致使文件寫入一半,數據不完整的情況,原理是先寫入一個臨時文件,寫完後再重命名;
base::MemoryMappedFile 提供了一種將只讀文件所有或者部分映射到內存,讀取文件至關於內存訪問,加快讀取的速度的機制;
file_util.h 裏面提供大量文件操做的輔助方法,好比 CreateTemporaryFile,GetFileSize 等等;
base::Timer 實際上至關於 MessageLoop::PostDelayedTask 的封裝,對外提供了一次性或者不斷重複的計時器功能。Timer 的構造函數裏面 retain_user_task 的含義是,當 Timer 被 Stop 的時候,關聯的任務是否被保留,默認值爲 true,也就是保留而不置空。跟 WTF 裏面的 Timer 同樣,base::Timer 是有線程歸屬性的,它屬於調用 Start 或者 Reset 方法的線程,設置的任務也在這個線程裏面執行。
base::Timer timer(false, false); EXPECT_FALSE(timer.IsRunning()); timer.Start(FROM_HERE, TimeDelta::FromDays(1), base::Bind(&TimerTestCallback)); EXPECT_TRUE(timer.IsRunning()); timer.Stop(); EXPECT_FALSE(timer.IsRunning()); EXPECT_TRUE(timer.user_task().is_null());
base::ElapsedTimer 提供一個簡單的方法給程序計算某些操做的耗時。
Chromeium 提供了 LOG,DLOG,VLOG 幾種輸出日誌的方式,相似下面這樣的代碼:
LOG(INFO) << "Found " << num_cookies << " cookies";LOG_IF(INFO, num_cookies > 10) << "Got lots of cookies";
INFO 是輸出日誌的級別,一共包括 INFO,WARNING,ERROR 和 FATAL 這四種,其中 FATAL 會在日誌輸出後自動引起一個崩潰。LOG_IF 提供了額外的條件判斷,條件成立時才輸出日誌。DLOG 跟 LOG 的區別是 DLOG 只在 DEBUG 版本才生效,而 VLOG 跟 LOG 的區別是能夠用 verbose 級別來控制是否生效,好比:
VLOG(1) << "I'm printed when you run the program with --v=1 or more";VLOG(2) << "I'm printed when you run the program with --v=2 or more";
當啓動開關 --v=1 時 VLOG 1 以上的級別生效。
Chromium 提供了強大的 Tracing 機制,在 Android 上也對接了 Android Systrace 機制,因此對咱們來講,最簡單的方式就打開 Chromium Tracing,而後經過 Android Systrace 捕捉跟蹤的輸出。
在 TestShell 裏面,咱們能夠經過設置 BrowserActivity.ENABLE_ATRACE 開啓 Chromium Tracing,或者經過菜單開啓,而後調用 Android systrace 命令捕捉便可。
若是要增長跟蹤的方法,最簡單的方式是使用以下代碼:
TRACE_EVENT0("android_webview", "BrowserViewRenderer::OnDrawHardware");
更復雜的跟蹤方式能夠參考 trace_event.h 裏面的說明文檔。
base::debug::StackTrace 提供了調用堆棧打印的功能,StackTrace 會在被構造的時候存儲當前的調用堆棧數據,而後能夠經過它直接打印到控制檯或者獲取相應的文本,在 Android 上是經過 logcat 輸出 error 日誌。通常來講 StackTrace 能夠做爲函數的臨時變量輸出當前函數的調用堆棧,也能夠做爲對象的成員變量記錄對象建立時的調用堆棧。StackTrace 輸出的是地址信息,還須要使用符號表和對應的工具翻譯成可讀的函數名字。
下面是一個簡單的使用例程和輸出的結果:
void AwContents::Destroy(JNIEnv* env, jobject obj) { base::debug::StackTrace().Print(); ...}#00 0x751c38a1 /data/app-lib/com.uc.webkit.test-1/libwebviewuc.so+0x001f08a1#01 0x4153f30f /system/lib/libdvm.so+0x0001d30f
Chromium 提供了 MemoryPressureListener 接口,在 Android 上實際對接了 ComponentCallbacks2 的 onTrimMemory 和 onLowMemory(MemoryPressureListener.java)。
使用方式以下:
void OnMemoryPressure(MemoryPressureLevel memory_pressure_level) { ... } // Start listening. MemoryPressureListener* my_listener = new MemoryPressureListener(base::Bind(&OnMemoryPressure)); ... // Stop listening. delete my_listener;
建立 MemoryPressureListener 對象,並傳入一個 Callback 對象,啓動監聽;
銷燬 MemoryPressureListener 中止監聽;
Callback 對象會在建立 MemoryPressureListener 的線程被調用,調用是異步的,經過線程消息,即便這個線程就是 Android 的 UI 線程;
MemoryPressureLevel 包括 MEMORY_PRESSURE_MODERATE 和 MEMORY_PRESSURE_CRITICAL,跟 Android ComponentCallbacks2.onTrimMemory 和 onLowMemory 的對應關係以下面代碼所示;
// Modules are advised to free buffers that are cheap to re-allocate and not// immediately needed.DEFINE_MEMORY_PRESSURE_LEVEL(MEMORY_PRESSURE_MODERATE, 0)// At this level, modules are advised to free all possible memory.// The alternative is to be killed by the system, which means all memory will// have to be re-created, plus the cost of a cold start.DEFINE_MEMORY_PRESSURE_LEVEL(MEMORY_PRESSURE_CRITICAL, 2)
public void onTrimMemory(int level) { maybeNotifyMemoryPresure(level);}public void onLowMemory() { nativeOnMemoryPressure(MemoryPressureLevelList.MEMORY_PRESSURE_CRITICAL);}public static void maybeNotifyMemoryPresure(int level) { if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { nativeOnMemoryPressure(MemoryPressureLevelList.MEMORY_PRESSURE_CRITICAL); } else if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND || level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { // Don't notifiy on TRIM_MEMORY_UI_HIDDEN, since this class only // dispatches actionable memory pressure signals to native. nativeOnMemoryPressure(MemoryPressureLevelList.MEMORY_PRESSURE_MODERATE); }}
base::BuildInfo 基本上等同於 Android 的 android.os.Build 的 Native 版本,能夠經過它得到一些 Build 相關的信息,好比系統版本號等。
path_utils.h 提供一些輔助函數,用於獲取 Android 系統或者應用相關的特定目錄,好比 GetDataDirectory 返回當前應用的 Data 目錄。
jni_string.h 提供了一些跟字串相關的輔助函數,用於 Java String 和 Native String 之間的轉換,好比 ConvertJavaStringToUTF8,ConvertUTF8ToJavaString 等。
jni_array.h 提供了一些跟數組相關的輔助函數,好比 ToJavaXXXArray 將一個 Native 數組轉換成一個 Java 數組對象,轉換過程當中原始的數據會被拷貝。
base::android::ScopedJavaLocalRef 和 base::android::ScopedJavaGlobalRef 提供了在 Native 端持有一個 Java 對象,並在 Scoped 對象被銷燬時自動解除該 Java 對象引用的機制,有點相似是針對 Java 對象的 scoped_refptr。
ScopedJavaLocalRef 對應 JNI 的 LocalRef,做爲棧對象在函數內部使用,通常用於在函數結束時自動解除關聯的 Java 對象的引用,或者做爲函數的返回值傳遞 Java 對象的引用給它的調用者:
bool GetDatabaseDirectory(FilePath* result) { JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jstring> path = Java_PathUtils_getDatabaseDirectory(env, GetApplicationContext()); FilePath data_path(ConvertJavaStringToUTF8(path)); *result = data_path; return true;}
ScopedJavaGlobalRef 對應 JNI 的 GlobalRef,通常做爲類的成員變量,或者在須要超過某個函數的調用生命週期去持有一個 Java 對象的情況下使用。
下面的例子演示了一個異步回調的處理,咱們須要一個 ScopedJavaGlobalRef 保證這個關聯的 Java 對象在回調函數被真正執行時任然存活而不會被銷燬,base::Owened 將 j_callback 的擁有權轉移給 base::Bind 建立的 Callback 對象。
void GenerateMHTMLCallback(ScopedJavaGlobalRef<jobject>* callback, const base::FilePath& path, int64 size) { JNIEnv* env = AttachCurrentThread(); // Android files are UTF8, so the path conversion below is safe. Java_AwContents_generateMHTMLCallback( env, ConvertUTF8ToJavaString(env, path.AsUTF8Unsafe()).obj(), size, callback->obj());}} // namespacevoid AwContents::GenerateMHTML(JNIEnv* env, jobject obj, jstring jpath, jobject callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); ScopedJavaGlobalRef<jobject>* j_callback = new ScopedJavaGlobalRef<jobject>(); j_callback->Reset(env, callback); base::FilePath target_path(ConvertJavaStringToUTF8(env, jpath)); web_contents_->GenerateMHTML( target_path, base::Bind(&GenerateMHTMLCallback, base::Owned(j_callback), target_path));}
base::JavaObjectWeakGlobalRef 用於持有一個 Java 對象的弱引用,對應 JNI 的 WeakGlobalRef,當須要使用這個 Java 對象時能夠經過 JavaObjectWeakGlobalRef.get 返回一個 ScopedJavaLocalRef。
這一節的內容包括一些比較零散,沒法歸類的工具類型。
base::SupportsUserData 是用來給一個對象增長 UserData 支持的輔助類,User Data,也叫 Client Data,通常使用 Key-Value 的方式存儲,是這個對象的使用者將本身或者其它的使用者須要用到的一些數據附加在這個對象上面的一種機制,對象自己只是做爲這些數據的一個載體。
須要承載 User Data 的類,須要繼承 SupportsUserData,好比 content::WebContents,而須要做爲 User Data 存儲的類型,須要繼承 SupportsUserData::Data,使用 void* 指針作 key。SupportsUserData 是非線程安全的,若是跨線程使用,須要使用者本身保證線程安全。
LazyInstance 提供了一種延遲建立全局靜態對象的方式,它的優勢是:
它預先在程序的靜態內存區分配了對象的內存,當對象建立時就不須要在堆上分配內存,加快了對象建立的速度和減小堆內存碎片;
它的對象建立是線程安全的,不用擔憂多線程競爭的情況;
它延遲對象的建立到第一次使用的時候,避免在程序啓動時建立,減小了啓動的時間開銷;
總的來講 LazyInstance 就像是函數內部的靜態對象的線程安全版本,下面是使用的例程:
static LazyInstance<MyClass> my_instance = LAZY_INSTANCE_INITIALIZER; void SomeMethod() { my_instance.Get().SomeMethod(); // MyClass::SomeMethod() MyClass* ptr = my_instance.Pointer(); ptr->DoDoDo(); // MyClass::DoDoDo }
若是明確不須要銷燬對象,不須要調用析構函數,可使用 Leaky 類型定義(實際上在 CAW 上,用不用 Leaky 都同樣,參看下面的 Singleton):
base::LazyInstance<GlobalTileManager>::Leaky g_tile_manager = LAZY_INSTANCE_INITIALIZER;
通常方便本身的類型實現單例模式的輔助類,使用的例程以下:
// In your header: template <typename T> struct DefaultSingletonTraits; class FooClass { public: static FooClass* GetInstance(); void Bar() { ... } private: FooClass() { ... } friend struct DefaultSingletonTraits<FooClass>; DISALLOW_COPY_AND_ASSIGN(FooClass); };// In your source file: FooClass* FooClass::GetInstance() { return Singleton<FooClass>::get(); }// And to call methods on FooClass: FooClass::GetInstance()->Bar();
須要注意的是:
Singleton::get() 的調用方法必須命名爲 GetInstance;
GetInstance 不能是 inline 的,也就是說它的實現不能放在頭文件裏面;
Singleton::get() 有必定的時間開銷,避免在循環的 inner loop 裏面每次都調用;
對於 CAW 來講,使用 Singleton 的類型的析構函數是不會被自動調用的,對於 Chrome for Android 來講,在子進程退出時,使用 Singleton 的類型的析構函數在進程退出時被自動被調用,另外 LazyInstance 的情況也同樣;
總的來講,Chromium 並不鼓勵使用單例模式,因此能不用仍是不用。
base::AutoReset 是一個很簡單的輔助類,它通常做爲棧對象使用,用途是構造時保存變量原有的值並設置新的值,當生命週期結束,析構的時候恢復變量原有的值。
{ base::AutoReset<bool> frame_resetter(&viewport_clip_valid_for_dcheck_, true); layer_tree_host_->SetNeedsRedrawRect(clip_); layer_tree_host_->Composite(gfx::FrameTime::Now()); }
base::ObserverList 是幫助實現觀察者模式的一個輔助類,顧名思義,它提供了一個觀察者列表容器。除此之外,使用 ObserverList 而不是直接使用 std::vector 或者 std::list 的緣由還在於 ObserverList 提供了一個特定版本的迭代器實現,在迭代的過程當中從容器中刪除本身或者其它的 Observer 是安全的,迭代器的 GetNext 方法會自動檢查容器是否被修改過,正確返回修改事後的容器的下一個元素。
通常的使用方式以下:
class MyWidget { public: ... class Observer { public: virtual void OnFoo(MyWidget* w) = 0; virtual void OnBar(MyWidget* w, int x, int y) = 0; }; void AddObserver(Observer* obs) { observer_list_.AddObserver(obs); } void RemoveObserver(Observer* obs) { observer_list_.RemoveObserver(obs); } void NotifyFoo() { FOR_EACH_OBSERVER(Observer, observer_list_, OnFoo(this)); } void NotifyBar(int x, int y) { FOR_EACH_OBSERVER(Observer, observer_list_, OnBar(this, x, y)); } private: ObserverList<Observer> observer_list_; };
base::ObserverListThreadSafe 至關於 base::ObserverList 的線程安全版本,經過 ObserverListThreadSafe.Notify 能夠調用註冊的 Observer 的某一個指定的方法,而且這個方法是在這個 Observer 所屬的線程上被調用, 所謂 Observer 所屬的線程就是指將 Observer 加入到 ObserverListThreadSafe 裏面的那個調用線程。爲了作到上述這一點,ObserverListThreadSafe 是經過 PostTask 到線程的消息循環來實現的,這也意味着跟 ObserverList 不一樣的是,Notify 和 Callback 被調用是異步的,而 ObserverList 是同步的,MemoryPressureListener 的內部實現就使用了 ObserverListThreadSafe。
MemoryPressureListener::MemoryPressureListener( const MemoryPressureListener::MemoryPressureCallback& callback) : callback_(callback) { g_observers.Get().AddObserver(this);}MemoryPressureListener::~MemoryPressureListener() { g_observers.Get().RemoveObserver(this);}void MemoryPressureListener::Notify(MemoryPressureLevel memory_pressure_level) { callback_.Run(memory_pressure_level);}// staticvoid MemoryPressureListener::NotifyMemoryPressure( MemoryPressureLevel memory_pressure_level) { TRACE_EVENT1("memory", "MemoryPressureListener::NotifyMemoryPressure", "level", memory_pressure_level); g_observers.Get().Notify(&MemoryPressureListener::Notify, memory_pressure_level);}