日本車商組「大聯盟」加快EV研發,鈴木傳結盟豐田陣營

為了加快電動車(EV)研發腳步,豐田汽車(Toyota Motor)、馬自達(Mazda Motor)以及豐田最大零件製造商Denso 於9月28日宣布成立名為「EV Common Architecture Spirit」(簡稱:EV C.A. Spirit)的電動車開發合資企業。而根據日媒最新報導指出,上述豐田主導的EV聯盟有望獲得鈴木(Suzuki)等多家日系車廠加盟。

日經新聞30日報導,鈴木計畫加盟豐田所籌組的EV聯盟,期望藉此加快EV的研發。據報導,鈴木計畫入股豐田主導的「EV C.A. Spirit」,預估持股比重將和馬自達、Denso一樣為5%,且除了玲木之外,豐田子公司日野(Hino)以及和豐田擁有資本關係、計畫在2021年開賣EV的SUBARU也考慮加盟,大發(Daihatsu)也有可能參一咖。

EV C.A. Spirit將開發小型車、轎車、SUV和輕型卡車電動車技術,豐田將持有90%的股份,馬自達、Denso分別擁有5%股權

豐田、馬自達目前都還沒販售純電動轎車。日經亞洲評論28日報導,馬自達、豐田分別計畫於2019年、2020年發表量產型電動車。

(本文內容由授權使用。圖片出處:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

印度政府宣布向Tata Motors採購1萬輛電動車

 

Thomson Reuters報導,印度政府9月29日宣布向Tata Motors Ltd採購1萬輛電動車、未來3到4年將用來分批汰換原有的公務車。Tata Motors將自11月起分兩階段供應電動車。印度政府約有50萬輛公務車。

英國最大汽車製造商Jaguar Land Rover(Tata Motors子公司)9月7日宣布,2020年起旗下所有新車都將具備電動或油電混合驅動選項。英國跟隨法國以及馬德里、墨西哥城和雅典等城市的抗空汙腳步,7月宣布將自2040年起禁止販售汽油和柴油新車。

livemint.com 9月30日報導,Tata第一批電動車將在今年11月交送給印度政府旗下合資企業「能源效率服務有限公司(Energy Efficiency Services Limited;EESL)」。

(本文內容由授權使用。圖片出處:public domain CC0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

為什麼人們熱愛張小龍?

  文/周掌柜

  來源:周掌柜(ID:zhouzhanggui525)

  “萬物之中,希望至美”當張小龍用這句話結束微信公開課演講的時候,很多人意猶未盡。

  不可否認,微信已經成為騰訊和用戶之間最大的公約數,張小龍更像一位“技術之神”守護着 10 億人每天都會打開的微信,守護着騰訊的未來。

  但另一側面,除了每年的公開課分享,用戶沒有太多的機會可以感知到他,也沒有人討論他。他說的語言不如李彥宏有未來,不如馬雲有格局,也不同於馬化騰更關注大變革,他不是超級大佬,但沒有人會忽略中國互聯網這位獨特的存在,甚至擁有更多的期待。他並不是一位專業的佈道者和演講者,但他的演講讓人充滿溫暖,多少打消了很多人對騰訊“老齡化“的慣性憂慮。

  張小龍 2018 年的演講時常斷斷續續,一會兒喝水,一會兒停下來思考,還有時候一個頭緒扎進去出不來。

  2019 年不太一樣,看得出是經過系統的梳理,對微信的即時通訊、小遊戲、小程序、訂閱號等都有系統的思考和闡述,近 4 萬字的內容傳播到朋友圈的時候,雖然大部分人都沒有認真看完,但從質樸的語言里都能感受到善良和誠意,而這,或許和張小龍心中對騰訊戰略挑戰的思考有關。

  不可否認,騰訊已經從成功的中年人進入對“老齡化“的恐懼之中,微信雖然仍如孩子般的讓人喜愛,但用戶已經失去了當初對其顛覆式創新的怦然心動。

  如果一定要概括小龍這次演講如此清新簡潔的系統性表達,背後也可以抽象出三個線索: 

  第一個關於“本質“,他幾乎對每一個橋段的敘述都從初心到溯源進而強化了對本質的認知,比如,他說:“朋友圈本質上是一個什麼,朋友圈其實開創了一個新的社交的場所,其實它不止是一個時間流。我把它比方成一個廣場“;

  第二個關於是“常識”,他不斷強調和用戶互動過程中受到的啟發,以及自己和微信團隊抵抗住了 KPI、情緒、眼前潮流的誘惑如何尊重常識,比如,他說:”可能在遠古的時候,如果人都是一個一個個體的話,他是沒有社交的,當人類成為一種群居的動物的時候,原始的社交就產生了“,這背後是對廣泛用戶需求的常識性理解;

  第三個關於“大邏輯”,演講中對大邏輯的闡述非常多,其中最大的邏輯我們姑且概括為”朋友哲學“,他說:“如果微信是一個人,它一定是你最好的朋友,你才願意花那麼多時間給它”。可見,小龍用“交朋友“的大邏輯理解對於微信細節功能的每一個決策。

  以上的這個提煉,其實從張小龍的世界觀中一定是違背了他娓娓道來的初衷,但從我們戰略顧問分析的角度,也許有助於形成可以借鑒傳承的樸素方法。但,張小龍反對的其實都是騰訊的組織現在廣泛存在的。

  當然,本文最大的一個初衷,或者說“原動力”是——理解張小龍的世界!在這個時間點重新品讀張小龍和微信,還有兩個特別有價值的視角:

  其一是對騰訊這家中國領先的超大型互聯網公司對智能時代的理解,小龍談 AI 並不多,卻可以清晰的感知到微信進化的 AI 思維;

  其二是對騰訊這家公司最近半年股價跌宕起伏的股價表現看,微信所代表的騰訊未來,更加值得關注。

  最新数字显示:微信的日登錄量超過 10 億,這確實是一個特別大的里程碑,這可能是國內歷史上第一款 App 有 10 億 DAU 的數量級。從這個角度上看,未來最有可能在互聯網領域抵抗頭條系進攻的不太可能是一個橫空出世的新產品或另一個小龍,最大概率是微信的重生。“神化“張小龍有點接近吹捧,可他依然和必然性需要具備一種神性的思考,才能引領微信的未來,引領用戶的預期,才有機會讓騰訊真正無縫進入下一個嶄新的智能時代。“技術之神”的回歸也是用戶對騰訊最大的期待。

  在以上粗線條的勾勒下,周掌柜戰略諮詢的多位合伙人,通過對最近幾年張小龍微信公開課的演講分析(尤其這次),結合我們近兩年對騰訊員工的深入訪談,努力感知他質樸、純粹和清澈的語言,力求給大家呈現張小龍更系統的 “微信哲學“以及對騰訊未來的思考和展望。客觀講,縱使他的”神性“思維涉及吹捧,但微信背後的騰訊確實需要重回神聖之地,需要類似小龍和更多小龍去點亮未來。

  1

  張小龍的微信哲學

  小龍最開始就解釋了“初心”和“原動力”的關係,他似乎總覺得“初心”無法表達自己,“原動力”更接近於客觀事實。的確,“初心”是未經進化的意願,“原動力”更符合從無到有、從邊緣到中心的成功路徑,這非常客觀。

  他的“原動力”很簡單:

  第一,堅持做一個好的,與時俱進的工具。

  第二個原動力是,“讓創造者體現價值”。

  在筆者 2016 年 6 月發表於英國《金融時報》中文網的文章《張小龍的星空里》,描述了一脈相承的線索:1969 年 12 月出生,傳說中最花心的射手座(這也可能是其藝術氣質的某種來源);1987 年到 1994 年在華中科技大學電信系從本科讀到碩;1997 年,也就是畢業 3 年後,他發布了 Foxmail 這款經典的郵箱軟件,經過多次叫賣不成之後(包括 15 萬被雷軍拒絕),2000 年以 1200 萬的價格賣給了博大公司;2005 年騰訊收購了 Foxmail,張小龍加入騰訊任廣州研發部總經理;此後又負責 QQ 郵箱,2010 年申請立項開發微信。

  這次演講中,張小龍同樣提到了延續以往經歷的思考,他說:“在微信上線之前的一年裡,我們把 QQ 郵箱做到了國內第一名,然後在郵箱裏面又做了很多嘗試,包括漂流瓶等等,包括我花了一年多的時間折騰的郵箱裏面的閱讀空間”。這個原動力確實一脈相承的,他在過去的十幾年裡依然秉承着兩個深植於內心的原動力,時間沒有讓他的原動力發生改變。他提到:“原動力其實應該是內心深處的一種認知和期望,它很強大,以至於可以堅持很久,克服很多困難去達到它”。隱約,我們也可以感知演講背後的十幾年沉默中,他並非是高歌猛進的成功,而是堅持和克服困難,包括騰訊組織的摩擦力,渴望更完美的自己。

  下錶我們通過與他提到的德國產品設計師迪特·拉姆斯(Dieter Rams)的對比,進一步分析他的微信哲學,如表:

  張小龍作為微信產品的主要功能和體驗設計師,受微軟、蘋果等品牌深刻影響,在中國互聯網行業獨樹一幟的形成超越商業的產品思維。

  他對好的產品的感知特徵是:

  1. 好的產品是自己會說話,盡可能產品說話,而不是解釋很多東西;

  2. 好的產品是最好的工具,關注於對錯的是非價值主張,而不是利益最大化;

  3. 好的產品圍繞用戶想象力感知,沒有標準答案;

  4. 選擇的權力交給用戶。不左右哪些服務是用戶有用的,公眾號就是這個思路,系統沒有推薦;

  5. 把用戶當朋友,而不是聽從驅使的群體。

  他的產品價值觀是

  1. 大人談利益,小孩子談對錯,利益出發,產品越走越偏,成為利益堆砌,失去產品最本質的東西;

  2. 所有努力一個目標,尊重用戶,尊重個人;

  3. 用外掛打了很多分,朋友誠信的角度是有問題的。外掛的行為破壞系統規則,讓規則失效了。尊重用戶。沒有騷擾信息,沒有聊天記錄保留,不做任何誘導行為;

  4. 故意感動人也是挺不尊重他的表現;

  5. 關注效率。小程序和小遊戲:用完即走,走了就回來。工具是完成任務,越高的效率越好;

  6. 超越短期功利主義:很多業界的產品經理會被自己所在的公司誤導。因為公司的目的是要流量要變現,所以大家的 KPI 就是如何產生流量如何變現,這是短視的。

  在產品體驗上,他特意提到了“尊重”兩個字,滲透着濃濃的用戶思維和人文思考;在品格方面,他提出不能“機會主義”,不為任何一個風口做事情。應該說以上一系列的梳理和提煉,都可以清晰感知他已經擁有了一套系統的方法論,這些方法論看似瑣碎,但其實更多的是在技術應用中融入到每一個功能裏面,充滿了產品主義的匠人精神。

  小龍對於小程序的表達,尤其經典,他說:“小程序是信息的組織方式和信息的載體,小程序最終的目的代表接觸到的任何事物背後的信息,以及對背後信息的訪問方式”。這種表達方式不僅犀利,而且可以感知到他對信息論、宇宙學的深刻理解,當今世界最前沿的物理學家,幾乎都是用信息的角度理解物質世界的,和他提到的陽明心學異曲同工,充滿思辨

  應該說這些微信哲學的提煉,是他多次演講中最精彩的部分,當然,單純從微信的角度看這是已經被證明的成功,可如果放大到整個騰訊或許微信的成功並沒有為全局注入面向未來的新動能。

  2

  張小龍潛在的思維局限

但這次演講並不完美。

  愛打遊戲的張小龍,對小遊戲的娓娓道來,讓人似乎回到了 20 年前那個深處陋室執着締造產品的年輕人。他說自己打“跳一跳”到 6000 多分的時候被系統命名為“無聊大師”,到 6000 多分得到稱號——立地成佛。非常生動和有趣。可背後則是微信對騰訊遊戲業務產生很少的貢獻。

  回歸他的初衷:小遊戲一定是放鬆的手段,讓自己平靜,普通人心跳加速和更緊張的狀態不太對。他懷疑 3000 多分用戶真實性的時候,特意準備了禮品,讓他們來辦公室當面打,見證這個遊戲里進步的速度有多快。進而他升華的評論說:”玩一個小遊戲是微信的正經事。跳一跳是微信做的一個實驗”。這樣鮮活的管理方法,其實很難讓人跳出來對應這位年薪億級,管理 10 億用戶應用的大老闆。這些都是遠遠偏離正統的管理思維的,也許和騰訊這家逐漸成熟的超級巨頭的管理文化也不太匹配。但這恰恰是微信團隊的競爭力之所在,他們很好的解決了創業思維和繁複管理架構的矛盾,一如既往的追隨用戶,解決問題。但不由得讓人們追問:微信思維究竟實際的對騰訊大組織有多少推動?

  這確實是一個真問題。

  坦率地說,這次張小龍的分享,我們結合過去來看,除了那種久違的親切和價值,也隱含折射出微信和騰訊某些潛在的風險和危機:

  · 遠離年輕人的風險:當初的小馬哥已經 49 歲,雖然公眾依然可以回憶起他當初做 QQ 的樣子,一如既往的對他表示尊敬。但確實騰訊的高管團隊,包括張小龍在內已經不在年輕。對於這次張小龍讓人鼓掌的分享,近乎完美,但也已經遠離了年輕人執念、莽撞的質感。小龍並沒有特意提到年輕人的需求,這或多或少讓人失望;

  · App成功觀念的固化:這種固化其實是是雙刃劍,前面我們也提到了傳承初心,但硬幣的另一面就是路徑依賴,特別是成功路徑依賴。比如對比今日頭條,騰訊微視跟隨的非常緊密,也做的非常好,但似乎一直沒有超越。頭條系的核心技術邏輯是 AI 的中心化,這和小龍多次提到的去中心化有着觀念的差異。即使我們無法評價其好壞,但智能時代的智能聚集是否天然具有中心化的要素?這些顛覆思維對微信的衝擊是顯而易見,註定也是風險;

  · 信息顆粒度認知的慣性思維:微信的訂閱號、看一看、搖一搖、小程序、小遊戲,實際上這些都是基於信息的文字、語音、圖片顆粒度的產品設計為主,但抖音的基礎小視頻,正在成為年輕人最大的趨勢。微信在短視頻這樣的創新信息顆粒度如何發力?5G 時代一定是一個迫切解決的問題。甚至包括基於 AI 和視頻結合的新型智能化圖形界面是什麼形態?能否想經典電影《HER》(她)裏面描述的是一種中央智能的人機對話?這關乎微信的長遠未來。

  · 企業微信認知遠離組織:小龍提到企業微信將來要做的一個方向,“相信對企業有很大的吸引力,因為它可以讓每個員工可以直接提供服務”,這很真誠,但本質還是 2C 思維的認知,企業更多的應用場景使用基礎都是組織協作,和單純 2C 思維的信息傳遞是有很大不同的,所以在企業場景中,阿里等公司能否後來居上,或者割據一方,都是微信面對的問題;

  · 缺少科技研髮型創新基因:這一點對於張小龍來講近乎苛求,但當微信發展到現在的規模,理論上是需要突破應用層面做很多通訊、AI、大數據、智能硬件、手機交互、Iot 連接技術平台等底層的研發級擴展,微信的想象力對於騰訊全局而言目前還僅僅停留在 App 的成功,這不免是一個遺憾。相對於騰訊最新的組織結構變革,微信是存在於研發創新矩陣邊緣的帝國,這是違背戰略常識的,為何不在微信內部去組織研發?

  · 更核心的一個挑戰在於小龍人本思想的通用性:之前我們把以人為本的社會形態定義為“碳基文明”,而把智能時代以信息技術定義為“硅基文明”,如果套用這個分類,微信是在碳基文明的人本主義和硅基文明的智能至上中間的一種形態。小龍定義為以人為核心的“工具”,人的“工具”。但未來趨勢是否硅基文明不需要工具,同時擁有生命?這個追問看似很《三體》,但確實一個進化的大邏輯。騰訊雖然在多個場景都在應用 AI,但顯然對於 AI 的用戶層面的創新和展現缺少新意。

  面對小龍精彩的演講,我們有了很多海闊天空和雞蛋里挑骨頭的觀點,這其實也違背周掌柜諮詢的多位合伙人對騰訊的尊重。這些註定不是眼前挑戰,談不上批評,更多是我們被帶入和感染之後的一種回饋。

  3

  張小龍與騰訊的未來

  在小龍的演講中,其實沒有提到太多大騰訊的未來,這應該不是他最關心的,但不可否認從若干語言線索中,流露不少真誠的看法。

  目前騰訊總體的情況是這樣的:

  1. 基於產業互聯網的未來方向其實具備高度的不確定性,不僅是對雲、智能、Iot 的理解上騰訊缺少經驗,對於所謂產業互聯網如何支撐巨頭進化,實際上並沒有給出具體的業績支撐。而產業互聯網和消費級互聯網最核心的一個差別是“矩陣式研發”,這和互聯網靈活的基於用戶使用數據迭代邏輯完全不同。還涉及到嚴密系統的研發管理,華為、平安等技術創新能力強的公司都是用了 10 年甚至 20 年的時間構建的技術管理體系,騰訊看似一直在互聯網的前沿,但和科技研髮型企業差距甚大;

  2. 最新調整的戰略架構,明顯是從創業早期的野蠻生長“賽馬”體系向組織協作演進。這是必須和迫切的,一位騰訊高管訪談中表示:騰訊內部重複競爭、資源浪費、部門牆嚴重到讓人不敢面對,一個支付功能背後曾有 4 個團隊同時在做,應用於不同場景,而且研發的基礎框架內部都沒有開源共享。這個問題很多人在內部論壇問馬化騰,他也僅僅給出了:“重複創新有利於把握機會”這樣一個過於簡潔的回答,依然是互聯網思維的“找機會”邏輯。一個遊戲業務線,很多遊戲定位在 PK 競爭引導下高度同質化,缺少創意。創業組織架構中,對領軍者的億級巨額激勵,對應輔助團隊成員的杯水車薪,長期帶來了一種不利於組織協作的激勵不平衡。這些讓騰訊兩個部門之間的溝通甚至比騰訊與其他公司溝通還要繁瑣,甚至出現很多業務不願意在公司內採購服務的極端現象。而騰訊的 CEO 馬化騰和總裁劉熾平是兩條線平行管理公司,這從某種程度上體現了大股東的資本意志,從其組織變革中也可以清晰的嗅到投行思維,說“金融綁架”騰訊是缺少根據的,但是多位香港金融背景高管帶給騰訊的肯定不是技術思維,這可以肯定。於是,註定對於本來就應該技術驅動的騰訊來說,無異於閹割了其技術引領的可能性,這也被一些持批評態度的媒體認為是 Tony(張志東 )之後騰訊一直沒有 CTO 的原因;

  3. 面對頭條系在 AI 算法新聞平台以及抖音短視頻平台,還有後面一系列火山視頻等競爭的格局下,騰訊衝鋒的團隊似乎少了歷史上攻城拔寨的銳氣,其投資的騰訊系中也始終沒有出類拔萃的“小頭條”出現。這導致騰訊一直沒有能真正在 AI 平台和短視頻這兩個戰略機遇點上翻盤,對於關心騰訊的人來說,前沿的引領性、技術的引領性是遠遠大於遊戲業務政策起伏的影響,騰訊的“命根子”正在被挑戰,而且看不到逆轉的跡象。雖然 2019 年出騰訊宣布成立技術委員會,下決心對組織架構下狠手,但背後缺少的功課非一日之功,能找到統領技術研發框架的人才也並不容易,這還不算技術管理和組織管理可能聘請外部諮詢公司重構的難度。這些都是小龍這次質樸演講的大背景,也很難讓我們不對其創新思維賦予更高的意義。

  小龍有不少有趣的回顧,多少與此相關:

  其一關於“眼球經濟”,他說:“停留時長讓我聯想到 2000 年左右,當時互聯網剛起來,流行的一個詞叫眼球經濟。所有的網站目標都是要獲取盡可能多的眼球注意力”。進而評論道:“我們期望並不是我們要獲得更多的現金的回報,而是現狀是這裏面真正高質量原創的遊戲還不是特別多“。

  其二談到 KPI,他說:”用戶迷信保健品,我們就推保健品的文章。如果從 KPI 的角度,這樣是最容易完成 KPI 的。但是如果我們推薦給用戶新的知識,用戶會離開的。因為惰性是人共有的特性。沒有人願意主動去學習新知識,去傷腦筋啊“。

  雖然,對於這些誠懇的回顧,我們去過度聯想顯得不太厚道,但客觀講,KPI 正是騰訊這艘大船這些年航速變慢的原因,騰訊似乎悄悄的從對用戶負責開始向股民負責,失去了想象力和進攻性;而眼球經濟,正是目前騰訊面對低迷股價所反思的,一位騰訊前公關負責人說:“在和阿里打嘴仗的時候,我們投資了那麼多的媒體,但是關鍵時刻竟然不知道如何回應。馬化騰的很多發言稿都是臨時抱佛腳,頭一天晚上改來改去,也談不上戰略的溝通和傳承”。這些都是騰訊不為人知的另一個側面,如果“KPI 綜合症”和“眼球經濟短視症”得不到徹底消除,對於通過近乎美式公司的組織架構以及麥肯錫式的戰略方向,疊加投行式的精細化財務管控,騰訊是否有更美好的未來? 

  而不可避免的,騰訊的挑戰和壓力也必然傳達到微信團隊身上。細心的用戶已經發現從騰訊股價 2018 年下半年收到挑戰之後,微信的朋友圈廣告質量降低,並且明顯增多。這雖然依然在可以理解的範圍內,但不可否認微信承載了騰訊這艘大船的業績壓力。而正如小龍所言,小遊戲低於預期,那麼微信如何推動騰訊傳統第一大遊戲收入來源重拾增長呢? 

  過度的對比張小龍和馬化騰,並不厚道。就像對比馬雲和阿里 CTO 誰更懂技術一樣。但這背後的一個很大的問題在於:騰訊到底是要培養出更多的張小龍?還是培育出更多的賺錢業務?而或通過培訓新的張小龍帶來新利潤?

  目前騰訊並沒有清晰的戰略指向,外界看到的還是一家強悍、偉大但已經沒有傳奇的公司,內部人看到的是 KPI 和業績壓力下的低效以及大公司病。沒有傳奇,就已經低於大部分用戶和公眾對騰訊的預期,而孵化傳奇一定不是簡單的“產業互聯網”和“組織架構變革”能達到的,沒有一個圍繞技術天才的創新氛圍,騰訊理論上和美國的戴爾、惠普沒有什麼兩樣。說的誇張一點——“技術之神”能否重新照耀這家公司?讓這家成功的公司從平庸的成功走向新的傳奇!這背後是無數人的疑問和期待。

  無論如何,張小龍表達的“善良”價值觀,留給那天晚上幾十萬讀者口有餘香的溫暖。如果能同時激勵整個騰訊的大船在“善良”的軌道上航行得更遠,這一切都是美好的。“每天都有 5 億人說我們做得不好,每天還有 1 億人想教我怎麼樣做產品”,小龍這句話的背後,切實說明了用戶的熱情和心意。

  為什麼人們熱愛張小龍?因為他在我們身邊,一直默默善良的陪伴。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

豐田看好印尼製造,將投資 20 億美元在印尼開發電動車

印尼海洋事務統籌部(Coordinating Ministry for Maritime Affairs)27 日宣布,日本豐田汽車(Toyota)計劃在未來 4 年內投資 20 億美元,在印尼開發電動車款,首先以混合動力汽車(Hybrid vehicle)為起點,以加速應對全球汽車產業的電動化浪潮。

路透社週四報導,豐田執行董事兼社長豐田章男在聲明中表示,2019 年至 2023 年期間,將逐步增加投資對印尼的投資,總額上看 28.3 兆印尼盾(約 20 億美元)。他指出,受惠於印尼政府的政策推動,豐田將印尼視為重要的電動車投資首選地,公司將分階段逐步投資,以遵循政府的電動車發展方針。

印尼是東南亞地區的最大經濟體,擁有豐富的鎳礦資源,這是電動車鋰電池的主要材料之一,成為吸引外國汽車製造商的拉力。此外,印尼政府先前也宣布,將給予電池和電動車製造商減稅優惠,並與其他國家簽訂關稅減免協議,積極推動電動車產業發展。

目前印尼已是東協第二大汽車生產國,僅次於泰國。韓媒《BusinessKorea》6 月 25 日報導,受中國車市步入寒冬和產能嚴重過剩影響,南韓現代汽車(Hyundai Motor)決定關閉位於北京郊區的北京 1 號工廠,並將生產業務從中國遷至印尼,加速打入廣大的東南亞市場。

《新加坡商業評論》(Singapore Business Review)2 月 15 日報導,國際信評機構惠譽旗下的市場研究調查機構 Fitch Solutions 預測,印尼汽車年產量將在 2019 年達到 5.8% 的成長率,主要是源於全球電動車和 SUV 的需求不斷增加。

除此之外,印尼汽車產量也受到菲律賓、沙烏地阿拉伯、泰國和越南等市場帶動,預估合計銷量將成長 7.8%。2017 年,上述國家佔印尼整體汽車出口比重分別為 25.7%、7.4%、13.8% 和 6.2%。

(本文內容由 授權使用。首圖來源: CC BY 2.0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

不看好歐洲純電動車市場,BMW 高層:監管機構想要,但沒有顧客真的需要

電動車已成為多數車商的發展趨勢,然而近日在慕尼黑舉行的 NextGen 活動期間,BMW 執行董事 Klaus Fröhlich 卻語出驚人的表示,他認為電動車轉型被過度吹捧,實際上消費者對這些車輛根本沒有需求。

考量到 BMW 日前才宣布將加速純電動車發展計畫,Fröhlich 的發言看似大膽,但其實並非沒有道理。據了解,這項發言主要是針對遊說團體 Transport and Environment 日前公開譴責歐洲車商的回應。

該篇報告 T&E 引述 EEA 的最新排氣數據,指出歐洲在電動車銷售明顯落後中美兩國,主要原因是市場純電動車的選擇和可用性都受侷限,而 T&E 認為,這是因為歐洲車商故意推遲純電動車的銷售計畫,好最大程度提高燃油車的利潤,「在環境方面,車商只會做法律規定他們要做的事情」。

對這項指控,Fröhlich 表示,他認為 T&E 忽略了最重要的問題:那就是歐洲客戶實際上並不想買純電動車。

Fröhlich 解釋,與美國人為不同目的駕駛不同車輛的習慣不同,歐洲人通常是「單車家庭」,車庫中只會有一輛車使用,因此他們並不願完全依賴純電動車,不應該拿美國的市場情況來比較。

除此之外,還有充電基礎設施的普及問題,Fröhlich 認為,柴油引擎至少還有 20 年、汽油引擎至少還有 30 年,即使是俄羅斯、中東、中國中西部地區這些市場,在缺乏基礎建設下,燃油引擎都至少還有 10~15 年時間。

「純電動車只適用中國、美國加州市場,至於其他地方,有更大電池的混合式動力車應該是更好的選擇。」

除了基礎設施普及,Fröhlich 也指出,在電池原始材料,純電動車的成本要遠高於燃油或混合動力車,隨著未來市場對電池原料的需求增加,一旦供應鏈無法支持,那麼價格可能只會有增無減。

Fröhlich 表示,如果政府能提供許多協助,BMW 可生產足以淹沒歐洲的百萬台純電動車,但在他看來,歐洲人並不想買這些東西,「監管機構想要純電動車,但沒有顧客真的需要。」

(合作媒體:。首圖來源: CC BY 2.0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

他本和牛頓雙宿雙飛,卻因羞澀錯失物理第二魔王威名

  來源:我是科學家 iScientist

  有這麼一個流傳甚廣的段子,說流行歌手林俊傑要是不努力,就只能回家繼承百億家產。

  但實際上歷史上還有比這更誇張的真實故事。

  他的父親是德文郡公爵家族小兒子,母親是肯特公爵的女兒,可謂富甲一方,真·“不好好科研,就只能繼承家產成為首富“。

  然而,他卻靠“牛頓之後英國最偉大的科學家”為人所知,如果他要和人攀比,根本輪不到要拼家產的地步。

卡文迪許

  但這僅僅是我們主角卡文迪許故事的冰山一角,他作為一名偉大科學家的完全體還要等到百年後麥克斯韋的意外發現才被世人所了解。

  歐姆定律、庫倫定律、電勢、電場,這些成就都悶在了他的手稿里,如果全都公開發表,那卡文迪許可能就是繼牛頓之後又一個大魔王級的人物。

  但如果終究是如果,現實就是卡文迪許錯失了與偶像牛頓在物理課本里“雙宿雙飛”的機會。而這一切緣起於他的羞澀。

卡文迪許

  一場 18 世紀的英國頂尖學術聚會上,卡文迪許身上穿着的都還是一套起皺的褪色西裝,外加一頂卷邊帽,靦腆地站在角落。

  不了解的人也許難以想象,眼前這個的衣着樸素的人竟是個百萬富翁,還是個跨越化學與物理兩界的科學奇才。

  要說是奇才,和他同時代的科學家可不覺得。因為在他們眼裡,卡文迪許並沒有那麼偉大。但在後人看來,他隱藏起來的科研成果才令人驚詫。

卡文迪許

  100 多年後,麥克斯韋發現他遺留下 20 多捆從未面世的物理、化學研究手稿。

  庫侖定律、歐姆定律、介電常數等後人才提出的概念,赫然出現在筆記本上。一些時隔幾百年才提出的定律也早在 18 世紀就被卡文迪許證明了出來。

  他甚至被懷疑是擁有現代先進物理知識穿越者。

詹姆斯·克拉克·麥克斯韋

  麥克斯韋用了 5 年的時間把這些資料整理成書。而這些疑似穿越的產物,才消除了人們對於卡文迪許的誤解。

  原來卡文迪許的古怪性格早已在科學界名聲昭著。他生性羞澀,幾乎不與人交談,甚至連自己的研究成果也羞於發表。科學怪人的一生只追求自己科研的爽快,也讓世人對他的景仰晚了 100 年。

  人們給他貼上不合群、內向、沉默寡言、古怪等標籤。童年的成長曆程在他身上刻下了這一個個深刻的烙印。

卡文迪許

  卡文迪許出生在一個英國貴族家庭,兩歲時母親就去世了。身為勛爵的科學家父親一手帶大了他和弟弟,卻很少有時間給予陪伴。

  作為彌補,父親實驗室里各種科學儀器成了卡文迪許的玩具。而忙碌的父親有時不得不帶上他出席倫敦皇家學會等科學家聚會的場合。

卡文迪許家裡的餐廳

  卡文迪許的科學啟蒙也就由此開始。童年的經歷開拓了他的科學視野,所以他從小就有了不錯的科學基礎。

  但硬邦邦的儀器取代了親情的關懷,卡文迪許幾乎沒有機會能夠與人交流。這讓他性格變得內向、沉默寡言,甚至疑似患有自閉症。

  他幾乎完全喪失了社交能力,但同時他也獲得了強大的思考能力。

卡文迪許

  這樣古怪的性格也讓卡文迪許越發沉迷於科研工作。他 18 歲考上了劍橋大學,學習了四年數學。

  但就在即將拿到畢業學位的前夕,他卻退學了。理由是對最後的考試中,關於神學知識的測試部分不滿。於是他寧願捨棄畢業學位而任性地退學。

  出於家境的優越,放棄了學位的卡文迪許並沒有因此而失去學習機會。這反而讓他不止局限於學校的數學教學,他又學習了深感興趣的物理和化學。

  在離開劍橋后不久,他追隨父親的影子,加入了皇家學會。融入科學界的圈子中,他才找到自己的價值,在深耕真理中做出影響世界的偉大貢獻。

英國皇家學會

  空氣的主要成分是氧氣、氮氣和二氧化碳。人類直到 18 世紀才發現,原來空氣的成分不止這些。

  卡文迪許在研究中發現了一種十分微量的惰性氣體。

  他用電火花消耗空氣中的氮氣時,出現了一些小氣泡。

  奇怪的是,無論實驗重複多少遍,最後都還會剩餘一些小氣泡不能被氧化。無論加入什麼試劑,這種氣泡都沒有消失。

  於是卡文迪許得出結論,空氣中除了氮氣和氧氣之外,還存在一種“濁氣”。這種“濁氣”非常穩定,而且總量不超過全部空氣的1/120。

  但這種氣體具體是什麼成分,卡文迪許就沒有繼續研究下去了。直到一百多年後,才有科學家依據卡文迪許當初的實驗,揭開了“濁氣”的真面目。

  物理學家瑞利重複卡文迪許當年的實驗得到小氣泡,測定出這種氣體的密度比氮氣大。

  化學家萊姆塞重新設計了一個新實驗,用分光鏡檢查后給其中一種新元素命了名。

  這是化學性質極其穩定的稀有氣體中的一種,氬氣。

  而位於元素周期表第一位的氫,也是在卡文迪許的研究下被人們認知。當時科學命名法還沒有誕生,普遍常見的氣體也只有俗名。

  比如氧氣在當時被稱為“消炎氣體”。而一種“易燃氣體”也引發了許多科學家的火熱研究。

卡文迪許也摻了一腳,還難得向皇家學會提交的一篇研究報告《人造空氣》。

  他用鐵、鋅等活潑金屬和稀硫酸反應,發現反應會產生一種氣體。這種氣體和空氣混合後點燃會爆炸,因此被叫做“易燃氣體”。它和氧氣相互反應還能生成液態的水。

  在當時,人們還以為水是一種元素,不知道這是氫和氧的化合物。

  卡文迪許的實驗其實就是現代高中化學中都學過的置換反應。而生成的氣體就是氫氣。

  現在人們對於氫氣的性質已經非常熟悉。但在那個時候,繁多的反應卻像一扇扇從未打開的大門,吸引了天生好奇心強烈的科學家們。

氫氣球爆炸

  卡文迪許跨域廣泛,除了化學之外,他對物理、天文、氣象等科學領域也有所研究。其中牛頓自然哲學觀點就對他產生了深遠的影響。

  地球有多重?自從牛頓發現萬有引力定律之後,這個問題似乎已經攻破在望。解決問題的關鍵在於計算出“萬有引力常數”。

  理論上來說,可以直接測量地面上兩個已知質量物體之間的引力求得。

  但實際上這個引力數值十分微小,測量起來非常困難。

  許多科學家為此設計了各種奇怪的模型進行計算,但始終難以攻克。

  在牛頓的理論影響下,卡文迪許從十幾歲就開始研究這個問題。

卡文迪許設計的扭稱模型

  他在劍橋大學的學習中請教到了一種巧妙的“扭稱”方法。於是他自己也設計了一個能觀察到微小力變化的模型。

  他在一根細長桿的兩端分別裝上一個小鉛球,再用石英絲橫吊著鉛球。

  如果用兩個大一些的鉛球靠近,由於產生引力,小鉛球就會發生擺動。

  而石英絲也會跟隨扭動,這時只要測量出石英絲的扭轉程度,就可以求出引力。

  為了排除干擾,他專門在一間屋子里進行實驗,還用價格昂貴的望遠鏡在屋子外觀察。

  但是當時實驗條件差,他只能通過肉眼觀察判斷石英絲的扭轉程度。

  然而引力的作用程度實在是微乎其微,眼看成功近在眼前,實驗結果卻無從得到。卡文迪許的實驗只好卡在半途。

  直到一天,他在路上看到小孩在玩鏡子反射太陽光的遊戲。小小的太陽光反射點映照在地上到處跳動。

  這讓卡文迪許大受啟發。他立馬回到實驗室改進了自己的扭稱裝置。

  他把在裝置上增加了一面鏡子,用反射到刻度線上的光線度量石英絲的扭動。這樣一來,石英絲的靈敏度大大提高,再通過簡單的力的計算就得出了引力的大小。

  這個堪稱上帝之手的扭稱實驗掂量起地球的質量,牛頓或許也因此安息了。

  5. 976×10^24 千克,也就是大約 60 萬億億噸。卡文迪許花費了四十多年的時間才得出這一個數值,最終終結了這個萬有引力難題。他被譽為“第一個稱量地球的人”。

  以上大體就是當時人們能了解到的卡文迪許成就了,至於為什麼將其他發現藏着掖着,只要跟他有過些許交流就能理解。

  孤僻的性格讓卡文迪許全心專註於科研實驗。濃厚的學術氛圍是對他不善言辭性格的極大寬容。他從來不主動結交朋友,對異性更是越發羞澀。

  甚至在家裡,大部分時候和女傭都是靠傳紙條來進行交流的。所以他終身都沒有結婚。

  而作為大富豪,卡文迪許對於錢財和交際卻完全沒有概念。

  有一次,一位工匠為他粉刷房間,過後他忘了給工匠付工錢。好友是在看不下去,告訴了他這件事。

  卡文迪許大吃一驚,連忙寫了一張兩萬英鎊的支票,還詢問夠不夠。這在當時幾乎是工匠十年的薪酬了,而他卻毫不在意。

  對於社交活動,卡文迪許是本能地抗拒的,除了每周一次的皇家學會聚會。而在宴會上,他也只是躲在角落默默地聆聽其他科學家的發言。

  在這裏他不需要說話,卻能收穫到最前沿的科學觀點和想法。但別人卻很難從他口中得知他深邃的思想和正在進行的研究。

  一位比較了解他的友人調侃,要想聽到卡文迪許高明的見解,就不能再宴會上和他有任何交流,否則他會羞澀地立馬逃跑。

  人們大都只知道卡文迪許稱量地球的成就,但他最成功的預言還埋藏在他的手稿中。他對電的研究甚至直接證明了牛頓對未來自然科學的設想。

  他原本打算用這篇文章當做牛頓提出萬有引力的《原理》中的續篇。但卻因為他羞於發表,而失去了與牛頓同享榮譽的機會。

曾經在皇家學會上發言的牛頓

  另外,他最早證明了電荷之間的相互作用力應該與距離的平方呈反比關係。

  在 1772-1773 年間,他作了個雙層同心球實驗,第一次精確測出電作用力與距離的關係,指數偏差不超過 0.02。

  後來法國人庫倫通過實驗驗證了他的發現,從此關於電荷間的受力規律被稱作庫倫定律。

夏爾·奧古斯丁·庫侖

  他還第一個提出了電勢的概念,指出了電勢與電流的正比關係。

  由於當時沒有測定電流的儀器,卡文迪許就把自己的身體當做實驗儀器。根據身體的麻木感覺來估計電流的強弱,發現了導體兩端的電勢(差)與通過它的電流成正比。

  這也就是我們物理課本電學章節中的歐姆定律。

格奧爾格·西蒙·歐姆

  後來麥克斯韋通過整理,才出版了卡文迪許手稿中關於電學的研究。而通過他本人的發表和後人的搜尋,才挖掘出卡文迪許冰山一角的研究成果。除了手稿之外,他還有多少不為人知的研究,我們也就不得而知了。

骨子里的自閉也鑄造了卡文迪許孤獨的一生。

  直到將死之際,他還刻意把傭人打發走,讓她在某個時間點再回來。

  而回來時發現,卡文迪許已經死在了床上。極度的羞澀讓他連死亡都不想別人看見。

  默默無聞的卡文迪許為科學事業做出了偉大貢獻,一生中卻沒有得過什麼獎。

  後來,麥克斯韋為紀念這位隱蔽的偉大科學家,建立了以他命名的實驗室。迄今為止,卡文迪許實驗室已經培養出 20 多位諾貝爾物理獎的獲得者。

  原本用沉默掩蓋科研成果,他捨棄了與牛頓齊名物理學史的機會。但他在角落裡散發出來的萬丈光芒卻無法收斂。假設卡文迪許的手稿當初沒有被藏着,我們現在課本里的可能就不止有牛頓力學三定律,或許還有卡文迪許電學定理一、定理二……

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

坑~夏令時冬令時引發的時間換算問題

 

起因

最近接觸到一些國外的項目,由於國內外有時差這個東西,對於某些基礎數據存到數據庫的時候需要記錄時間,為了方便,這裏採用了時間戳(int或者timestamp)記錄。由於時間戳全球都是一樣的,需要的時候根據時區進行轉換就能夠拿到當地的時間。

嗯~ o(* ̄▽ ̄*)o,這樣看起來確實沒什麼毛病。眾所周知,一天有24小時,換算成秒就是:24*60*60=86400秒。

然而,我在某次使用 MySql 的 FROM_UNIXTIME 發現一個問題,兩個時間相差86400秒,但是格式化之後卻不是相差一天!!!

假設北京時間2019年11月25日 12:00:00,對應的時間戳是:1574654400,照理說這個時間戳加上一天86400秒,理論上就是北京時間2019年11月26日 12:00:00,事實上確實如此,國內的話這麼算確實沒什麼問題,但是如果是國外時區的話,那可能會出問題。

由於國外部分國家有夏令時冬令時之分(具體下面會細說),直接加上86400秒可能會有問題。

感興趣的可以拿1572764400(太平洋時間2019-11-03 00:00:00,單位:)這個時間戳驗證下

拿代碼演示下:

PHP:

<?php

echo "PST時區的時間\n";
date_default_timezone_set('PST8PDT');
echo date('Y-m-d H:i:s',1572764400);
echo "\n";
echo date('Y-m-d H:i:s',1572764400+86400);
echo "\n";

//換個時區
echo "換成上海時區看看\n";
date_default_timezone_set('Asia/Shanghai');
echo date('Y-m-d H:i:s',1572764400);
echo "\n";
echo date('Y-m-d H:i:s',1572764400+86400);
echo "\n";

運行結果:

PST時區的時間
2019-11-03 00:00:00
2019-11-03 23:00:00
換成上海時區看看
2019-11-03 15:00:00
2019-11-04 15:00:00

明明是同一個時間戳,都是加上86400(一天),為什麼在上海這個時區是第二天,而在PST(美國太平洋時區)只加了23小時?神不神奇!意不意外!

為了弄清楚這個問題,首先得先了解下什麼是夏令時,什麼是冬令時

 

夏令時

夏令時,表示為了節約能源,人為規定時間的意思。也叫夏時制,夏時令(Daylight Saving Time:DST),又稱“日光節約時制”和“夏令時間”,在這一制度實行期間所採用的統一時間稱為“夏令時間”。

一般在天亮早的夏季人為將時間調快一小時,可以使人早起早睡,減少照明量,以充分利用光照資源,從而節約照明用電。各個採納夏時制的國家具體規定不同。目前全世界有近110個國家每年要實行夏令時。[1]

 

冬令時

有夏令時就會有冬令時。高緯度和中緯度的許多國家在夏季到來前,把時針撥快一小時,新的時間就是夏令時,到下半季秋季來臨前,再把時針撥回一小時,即形成冬令時。 [2] 

 

夏令時和冬令時的影響

拿美國來說,美國各個地區的時間都不同,不像中國一樣統一使用北京時間,美國一般以三月份第二個周日凌晨兩點當成夏季的開始,十一月份第一個周日的凌晨兩點當成冬季的開始。

所以在每年的三月份第二個周日凌晨兩點過後,時間就會往前調快一個小時;同理,十一月份第一個周日把這一個小時調回來

你也可以理解成美國那邊,一年裡面有一天只有23小時(夏天開始那一天),有一天有25小時(冬天開始那一天),其他時間每天都是24小時。

所以你會發現,夏天的時候,中國的北京時間(東八區)與美國太平洋時區(西八區)的時差是15小時,而到了冬天卻變成16小時

 

解決方案

回到開頭那個問題,如果我們想直接算第二天,直接加上86400(一天)可能在其他國家就會有我上面那個夏令時和冬令時時間換算的問題,要如何避免呢?首先能夠確定的是,直接加上86400是不可取的,如果加上一天能否行得通

PHP:

<?php

echo "PST時區的時間\n";
date_default_timezone_set('PST8PDT');
echo date('Y-m-d H:i:s',1572764400);
echo "\n";
echo date('Y-m-d H:i:s',1572764400+86400);
echo "\n";

echo "--------------------------\n";
echo date('Y-m-d H:i:s',1572764400);
echo "\n";
echo date('Y-m-d H:i:s',strtotime('+1 day',1572764400));
echo "\n";

運行結果:

PST時區的時間
2019-11-03 00:00:00
2019-11-03 23:00:00
--------------------------
2019-11-03 00:00:00
2019-11-04 00:00:00

可以看出,不直接加上86400,直接在日期上加上一天是完全沒問題的。

JavaScript:

var date = new Date(1572764400*1000);
date.setDate(date.getDate()+1);
var timestamp = Math.round(date.getTime()/1000);

注意:JS的時間戳是毫秒!!!

 

結論

在經濟全球化快速發展的今天,在軟件開發的過程中,盡量養成習慣,由於夏令時和冬令時不是固定的,開發在時間計算上應該慎用86400進行加減運算,時間計算請直接對日期進行加減,展示時間給用戶看的時候盡量結合當地時間,結合夏令時和冬令時計算出準確的當地時間,避免產生不必要的分歧。

 

參考:

[1]. 

[2]. 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

.NET高級特性-Emit(2)類的定義,.NET高級特性-Emit(1)

  在上一篇博文發了一天左右的時間,就收到了博客園許多讀者的評論和推薦,非常感謝,我也會及時回復讀者的評論。之後我也將繼續撰寫博文,梳理相關.NET的知識,希望.NET的圈子能越來越大,開發者能了解/深入.NET的本質,將工作做的簡單又高效,拒絕重複勞動,拒絕CRUD。

  ok,咱們開始繼續Emit的探索。在這之前,我先放一下我往期關於Emit的文章,方便讀者閱讀。

  《》

一、基礎知識

  既然C#作為一門面向對象的語言,所以首當其沖的我們需要讓Emit為我們動態構建類。

  廢話不多說,首先,我們先來回顧一下C#類的內部由什麼東西組成:

  (1) 字段-C#類中保存數據的地方,由訪問修飾符、類型和名稱組成;

  (2) 屬性-C#類中特有的東西,由訪問修飾符、類型、名稱和get/set訪問器組成,屬性的是用來控制類中字段數據的訪問,以實現類的封裝性;在Java當中寫作getXXX()和setXXX(val),C#當中將其變成了屬性這種語法糖;

  (3) 方法-C#類中對邏輯進行操作的基本單元,由訪問修飾符、方法名、泛型參數、入參、出參構成;

  (4) 構造器-C#類中一種特殊的方法,該方法是專門用來創建對象的方法,由訪問修飾符、與類名相同的方法名、入參構成。

  接着,我們再觀察C#類本身又具備哪些東西:

  (1) 訪問修飾符-實現對C#類的訪問控制

  (2) 繼承-C#類可以繼承一個父類,並需要實現父類當中所有抽象的方法以及選擇實現父類的虛方法,還有就是子類需要調用父類的構造器以實現對象的創建

  (3) 實現-C#類可以實現多個接口,並實現接口中的所有方法

  (4) 泛型-C#類可以包含泛型參數,此外,類還可以對泛型實現約束

  以上就是C#類所具備的一些元素,以下為樣例:

public abstract class Bar
{
    public abstract void PrintName();
}
public interface IFoo<T> { public T Name { get; set; } } //繼承Bar基類,實現IFoo接口,泛型參數T
public class Foo<T> : Bar, IFoo<T>
  //泛型約束
  where T : struct {
//構造器 public Foo(T name):base() { _name = name; } //字段 private T _name; //屬性 public T Name { get => _name; set => _name = value; } //方法 public override void PrintName() {
    Console.WriteLine(_name.ToString()); }
}

  在探索完了C#類及其定義后,我們要來了解C#的項目結構組成。我們知道C#的一個csproj項目最終會對應生成一個dll文件或者exe文件,這一個文件我們稱之為程序集Assembly;而在一個程序集中,我們內部包含和定義了許多命名空間,這些命令空間在C#當中被稱為模塊Module,而模塊正是由一個一個的C#類Type組成。

 

 

 

   所以,當我們需要定義C#類時,就必須首先定義Assembly以及Module,如此才能進行下一步工作。

二、IL概覽

   由於Emit實質是通過IL來生成C#代碼,故我們可以反向生成,先將寫好的目標代碼寫成cs文件,通過編譯器生成dll,再通過ildasm查看IL代碼,即可依葫蘆畫瓢的編寫出Emit代碼。所以我們來查看以下上節Foo所生成的IL代碼。

  

 

 

   從上圖我們可以很清晰的看到.NET的層級結構,位於樹頂層淺藍色圓點表示一個程序集Assembly,第二層藍色表示模塊Module,在模塊下的均為我們所定義的類,類中包含類的泛型參數、繼承類信息、實現接口信息,類的內部包含構造器、方法、字段、屬性以及它的get/set方法,由此,我們可以開始編寫Emit代碼了

三、Emit編寫

  有了以上的對C#類的解讀和IL的解讀,我們知道了C#類本身所需要哪些元素,我們就開始根據這些元素來開始編寫Emit代碼了。這裏的代碼量會比較大,請讀者慢慢閱讀,也可以參照以上我寫的類生成il代碼進行比對。

  在Emit當中所有創建類型的幫助類均以Builder結尾,從下錶中我們可以看的非常清楚

元素中文 元素名稱 對應Emit構建器名稱
程序集  Assembly AssemblyBuilder
模塊  Module ModuleBuilder
 Type TypeBuilder
構造器  Constructor ConstructorBuilder
屬性  Property PropertyBuilder
字段  Field FieldBuilder
方法  Method MethodBuilder

  由於創建類需要從Assembly開始創建,所以我們的入口是AssemblyBuilder

  (1) 首先,我們先引入命名空間,我們以上節Foo類為樣例進行編寫

using System.Reflection.Emit;

  (2) 獲取基類和接口的類型

var barType = typeof(Bar);
var interfaceType = typeof(IFoo<>);

  (3) 定義Foo類型,我們可以看到在定義類之前我們需要創建Assembly和Module

//定義類
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Edwin.Blog.Emit"), AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("Edwin.Blog.Emit");
var typeBuilder = moduleBuilder.DefineType("Foo", TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit);

  (4) 定義泛型參數T,並添加約束

//定義泛型參數
var genericTypeBuilder = typeBuilder.DefineGenericParameters("T")[0];
//設置泛型約束
genericTypeBuilder.SetGenericParameterAttributes(GenericParameterAttributes.NotNullableValueTypeConstraint);

  (5) 繼承和實現接口,注意當實現類的泛型參數需傳遞給接口時,需要將泛型接口添加泛型參數后再調用AddInterfaceImplementation方法

//繼承基類
typeBuilder.SetParent(barType);
//實現接口
typeBuilder.AddInterfaceImplementation(interfaceType.MakeGenericType(genericTypeBuilder));

  (6) 定義字段,因為字段在構造器值需要使用,故先創建

//定義字段
var fieldBuilder = typeBuilder.DefineField("_name", genericTypeBuilder, FieldAttributes.Private);

  (7) 定義構造器,並編寫內部邏輯

//定義構造器
var ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.Standard, new Type[] { genericTypeBuilder });
var ctorIL = ctorBuilder.GetILGenerator();
//Ldarg_0在實例方法中表示this,在靜態方法中表示第一個參數
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Ldarg_1);
//為field賦值
ctorIL.Emit(OpCodes.Stfld, fieldBuilder);
ctorIL.Emit(OpCodes.Ret);

  (8) 定義Name屬性

//定義屬性
var propertyBuilder = typeBuilder.DefineProperty("Name", PropertyAttributes.None, genericTypeBuilder, Type.EmptyTypes);

  (9) 編寫Name屬性的get/set訪問器

//定義get方法
var getMethodBuilder = typeBuilder.DefineMethod("get_Name", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.SpecialName | MethodAttributes.Virtual, CallingConventions.Standard, genericTypeBuilder, Type.EmptyTypes);
var getIL = getMethodBuilder.GetILGenerator();
getIL.Emit(OpCodes.Ldarg_0);
getIL.Emit(OpCodes.Ldfld, fieldBuilder);
getIL.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(getMethodBuilder, interfaceType.GetProperty("Name").GetGetMethod()); //實現對接口方法的重載
propertyBuilder.SetGetMethod(getMethodBuilder); //設置為屬性的get方法
//定義set方法
var setMethodBuilder = typeBuilder.DefineMethod("set_Name", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.SpecialName | MethodAttributes.Virtual, CallingConventions.Standard, null, new Type[] { genericTypeBuilder });
var setIL = setMethodBuilder.GetILGenerator();
setIL.Emit(OpCodes.Ldarg_0);
setIL.Emit(OpCodes.Ldarg_1);
setIL.Emit(OpCodes.Stfld, fieldBuilder);
setIL.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(setMethodBuilder, interfaceType.GetProperty("Name").GetSetMethod()); //實現對接口方法的重載
propertyBuilder.SetSetMethod(setMethodBuilder); //設置為屬性的set方法

   (10) 定義並實現PrintName方法

//定義方法
var printMethodBuilder = typeBuilder.DefineMethod("PrintName", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, CallingConventions.Standard, null, Type.EmptyTypes);
var printIL = printMethodBuilder.GetILGenerator();
printIL.Emit(OpCodes.Ldarg_0);
printIL.Emit(OpCodes.Ldflda, fieldBuilder);
printIL.Emit(OpCodes.Constrained, genericTypeBuilder);
printIL.Emit(OpCodes.Callvirt, typeof(object).GetMethod("ToString", Type.EmptyTypes));
printIL.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }));
printIL.Emit(OpCodes.Ret);
//實現對基類方法的重載
typeBuilder.DefineMethodOverride(printMethodBuilder, barType.GetMethod("PrintName", Type.EmptyTypes));

  (11) 創建類

var type = typeBuilder.CreateType(); //netstandard中請使用CreateTypeInfo().AsType()

  (12) 調用

var obj = Activator.CreateInstance(type.MakeGenericType(typeof(DateTime)), DateTime.Now);
(obj as Bar).PrintName();
Console.WriteLine((obj as IFoo<DateTime>).Name);

四、應用

  上面的樣例僅供學習只用,無法運用在實際項目當中,那麼,Emit構建類在實際項目中我們可以有什麼應用,提高我們的編碼效率

  (1) 動態DTO-當我們需要將實體映射到某個DTO時,可以用動態DTO來代替你手寫的DTO,選擇你需要的字段回傳給前端,或者前端把他想要的字段傳給後端

  (2) DynamicLinq-我的第一篇博文有個讀者提到了表達式樹,而linq使用的正是表達式樹,當表達式樹+Emit時,我們就可以用像SQL或者GraphQL那樣的查詢語句實現動態查詢

  (3) 對象合併-我們可以編寫實現一個像js當中Object.assign()一樣的方法,實現對兩個實體的合併

  (4) AOP動態代理-AOP的核心就是代理模式,但是與其對應的是需要手寫代理類,而Emit就可以幫你動態創建代理類,實現切面編程

  (5) …

五、小結

  對於Emit,確實初學者會對其感到複雜和難以學習,但是只要搞懂其中的原理,其實最終就是C#和.NET語言的本質所在,在學習Emit的同時,也是在鍛煉你的基本功是否紮實,你是否對這門語言精通,是否有各種簡化代碼的應用。

  保持學習,勇於實踐;Write Less,Do More;作者之後還會繼續.NET高級特性系列,感謝閱讀!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

Android DecorView 與 Activity 綁定原理分析

一年多以前,曾經以為自己對 View 的添加显示邏輯已經有所了解了,事後發現也只是懂了些皮毛而已。經過一年多的實戰,Android 和 Java 基礎都有了提升,是時候該去看看 DecorView 的添加显示。

概論

Android 中 Activity 是作為應用程序的載體存在,代表着一個完整的用戶界面,提供了一個窗口來繪製各種視圖,當 Activity 啟動時,我們會通過 setContentView 方法來設置一個內容視圖,這個內容視圖就是用戶看到的界面。那麼 View 和 activity 是如何關聯在一起的呢 ?

 上圖是 View 和 Activity 之間的關係。先解釋圖中一些類的作用以及相關關係:

  • Activity : 對於每一個 activity 都會有擁有一個 PhoneWindow。

  • PhoneWindow :該類繼承於 Window 類,是 Window 類的具體實現,即我們可以通過該類具體去繪製窗口。並且,該類內部包含了一個 DecorView 對象,該 DectorView 對象是所有應用窗口的根 View。
  • DecorView 是一個應用窗口的根容器,它本質上是一個 FrameLayout。DecorView 有唯一一個子 View,它是一個垂直 LinearLayout,包含兩個子元素,一個是 TitleView( ActionBar 的容器),另一個是 ContentView(窗口內容的容器)。

  • ContentView :是一個 FrameLayout(android.R.id.content),我們平常用的 setContentView 就是設置它的子 View 。

  • WindowManager : 是一個接口,裏面常用的方法有:添加View,更新View和刪除View。主要是用來管理 Window 的。WindowManager 具體的實現類是WindowManagerImpl。最終,WindowManagerImpl 會將業務交給 WindowManagerGlobal 來處理。
  • WindowManagerService (WMS) : 負責管理各 app 窗口的創建,更新,刪除, 显示順序。運行在 system_server 進程。

ViewRootImpl :擁有 DecorView 的實例,通過該實例來控制 DecorView 繪製。ViewRootImpl 的一個內部類 W,實現了 IWindow 接口,IWindow 接口是供 WMS 使用的,WSM 通過調用 IWindow 一些方法,通過 Binder 通信的方式,最後執行到了 W 中對應的方法中。同樣的,ViewRootImpl 通過 IWindowSession 來調用 WMS 的 Session 一些方法。Session 類繼承自 IWindowSession.Stub,每一個應用進程都有一個唯一的 Session 對象與 WMS 通信。

DecorView 的創建 

先從 Mainactivity 中的代碼看起,首先是調用了 setContentView;

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

該方法是父類 AppCompatActivity 的方法,最終會調用 AppCompatDelegateImpl 的 setContentView 方法:

// AppCompatDelegateImpl  
public void setContentView(int resId) { this.ensureSubDecor(); ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290); contentParent.removeAllViews(); LayoutInflater.from(this.mContext).inflate(resId, contentParent); this.mOriginalWindowCallback.onContentChanged(); }

ensureSubDecor 從字面理解就是創建 subDecorView,這個是根據主題來創建的,下文也會講到。創建完以後,從中獲取 contentParent,再將從 activity 傳入的 id xml 布局添加到裏面。不過大家注意的是,在添加之前先調用 removeAllViews() 方法,確保沒有其他子 View 的干擾。

    private void ensureSubDecor() {
        if (!this.mSubDecorInstalled) {
            this.mSubDecor = this.createSubDecor(); 
            ......
        }
        ......
    }        

 最終會調用 createSubDecor() ,來看看裏面的具體代碼邏輯:

 private ViewGroup createSubDecor() {
        // 1、獲取主題參數,進行一些設置,包括標題,actionbar 等 
        TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
        if (!a.hasValue(styleable.AppCompatTheme_windowActionBar)) {
            a.recycle();
            throw new IllegalStateException("You need to use a Theme.AppCompat theme (or descendant) with this activity.");
        } else {
            if (a.getBoolean(styleable.AppCompatTheme_windowNoTitle, false)) {
                this.requestWindowFeature(1);
            } else if (a.getBoolean(styleable.AppCompatTheme_windowActionBar, false)) {
                this.requestWindowFeature(108);
            }

            if (a.getBoolean(styleable.AppCompatTheme_windowActionBarOverlay, false)) {
                this.requestWindowFeature(109);
            }

            if (a.getBoolean(styleable.AppCompatTheme_windowActionModeOverlay, false)) {
                this.requestWindowFeature(10);
            }

            this.mIsFloating = a.getBoolean(styleable.AppCompatTheme_android_windowIsFloating, false);
            a.recycle();
            // 2、確保優先初始化 DecorView
            this.mWindow.getDecorView();
            LayoutInflater inflater = LayoutInflater.from(this.mContext);
            ViewGroup subDecor = null;
            // 3、根據不同的設置來對 subDecor 進行初始化
            if (!this.mWindowNoTitle) {
                if (this.mIsFloating) {
                    subDecor = (ViewGroup)inflater.inflate(layout.abc_dialog_title_material, (ViewGroup)null);
                    this.mHasActionBar = this.mOverlayActionBar = false;
                } else if (this.mHasActionBar) {
                    TypedValue outValue = new TypedValue();
                    this.mContext.getTheme().resolveAttribute(attr.actionBarTheme, outValue, true);
                    Object themedContext;
                    if (outValue.resourceId != 0) {
                        themedContext = new ContextThemeWrapper(this.mContext, outValue.resourceId);
                    } else {
                        themedContext = this.mContext;
                    }

                    subDecor = (ViewGroup)LayoutInflater.from((Context)themedContext).inflate(layout.abc_screen_toolbar, (ViewGroup)null);
                    this.mDecorContentParent = (DecorContentParent)subDecor.findViewById(id.decor_content_parent);
                    this.mDecorContentParent.setWindowCallback(this.getWindowCallback());
                    if (this.mOverlayActionBar) {
                        this.mDecorContentParent.initFeature(109);
                    }

                    if (this.mFeatureProgress) {
                        this.mDecorContentParent.initFeature(2);
                    }

                    if (this.mFeatureIndeterminateProgress) {
                        this.mDecorContentParent.initFeature(5);
                    }
                }
            } else {
                if (this.mOverlayActionMode) {
                    subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple_overlay_action_mode, (ViewGroup)null);
                } else {
                    subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple, (ViewGroup)null);
                }

                if (VERSION.SDK_INT >= 21) {
                    ViewCompat.setOnApplyWindowInsetsListener(subDecor, new OnApplyWindowInsetsListener() {
                        public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
                            int top = insets.getSystemWindowInsetTop();
                            int newTop = AppCompatDelegateImpl.this.updateStatusGuard(top);
                            if (top != newTop) {
                                insets = insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), newTop, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
                            }

                            return ViewCompat.onApplyWindowInsets(v, insets);
                        }
                    });
                } else {
                    ((FitWindowsViewGroup)subDecor).setOnFitSystemWindowsListener(new OnFitSystemWindowsListener() {
                        public void onFitSystemWindows(Rect insets) {
                            insets.top = AppCompatDelegateImpl.this.updateStatusGuard(insets.top);
                        }
                    });
                }
            }

            if (subDecor == null) {
                throw new IllegalArgumentException("AppCompat does not support the current theme features: { windowActionBar: " + this.mHasActionBar + ", windowActionBarOverlay: " + this.mOverlayActionBar + ", android:windowIsFloating: " + this.mIsFloating + ", windowActionModeOverlay: " + this.mOverlayActionMode + ", windowNoTitle: " + this.mWindowNoTitle + " }");
            } else {
                if (this.mDecorContentParent == null) {
                    this.mTitleView = (TextView)subDecor.findViewById(id.title);
                }

                ViewUtils.makeOptionalFitsSystemWindows(subDecor);
                ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);
                ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(16908290);
                if (windowContentView != null) {
                    while(windowContentView.getChildCount() > 0) {
                        View child = windowContentView.getChildAt(0);
                        windowContentView.removeViewAt(0);
                        contentView.addView(child);
                    }

                    windowContentView.setId(-1);
                    contentView.setId(16908290);
                    if (windowContentView instanceof FrameLayout) {
                        ((FrameLayout)windowContentView).setForeground((Drawable)null);
                    }
                }
                // 將 subDecor 添加到 DecorView 中
                this.mWindow.setContentView(subDecor);
                contentView.setAttachListener(new OnAttachListener() {
                    public void onAttachedFromWindow() {
                    }

                    public void onDetachedFromWindow() {
                        AppCompatDelegateImpl.this.dismissPopups();
                    }
                });
                return subDecor;
            }
        }
    }
                    

上面的代碼總結來說就是在做一件事,就是創建 subDecor。攤開來說具體如下:

1、根據用戶選擇的主題來設置一些显示特性,包括標題,actionbar 等。

2、根據不同特性來初始化 subDecor;對 subDecor 內部的子 View 進行初始化。

3、最後添加到 DecorView中。

添加的具體代碼如下:此處是通過調用 

 // AppCompatDelegateImpl   this.mWindow.getDecorView();

 // phoneWindow    public final View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }
 

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
 // 生成 DecorView             mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
 // 這樣 DecorView 就持有了window             mDecor.setWindow(this);
        }
      ......
}


   protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use // the context we have. Otherwise we want the application context, so we don't cling to the // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
   }

到此,DecorView 的創建就講完了。可是我們似乎並沒有看到 DecorView 是被添加的,什麼時候對用戶可見的。

 WindowManager

View 創建完以後,那 Decorview 是怎麼添加到屏幕中去的呢?當然是 WindowManager 呢,那麼是如何將 View 傳到 WindowManager 中呢。

看 ActivityThread 中的 handleResumeActivity 方法:

// ActivityThread
public
void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { ...... final int forwardBit = isForward ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; // If the window hasn't yet been added to the window manager, // and this guy didn't finish itself or start another activity, // then go ahead and add the window. boolean willBeVisible = !a.mStartedActivity; if (!willBeVisible) { try { willBeVisible = ActivityManager.getService().willActivityBeVisible( a.getActivityToken()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; ...... if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; wm.addView(decor, l); } else { // The activity will get a callback for this {@link LayoutParams} change // earlier. However, at that time the decor will not be set (this is set // in this method), so no action will be taken. This call ensures the // callback occurs with the decor set. a.onWindowAttributesChanged(l); } } // If the window has already been added, but during resume // we started another activity, then don't yet make the // window visible. } else if (!willBeVisible) { if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set"); r.hideForNow = true; } // Get rid of anything left hanging around. cleanUpPendingRemoveWindows(r, false /* force */); // The window is now visible if it has been added, we are not // simply finishing, and we are not starting another activity. if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) { if (r.newConfig != null) { performConfigurationChangedForActivity(r, r.newConfig); if (DEBUG_CONFIGURATION) { Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig " + r.activity.mCurrentConfig); } r.newConfig = null; } if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward); WindowManager.LayoutParams l = r.window.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != forwardBit) { l.softInputMode = (l.softInputMode & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) | forwardBit; if (r.activity.mVisibleFromClient) { ViewManager wm = a.getWindowManager(); View decor = r.window.getDecorView(); wm.updateViewLayout(decor, l); } } r.activity.mVisibleFromServer = true; mNumVisibleActivities++; if (r.activity.mVisibleFromClient) {           // 這裏也會調用addview r.activity.makeVisible(); } } r.nextIdle = mNewActivities; mNewActivities = r; if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r); Looper.myQueue().addIdleHandler(new Idler()); }

上面的代碼主要做了以下幾件事:

1、獲取到 DecorView,設置不可見,然後通過 wm.addView(decor, l) 將 view 添加到 WindowManager;

2、在某些情況下,比如此時點擊了輸入框調起了鍵盤,就會調用 wm.updateViewLayout(decor, l) 來更新 View 的布局。

3、這些做完以後,會調用 activity 的  makeVisible ,讓視圖可見。如果此時 DecorView 沒有添加到 WindowManager,那麼會添加。 

// Activity
void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); }

 接下來,看下 addview 的邏輯。 WindowManager 的實現類是 WindowManagerImpl,而它則是通過 WindowManagerGlobal 代理實現 addView 的,我們看下 addView 的方法:

// WindowManagerGlobal  
 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
           // ......
    
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
           // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            } 
}

在這裏,實例化了 ViewRootImpl 。同時調用 ViewRootImpl 的 setView 方法來持有了 DecorView。此外這裏還保存了 DecorView ,Params,以及 ViewRootImpl 的實例。

現在我們終於知道為啥 View 是在 OnResume 的時候可見的呢。

 ViewRootImpl

實際上,View 的繪製是由 ViewRootImpl 來負責的。每個應用程序窗口的 DecorView 都有一個與之關聯的 ViewRootImpl 對象,這種關聯關係是由 WindowManager 來維護的。

先看 ViewRootImpl 的 setView 方法,該方法很長,我們將一些不重要的點註釋掉:

   /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ......
               
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.

                requestLayout();
                ......
            }
        }
    }

這裏先將 mView 保存了 DecorView 的實例,然後調用 requestLayout() 方法,以完成應用程序用戶界面的初次布局。

 public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

因為是 UI 繪製,所以一定要確保是在主線程進行的,checkThread 主要是做一個校驗。接着調用 scheduleTraversals 開始計劃繪製了。

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

這裏主要關注兩點:

mTraversalBarrier : Handler 的同步屏障。它的作用是可以攔截 Looper 對同步消息的獲取和分發,加入同步屏障之後,Looper 只會獲取和處理異步消息,如果沒有異步消息那麼就會進入阻塞狀態。也就是說,對 View 繪製渲染的處理操作可以優先處理(設置為異步消息)。

mChoreographer: 編舞者。統一動畫、輸入和繪製時機。也是這章需要重點分析的內容。

mTraversalRunnable :TraversalRunnable 的實例,是一個Runnable,最終肯定會調用其 run 方法:

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

doTraversal,如其名,開始繪製了,該方法內部最終會調用 performTraversals 進行繪製。

  void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

到此,DecorView 與 activity 之間的綁定關係就講完了,下一章,將會介紹 performTraversals 所做的事情,也就是 View 繪製流程。 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

Java虛擬機詳解(十)——類加載過程

  在上一篇文章中,我們詳細的介紹了Java,那麼這些Class文件是如何被加載到內存,由虛擬機來直接使用的呢?這就是本篇博客將要介紹的——類加載過程。

1、類的生命周期

  類從被加載到虛擬機內存開始,到卸載出內存為止,其聲明周期流程如下:

  

  上圖中紅色的5個部分(加載、驗證、準備、初始化、卸載)順序是確定的,也就是說,類的加載過程必須按照這種順序按部就班的開始。這裏的“開始”不是按部就班的“進行”或者“完成”,因為這些階段通常是互相交叉混合的進行的,通常會在一個階段執行過程中調用另一個階段。

2、加載

  “加載”階段是“類加載”生命周期的第一個階段。在加載階段,虛擬機要完成下面三件事:

  ①、通過一個類的全限定名來獲取定義此類的二進制字節流。

  ②、將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。

  ③、在Java堆中生成一個代表這個類的java.lang.Class對象,作為方法區這些數據的訪問入口。

  PS:類的全限定名可以理解為這個類存放的絕對路徑。方法區是JDK1.7以前定義的運行時數據區,而在JDK1.8以後改為元數據區(Metaspace),主要用於存放被Java虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。詳情可以參考這邊該系列的第二篇文章——。

  另外,我們看第一點——通過類的權限定名來獲取定義此類的二進制流,這裏並沒有明確指明要從哪裡獲取以及怎樣獲取,也就是說並沒有明確規定一定要我們從一個 Class 文件中獲取。基於此,在Java的發展過程中,充滿創造力的開發人員在這個舞台上玩出了各種花樣:

  1、從 ZIP 包中讀取。這稱為後面的 JAR、EAR、WAR 格式的基礎。

  2、從網絡中獲取。比較典型的應用就是 Applet。

  3、運行時計算生成。這就是動態代理技術。

  4、由其它文件生成。比如 JSP 應用。

  5、從數據庫中讀取。

  加載階段完成后,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區中,然後在Java堆中實例化一個 java.lang.Class 類的對象,這個對象將作為程序訪問方法區中這些類型數據的外部接口。

  注意,加載階段與連接階段的部分內容(如一部分字節碼文件的格式校驗)是交叉進行的,加載階段尚未完成,連接階段可能已經開始了。

3、驗證

  驗證是連接階段的第一步,作用是為了確保 Class 文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。

  我們說Java語言本身是相對安全,因為編譯器的存在,純粹的Java代碼要訪問數組邊界外的數據、跳轉到不存在的代碼行之類的,是要被編譯器拒絕的。但是前面我們也說過,Class 文件不一定非要從Java源碼編譯過來,可以使用任何途徑,包括你很牛逼,直接用十六進制編輯器來編寫 Class 文件。

  所以,如果虛擬機不檢查輸入的字節流,將會載入有害的字節流而導致系統崩潰。但是虛擬機規範對於檢查哪些方面,何時檢查,怎麼檢查都沒有明確的規定,不同的虛擬機實現方式可能都會有所不同,但是大致都會完成下面四個方面的檢查。

①、文件格式驗證

  校驗字節流是否符合Class文件格式的規範,並且能夠被當前版本的虛擬機處理。

  一、是否以魔數 0xCAFEBABE 開頭。

  二、主、次版本號是否是當前虛擬機處理範圍之內。

  三、常量池的常量中是否有不被支持的常量類型(檢查常量tag標誌)

  四、指向常量的各種索引值中是否有指向不存在的常量或不符合類型的常量。

  五、CONSTANT_Utf8_info 型的常量中是否有不符合 UTF8 編碼的數據。

  六、Class 文件中各個部分及文件本身是否有被刪除的或附加的其他信息。

  以上是一部分校驗內容,當然遠不止這些。經過這些校驗后,字節流才會進入內存的方法區中存儲,接下來後面的三個階段校驗都是基於方法區的存儲結構進行的。

②、元數據驗證

  第二個階段主要是對字節碼描述的信息進行語義分析,以保證其描述的信息符合Java語言規範要求。

  一、這個類是否有父類(除了java.lang.Object 類之外,所有的類都應當有父類)。

  二、這個類的父類是否繼承了不允許被繼承的類(被final修飾的類)。

  三、如果這個類不是抽象類,是否實現了其父類或接口之中要求實現的所有普通方法。

  四、類中的字段、方法是否與父類產生了矛盾(例如覆蓋了父類的final字段、或者出現不符合規則的重載)

③、字節碼驗證

  第三個階段字節碼驗證是整個驗證階段中最複雜的,主要是進行數據流和控制流分析。該階段將對類的方法進行分析,保證被校驗的方法在運行時不會做出危害虛擬機安全的行為。

  一、保證任意時刻操作數棧中的數據類型與指令代碼序列都能配合工作。例如不會出現在操作數棧中放置了一個 int 類型的數據,使用時卻按照 long 類型來加載到本地變量表中。

  二、保證跳轉指令不會跳轉到方法體以外的字節碼指令中。

  三、保證方法體中的類型轉換是有效的。比如把一個子類對象賦值給父類數據類型,這是安全的。但是把父類對象賦值給子類數據類型,甚至賦值給完全不相干的類型,這就是不合法的。

④、符號引用驗證

  符號引用驗證主要是對類自身以外(常量池中的各種符號引用)的信息進行匹配性的校驗,通常需要校驗如下內容:

  一、符號引用中通過字符串描述的全限定名是否能夠找到相應的類。

  二、在指定類中是否存在符合方法的字段描述符及簡單名稱所描述的方法和字段。

  三、符號引用中的類、字段和方法的訪問性(private、protected、public、default)是否可以被當前類訪問。

4、準備

  準備階段是正式為類變量分配內存並設置類變量初始值的階段,這些內存是在方法區中進行分配。

  注意:

  一、上面說的是類變量,也就是被 static 修飾的變量,不包括實例變量。實例變量會在對象實例化時隨着對象一起分配在堆中。

  二、初始值,指的是一些數據類型的默認值。基本的數據類型初始值如下(引用類型的初始值為null):

  

 

   比如,定義 public static int value = 123 。那麼在準備階段過後,value 的值是 0 而不是 123,把 value 賦值為123 是在程序被編譯后,存放在類的構造器方法之中,是在初始化階段才會被執行。但是有一種特殊情況,通過final 修飾的屬性,比如 定義 public final static int value = 123,那麼在準備階段過後,value 就被賦值為123了。

5、解析

  解析階段是虛擬機將常量池中的符號引用替換為直接引用的過程。

  符號引用(Symbolic References):符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義的定位到目標即可。符號引用與虛擬機實現的內存布局無關,引用的目標不一定已經加載到內存中。

  直接引用(Direct References):直接引用可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用是與虛擬機實現內存布局相關的,同一個符號引用在不同虛擬機實例上翻譯出來的直接引用一般不會相同。如果有了直接引用,那麼引用的目標必定已經在內存中存在。

  解析動作主要針對類或接口、字段、類方法、接口方法四類符號引用,分別對應於常量池的 CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANTS_InterfaceMethodref_info四種類型常量。

6、初始化

   初始化階段是類加載階段的最後一步,前面過程中,除第一個加載階段可以通過用戶自定義類加載器參与之外,其餘過程都是完全由虛擬機主導和控制。而到了初始化階段,則開始真正執行類中定義的Java程序代碼(或者說是字節碼)。

  在前面介紹的準備階段中,類變量已經被賦值過初始值了,而初始化階段,則根據程序員的編碼去初始化變量和資源。

  換句話來說,初始化階段是執行類構造器<clinit>() 方法的過程

  ①、<clinit>() 方法 是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊(static{})中的語句合併產生的,編譯器收集的順序是由語句在源文件中出現的順序所決定的,靜態語句塊中只能訪問到定義在靜態語句塊之前的變量,定義在它之後的變量,在前面的靜態語句塊中可以賦值,但是不能訪問。

  比如如下代碼會報錯:

  

 

   但是你把第 14 行代碼放到 static 靜態代碼塊的上面就不會報錯了。或者不改變代碼順序,將第 11 行代碼移除,也不會報錯。

  ②、<clinit>() 方法與類的構造函數(或者說是實例構造器<init>()方法)不同,它不需要显示的調用父類構造器,虛擬機會保證在子類的<init>()方法執行之前,父類的<init>()方法已經執行完畢。因此虛擬機中第一個被執行的<init>()方法的類肯定是 java.lang.Object。

  ③、由於父類的<clinit>() 方法先執行,所以父類中定義的靜態語句塊要優先於子類的變量賦值操作。

  ④、<clinit>() 方法對於接口來說並不是必須的,如果一個類中沒有靜態語句塊,也沒有對變量的賦值操作,那麼編譯器可以不為這個類生成<clinit>() 方法。

  ⑤、接口中不能使用靜態語句塊,但仍然有變量初始化的賦值操作,因此接口與類一樣都會生成<clinit>() 方法。但接口與類不同的是,執行接口中的<clinit>() 方法不需要先執行父接口的<clinit>() 方法。只有當父接口中定義的變量被使用時,父接口才會被初始化。

  ⑥、接口的實現類在初始化時也一樣不會執行接口的<clinit>() 方法。

  ⑦、虛擬機會保證一個類的<clinit>() 方法在多線程環境中被正確的加鎖和同步。如果多個線程同時去初始化一個類,那麼只會有一個線程去執行這個類的<clinit>() 方法,其他的線程都需要阻塞等待,直到活動線程執行<clinit>() 方法完畢。如果在一個類的<clinit>() 方法中有很耗時的操作,那麼可能造成多個進程的阻塞。

  比如對於如下代碼:

package com.yb.carton.controller;

/**
 * Create by YSOcean
 */
public class ClassLoadInitTest {


    static class Hello{
        static {
            if(true){
                System.out.println(Thread.currentThread().getName() + "init");
                while(true){}
            }
        }
    }

    public static void main(String[] args) {
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"start");
            Hello h1 = new Hello();
            System.out.println(Thread.currentThread().getName()+"run over");
        }).start();


        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"start");
            Hello h2 = new Hello();
            System.out.println(Thread.currentThread().getName()+"run over");
        }).start();
    }

}

View Code

  運行結果如下:

  

 

   線程1搶到了執行<clinit>() 方法,但是該方法是一個死循環,線程2將一直阻塞等待。

  知道了類的初始化過程,那麼類的初始化何時被觸發呢?JVM大概規定了如下幾種情況:

  ①、當虛擬機啟動時,初始化用戶指定的類。

  ②、當遇到用以新建目標類實例的 new 指令時,初始化 new 指定的目標類。

  ③、當遇到調用靜態方法的指令時,初始化該靜態方法所在的類。

  ④、當遇到訪問靜態字段的指令時,初始化該靜態字段所在的類。

  ⑤、子類的初始化會觸發父類的初始化。

  ⑥、如果一個接口定義了 default 方法,那麼直接實現或間接實現該接口的類的初始化,會觸發該接口的初始化。

  ⑦、使用反射 API 對某個類進行反射調用時,會初始化這個類。

  ⑧、當初次調用 MethodHandle 實例時,初始化該 MethodHandle 指向的方法所在的類。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選