分類
發燒車訊

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

 

起因

最近接觸到一些國外的項目,由於國內外有時差這個東西,對於某些基礎數據存到數據庫的時候需要記錄時間,為了方便,這裏採用了時間戳(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地圖已可更新顯示潭子電動車充電站設置地點!!

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

分類
發燒車訊

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 是電子產業重要的元件?

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

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

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

分類
發燒車訊

豐田衝刺燃料電池車,傳產能將擴增至 10 倍以上

日刊工業新聞 3 日,豐田汽車(Toyota)計劃於 2020 年將燃料電池車(FCV)月產能提高至 3,000 台,將達現行的 10 倍以上水準。豐田計劃在 2020 年下半推出 FCV 車「MIRAI」的次代車款。

據報導,豐田目前利用元町工廠的專用產線生產「MIRAI」,年產能約 3,000 台,依此換算月產能相當於 250 台左右。

2018 年 MIRAI 全球銷售量約 2,400 台,而豐田目標在 2020 年以後將 FCV 年銷售量提高至 3 萬台以上水準。

截至台灣時間 3 日上午 10 點 21 分為止,豐田下跌 0.84%。

豐田於 2018 年 5 月 24 日宣布,為了因應計劃在 2020 年以後將 FCV 全球年銷售量提高至 3 萬台以上水準的目標,決議將增產 FCV 關鍵零件,計劃在愛知縣豐田市的本社工廠廠區內興建新廠房、增產燃料電池堆(Fuel Cell stack),且也計劃在愛知縣三好市的下山工廠內增設用來儲存氫燃料的高壓氫氣槽專用產線。上述新廠預計於 2020 年左右啟用。

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

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

【其他文章推薦】

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

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

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

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

分類
發燒車訊

6000年前一隻狗的癌症,如何在今天席捲全球?

  即使睿智如人類,強壯如猛獸,也無法躲過疾病的侵襲,癌症就是其中尤為嚴重的一種。

  時至今日,我們仍舊深陷於與癌症抗爭的泥潭中苦苦掙扎。目前已有的治療手段有手術、化學療法、放射線療法、癌症疫苗、免疫細胞療法等。但很遺憾,目前除了針對非實體瘤的治療方法效果卓著接近曙光,對大多數癌症仍舊是無計可施。

  究其原因,仍是我們對癌症不夠了解,或者說是無從下手。以人類短短百餘年的壽命極限來抗衡與進化同生的癌症,實在是有些蚍蜉撼樹的意味。癌症的偶然發生,以單一生命個體為單位進行研究,百餘年的度量難以拼接成完整的畫面。

  而今,一種至今已存在了 6000 余年的癌症為我們的研究提供了絕佳的條件。


來源:Ernesto del Aguila III, NHGRI

  CTVT(canine transmissible venereal tumor),即犬類傳染性性病腫瘤。這種癌症最早起源於中亞地區,來自於某條“始祖犬”的生殖器細胞基因突變。隨後,伴隨犬類的交配,生生不息,如今已經幾乎遍布世界的每個角落,至今已有約 6000 年。

  來自 40 多個國家的聯合團隊,通過對來自 43 個國家的 546 個 CTVT 腫瘤樣本和 495 個 CTVT 腫瘤宿主的正常樣本進行了外顯子測序,構建出時間系統發育譜系。同時研究者們對 CTVT 的癌症突變特徵進行了分析,並由此識別出 CTVT 的高度環境特異性突變過程,以及中性遺傳漂變是癌症長期演化的主要特徵,相關的研究細節發表在 Science 雜誌上。

  對 CTVT 的研究,為人類在數千年的時間單位上更好地認識癌症進化上提供了絕佳的機會,這也將是人類未來戰勝癌症的重要參考。

  一、對癌症的認識

  癌症,又名惡性腫瘤,是指細胞不正常的增生,且這些增生的細胞可能隨淋巴或血液系統攻佔身體的每個角落。千萬年間,人們始終沒有放棄與癌症的抗爭,卻屢屢折戟沉沙。因此,癌症在很長時間內都被認為是無法治癒的疾病,神靈的詛咒。

  在人類身上,目前已知的癌症已經超過 100 種。2015 年,約有 880 萬人死於癌症,這幾乎佔到了全球死亡人數的六分之一,其中的 70% 發生在低收入和中等收入國家。

  癌症並非一種源於工業化的人造疾病,而是與演化如影隨形,共同塑造了生命。癌症的存在歷史可以追溯至上萬年,但直到近百年間,人們才開始真正地了解癌症。

  18 世紀,醫生藉助解剖刀開始了與癌症的正式交鋒——腫瘤切除治療。但癌症的複發與轉移,成為橫亘在醫生們面前的又一條門檻。

  那麼,究竟什麼才是癌症背後真正的力量呢?答案是基因

  事實上,癌症是一種依賴基因突變的慢性疾病。一般來說,同一種癌症在不同患者身上,甚至是同一患者的不同器官或組織中,都可能具有不同的基因型。癌症,似乎可以看做是某些邪惡基因隨機發生於宿主個體間的一種“寄生”。

  肉體總有終結之時,但癌症永生。當然,對於絕大多數不具有傳染性的癌症來說,只是在時間跨度下的眾多個體間的廣義永生。事實上,有極少數的癌症的確可以在生命個體間傳播,延續着自己的生命,完成永生。

  但值得一提的是,傳染性癌症區別於感染型癌症,並不是通過病毒感染誘發的。大多數病毒感染誘發的癌症,如人乳頭瘤病毒引起的宮頸癌、乙肝病毒引起的肝癌,都可以通過接種疫苗有效預防。

  二、古老的癌症

  對於大多數癌症來說,他們隨機的發生於單一個體,隨個體的壽命而發生、發展、終結。而其中的極少數癌症,可以在個體間進行傳播,就像“寄生”在宿主中完成自身的演化時間線,CTVT 就是其中一員。

  這種來源於犬類的癌症起源於中亞,遺傳信息穩定且高度相似。對於它開始的時間,研究者們尚存在爭議,一部分人認為約在 1.1 萬年前犬類的馴化時間點上,也有人認為發生於時間稍近的 6000 多年前。

  通過犬類之間的交配、甚至是舔舐,CTVT 在群體間進行傳播。每一顆癌細胞就像是種子,到達下一個宿主體內,等待合適的時機繼續傳播。

  隨着大航海時代的到來,人類的生活半徑增大,而犬類也跟隨人類開始了他們的遷移。時至今日,幾乎在每塊大陸上,都有 CTVT 的痕迹。

  而如今,它居然歪打正着地成為研究癌症的最佳手段,幫助人類追蹤癌症的演化,破解癌症的謎團。

  三、揭開千年疑團

  在此項研究中,研究者們對來自 43 個國家的 546 個 CTVT 腫瘤樣本和 495 個 CTVT 腫瘤宿主的正常樣本進行了外顯子測序,並構建出時間系統發育譜系。分析結果显示,CTVT 細胞大約在 6200 年前首次於亞洲出現,目前廣泛分佈的 CTVT 細胞的源頭可以追溯到約 1900 年前的印度。彼時 CTVT 開始產生亞型,並開始向歐洲、亞洲蔓延擴散。隨着大航海時代的到來,CTVT 的傳播也搭上了“順風船”,跟隨人類的足跡踏上更多的陸地。


來源:Science

  隨後研究者們對 CTVT 的癌症突變特徵進行了分析,並由此識別出 CTVT 的高度環境特異性突變過程。同時,研究者發現了 5 個促進 CTVT 發生和傳播的早期驅動基因:SETD2,CDKN2A,MYC,PTEN 和 RB1。研究者也發現,CTVT 幾乎沒有晚期陽性選擇,解釋了中性遺傳漂變是癌症長期演化的主要特徵。

  殖民、全球化、同質化,共同作用造成了如今的 CTVT。而存活了數千年、從來不能滅亡的 CTVT,同時也像活化石、錄影帶一樣記錄了癌症的進化歷程。管中窺豹,可見一斑。

  對於 CTVT 來說,癌細胞似乎更像是一種獨立的生命體在不同的“宿主”間傳播,雖然來源不同,但卻可以和不同個體的免疫系統都相安無事。儘管目前並未發現可以在人體間傳染的癌症,但足以為器官移植敲響警鐘。如果捐贈者的器官中留有癌症的“種子”,對於接受器官移植的人來說很可能是一場可怕的災難。

  同時,CTVT 的中性進化也為現代癌症的治療提供思路。對於一些進程緩慢的癌症,似乎可以嘗試適應性療法,而非在癌細胞和宿主間,一定要斗個“你死我活”。

  如果承載生命的主體是遺傳物質,那麼毫無疑問,癌症從未死去。如果短期內無法戰勝,找到與它“同生”的方法或許並不是最壞的選擇。

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

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

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

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

分類
發燒車訊

JVM 問題排查和性能優化常用的 JDK 工具

JDK 提供了一系列用於監控、診斷 Java 進程的工具,它們在 JDK 安裝目錄的 bin 目錄下,有 jps、jcmd、jstack、jinfo、jmap 等。其中jmc、jconsole、jvisualvm 是 GUI 工具,其他大部分都是命令行工具。

cd $JAVA_HOME/bin
ls

本篇只是個入門介紹,不涉及深入分析。每一個工具都有它專門的作用,掌握使用方法只是很簡單的入門階段,更重要的是根據工具得到的信息去分析系統存在的問題以及性能瓶頸,每一個工具的使用和分析都可以單獨成文。

jps

如果你用過 Linux,那肯定熟悉 ps 命令,用來查看進程列表的。jps 就好比是 ps 命令的子集,它查詢的是當前用戶下已經啟動的 Java 進程。這是進行線上問題排查的大門鑰匙,有了它才能下手後面的動作。

下面是 jps 的幫助文檔

usage: jps [-help]
       jps [-q] [-mlvV] [<hostid>]

Definitions:
    <hostid>:      <hostname>[:<port>]

一般的用法是 jps -l,前面一列显示 pid,後面一列显示進程名稱。

還可以用下列參數查看更具體的 Java 進程信息,用法為 jps -lv

jstack

查看 Java 進程內當前時刻的線程快照,也就是每條線程正在執行的方法棧情況,用於定位線程停頓、死鎖等長時間等待的問題。

以下是 jstack 的幫助文檔。

Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message

最常用的就是 jstack -pid 或者 jstack -l pid,打印線程狀態、棧使用情況。

如果是線上查看不方便的話,可以用命令 jstack -l pid > stack.log,輸出到文件中下載到本地查看。

jstack -m pid,打印 Java 和 Native 棧信息

如果 -l 和 -m 都不起作用的時候,可以使用 java -F pid 強制 dump。

jinfo

它的主要作用是查看 JVM 配置參數,還可以動態設置部分參數值。jinfo 使用時需要 attach 到目標 JVM 上。關於 attach jvm 可以點擊查看

使用 jinfo -h查看幫助文檔

Usage:
    jinfo [option] <pid>
        (to connect to running process)
    jinfo [option] <executable <core>
        (to connect to a core file)
    jinfo [option] [server_id@]<remote server IP or hostname>
        (to connect to remote debug server)

where <option> is one of:
    -flag <name>         to print the value of the named VM flag
    -flag [+|-]<name>    to enable or disable the named VM flag
    -flag <name>=<value> to set the named VM flag to the given value
    -flags               to print VM flags
    -sysprops            to print Java system properties
    <no option>          to print both of the above
    -h | -help           to print this help message

jinfo -flags pid

查看 JVM 參數,其中 Non-default VM flags 是虛擬機默認設置的參數,Command line 是用戶指定的參數,比如命令行啟動 jar 包的時候加上的參數。

jinfo -flag 參數名 pid

可以查看指定參數的值,比如查看堆的最大值(-XX:MaxHeapSize 也就是 -Xmx ):

jinfo -flag MaxHeapSize 92041

-XX:MaxHeapSize=20971520

jinfo -sysprops pid

查看系統參數

jinfo pid

查看 jvm 參數和系統參數

以上信息,如果我們用過 visualVM 等監控工具,一定非常熟悉。另外,我之前做過一個 ,也實現了這個功能。

另外,還可以修改部分參數值。

jinfo -flag [+|-] pid

jinfo -flag = pid

可以修改部分 JVM 參數。

前者可以修改布爾值參數,比如開啟簡單 GC 日誌

jinfo -flag +PrintGC 92041

後者是設置非布爾值參數的,比如設置 HeapDumpPath

jinfo -flag HeapDumpPath=/users/fengzheng/jvmlog

哪些參數是允許動態修改的呢,用下面這個命令可以查看

#Linux 和 Mac 
java -XX:+PrintFlagsInitial | grep manageable

#windows
java -XX:+PrintFlagsInitial | findstr manageable

jmap

jmap 查看給定進程、核心文件、遠程調試服務器的共享對象內存映射和堆內存細節的工具,可查看堆使用情況、堆內對象直方圖、加載類、生成堆快照等。

Usage:
    jmap [option] <pid>
        (to connect to running process)
    jmap [option] <executable <core>
        (to connect to a core file)
    jmap [option] [server_id@]<remote server IP or hostname>
        (to connect to remote debug server)

where <option> is one of:
    <none>               to print same info as Solaris pmap
    -heap                to print java heap summary
    -histo[:live]        to print histogram of java object heap; if the "live"
                         suboption is specified, only count live objects
    -clstats             to print class loader statistics
    -finalizerinfo       to print information on objects awaiting finalization
    -dump:<dump-options> to dump java heap in hprof binary format
                         dump-options:
                           live         dump only live objects; if not specified,
                                        all objects in the heap are dumped.
                           format=b     binary format
                           file=<file>  dump heap to <file>
                         Example: jmap -dump:live,format=b,file=heap.bin <pid>
    -F                   force. Use with -dump:<dump-options> <pid> or -histo
                         to force a heap dump or histogram when <pid> does not
                         respond. The "live" suboption is not supported
                         in this mode.
    -h | -help           to print this help message
    -J<flag>             to pass <flag> directly to the runtime system

jmap -heap pid

打印 JVM 堆概要信息,包括堆配置、新生代、老生代信息

jmap -histo pid

打印類的直方圖,也就是各個類實例的個數和空間佔用情況。

如果加 :live,jamp -histo:live pid 則只打印活動類的信息。這個命令會出發 GC 動作,會導致 JVM 停頓,所以在線上環境要慎用。

jmap -dump

dump 當前 JVM 堆,一般用法如下:

#dump 所有對象在堆中的分佈情況
jmap -dump:format=b,file=/Users/fengzheng/jvmlog/jamp_dump.hprof 95463

#加:live 參數 dump 存活對象在隊中的分佈情況
jmap -dump:live,format=b,file=/Users/fengzheng/jvmlog/jamp_dump.hprof 95463

之後再用堆分析工具,比如 visualVM、JProfile、MAT 等進行分析。和我們設置

-XX:+HeapDumpOnOutOfMemoryError 參數后,在發生 OOM 的時候 dump 的堆信息是一樣的。

注意,dump 的過程會比較慢,在這個過程中會發生 JVM 停頓,而且在使用 :live 參數后,會觸發 GC 操作。

jmap -clstats pid

Java 類加載器(ClassLoader)信息,包括加載器名稱、已加載類個數、佔用空間、父加載器、是否存活、類型信息。

jmap -finalizerinfo pid

查看等待被回收的對象。

jstat

jstat 主要用來通過垃圾回收相關信息來判斷 JVM 性能問題,也可以查看類加載、編譯的情況,主要的用法是通過持續的固定時間間隔的輸出來觀察。比如每 3 秒打印一次 GC 回收次數,連續打印 10 次,通過動態的變化來觀察 GC 是否過於密集。

下面是 jstat 的幫助手冊。

Usage: jstat -help|-options
       jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

Definitions:
  <option>      An option reported by the -options option
  <vmid>        Virtual Machine Identifier. A vmid takes the following form:
                     <lvmid>[@<hostname>[:<port>]]
                Where <lvmid> is the local vm identifier for the target
                Java virtual machine, typically a process id; <hostname> is
                the name of the host running the target Java virtual machine;
                and <port> is the port number for the rmiregistry on the
                target host. See the jvmstat documentation for a more complete
                description of the Virtual Machine Identifier.
  <lines>       Number of samples between header lines.
  <interval>    Sampling interval. The following forms are allowed:
                    <n>["ms"|"s"]
                Where <n> is an integer and the suffix specifies the units as 
                milliseconds("ms") or seconds("s"). The default units are "ms".
  <count>       Number of samples to take before terminating.
  -J<flag>      Pass <flag> directly to the runtime system.

通過 jstat -options 可以看到 jstat 支持查看哪些信息。

$ jstat -options
-class  #類加載情況 加載個數和空間使用
-compiler #即時編譯器信息
-gc  # GC情況 包括 young gc、full gc 次數、時間等
-gccapacity #年輕代、老年代的使用情況
-gccause #GC 統計信息和回收原因
-gcmetacapacity #显示有關metaspace大小的統計信息
-gcnew #新生代 GC 統計
-gcnewcapacity #新生代內存統計
-gcold #老年代 GC 統計
-gcoldcapacity #老年代內存使用情況
-gcutil #GC 匯總信息
-printcompilation #編譯方法統計

上述這些大多數可以對應到 visualVM 的這一部分显示

示例用法,如下是打印 5301 進程下的垃圾回收情況,-h 3 表示每 3 行輸出一次標題信息,3s 5 表示每 3s 輸出一次,一共輸出 5 次

jstat -gcutil -h 3 5301 3s 5

最後輸出的內容如下:

jstat -gcutil -h 3 5301 3s 5
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
 99.92   0.00  11.90  35.29  94.96  94.08     34   12.675     3    1.946   14.621
 99.92   0.00  11.90  35.29  94.96  94.08     34   12.675     3    1.946   14.621
 99.92   0.00  11.90  35.29  94.96  94.08     34   12.675     3    1.946   14.621
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
 99.92   0.00  11.94  35.29  94.96  94.08     34   12.675     3    1.946   14.621
 99.92   0.00  11.94  35.29  94.96  94.08     34   12.675     3    1.946   14.621

jcmd

jcmd 會將命令發送給 JVM。這些命令包括用於控制 Java Flight Recording(飛行記錄)、診斷命令等。 必須運行在 JVM 本地,不能遠程使用,並且必須用 JVM 啟動用戶執行。

通過 jps 命令找到一個 JVM 進程,然後使用下面的代碼可以看到 jcmd 支持的命令

#進程 5173 
jcmd 5173 help 

5173:
The following commands are available:
JFR.stop
JFR.start
JFR.dump
JFR.check
VM.native_memory
VM.check_commercial_features
VM.unlock_commercial_features
ManagementAgent.stop
ManagementAgent.start_local
ManagementAgent.start
GC.rotate_log
Thread.print
GC.class_stats
GC.class_histogram
GC.heap_dump
GC.run_finalization
GC.run
VM.uptime
VM.flags
VM.system_properties
VM.command_line
VM.version
help

基本包含了問題排查的常用命令,並且和上面介紹的幾個工具有部分重合。

通過命令 jcmd 5173 help GC.heap_dump 可以查詢到 GC.heap_dump 命令的使用方法,其他命令都可以通過這個方法找到使用說明

jcmd 5173 help GC.heap_dump
5173:
GC.heap_dump
Generate a HPROF format dump of the Java heap.

Impact: High: Depends on Java heap size and content. Request a full GC unless the '-all' option is specified.

Permission: java.lang.management.ManagementPermission(monitor)

Syntax : GC.heap_dump [options] <filename>

Arguments:
    filename :  Name of the dump file (STRING, no default value)

Options: (options must be specified using the <key> or <key>=<value> syntax)
    -all : [optional] Dump all objects, including unreachable objects (BOOLEAN, false)

然後通過如下代碼就可以 dump 堆信息下來了,和 jmap -dump 的作用一樣

jcmd 5173 GC.heap_dump /Users/fengzheng/jvmlog/jcmd_heap_dump.hprof

拋磚引玉就到此了,之後會對 jinfo、jmap、jstack、jstat、jcmd 做詳細說明,記得關注啊。

相關閱讀:

歡迎關注,不定期更新本系列和其他文章
古時的風箏 ,進入公眾號可以加入交流群

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

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

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

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

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

分類
發燒車訊

天啦!竟然從來沒有人講過 SpringBoot 支持配置如此平滑的遷移

SpringBoot 是原生支持配置遷移的,但是官方文檔沒有看到這方面描述,在源碼中才看到此模塊,spring-boot-properties-migrator,幸虧我沒有跳過。看到這篇文章的各位,可算是撿到寶了,相信你繼續往下看下去,定會忍不住點贊、收藏、關注。

效果

先放個效果吸引你 🙂

從 SpringBoot 2.0.0 版本開始,配置服務上下文,不支持 server.context-path,而需要server.servlet.context-path配置。但是只要加上以下一個官方依賴,就可以支持使用 server.context-path

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-properties-migrator</artifactId>
    </dependency>

server.context-path 所對應的屬性 ServerProperties#contextPath 在 Java 代碼中已不存在,server.servlet.context-path 所對應的的屬性在內部類 Servlet 中才有,為何加了此依賴就能實現如此神奇的效果呢。

原理

SpringBoot 對外部化配置原生支持遷移功能,所謂遷移,具體是指對應配置的屬性名變動,仍可以使用原來的屬性名配置。
spring-configuration-metadata.json 的信息可以輔助 IDE 進行配置的提示,也可以用來完成配置的遷移。非常的簡單。

相關文章:

通過閱讀代碼,獲得以下信息:

  1. 監聽 ApplicationPreparedEvent 事件(即:環境已準備事件),執行以下操作並收集信息
  2. classpath*:/META-INF/spring-configuration-metadata.json 中載入所有配置
  3. 從上下文的 environment 中過濾出提示的配置(滿足條件:1. deprecation 不為 null,且提示 level 為 error)
  4. 判斷是否兼容(兼容條件見下一節),提取出兼容的屬性
  5. 將 value 對應到 replacement 的 key,並將其屬性源命名為:migrate-原名
  6. 將配置遷移的新屬性源添加到 environment 中,且添加到原屬性源之前(優先級高)。
  7. 監聽事件:ApplicationReadyEvent(應用上下文已準備) 或 ApplicationFailedEvent(應用啟動失敗),打印以上步驟收集的遺留配置信息。以 warn 級別打印兼容的配置,以 error 級別打印不兼容的配置

配置兼容條件

根據元數據中定義的 type 判斷

  1. 如果舊類型、新類型其中之一為 null(元數據中未指定),則不兼容
  2. 如果兩個類型一樣,兼容
  3. 如果新類型是 Duration,而舊類型是 Long 或 Integer,則兼容
  4. 其他情況視為不兼容
  5. environment 中取配置信息,理論上支持 SpringBoot 所有的配置方式

效果

兼容效果:
棄用屬性(如果還存在)與替換后的屬性都會使用配置文件中的棄用的屬性名所對應的的值。

總結

使用配置遷移功能,需要以下步驟:

  1. 引入依賴:spring-boot-properties-migrator(支持配置遷移)、spring-boot-configuration-processor(生成元數據文件,如果已經有完整的,不需要此依賴)
  2. 元數據文件spring-configuration-metadata.json 中棄用屬性名對應的 properties 中必須有 deprecation(在additional-spring-configuration-metadata.json 中添加,相關文章: )
  3. deprecation 中需指定 levelerror
  4. deprecation 中需指定 replacement
  5. replacement 對應的屬性配置在元數據文件中存在,與棄用屬性兼容

經典示例之配置上下文

再說回一開始展示的配置上下文示例。

# 配置 servlet 服務上下文
server:
  context-path: test

從 SpringBoot 2.0.0 版本開始,以上配置不支持,點到配置元數據文件中(spring-configuration-metadata.json),發現如下信息:

{
  "properties": [
    {
      "name": "server.context-path",
      "type": "java.lang.String",
      "description": "Context path of the application.",
      "deprecated": true,
      "deprecation": {
        "level": "error",
        "replacement": "server.servlet.context-path"
      }
    },
    {
      "name": "server.servlet.context-path",
      "type": "java.lang.String",
      "description": "Context path of the application.",
      "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties$Servlet"
    }

替換屬性名為:server.servlet.context-path,此屬性在org.springframework.boot.autoconfigure.web.ServerProperties 中,且在類中可以發現,server.context-path 所對應的屬性 ServerProperties#contextPath 在代碼中已不存在,而是在內部類 Servlet 中有,也就是對應 server.servlet.context-path 的屬性才有。

但是其滿足配置兼容的條件,為什麼實際上使用卻好像不兼容呢?
其實是因為沒有引入依賴,當引入依賴,就會發現此方式配置可以起作用。

示例之兩種屬性都存在

代碼示例見

1、引入依賴

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-properties-migrator</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>

2、Java 配置
此處故意保留棄用屬性

@Data
@Configuration
@ConfigurationProperties(prefix = "my")
public class MyProperties {
  /** the project name */
  private String name;

  private App app;

  @Data
  public static class App {
    private String name;
  }
}

3、元數據配置,spring-configuration-metadata.json 由程序生成,自定義配置放在 additional-spring-configuration-metadata.json

{
  "properties": [
    {
      "name": "my.name",
      "type": "java.lang.String",
      "description": "the project name.",
      "deprecation": {
        "reason": "test the properties-migrator feature.",
        "replacement": "my.app.name",
        "level": "error"
      }
    },
    {
      "name": "my.app.name",
      "type": "java.lang.String",
      "sourceType": "com.lw.properties.migrator.config.MyProperties$App",
      "description": "the project name."
    }
  ]
}

4、在 properties 或 yml 文件中配置

my:
  name: lw
  app:
    name: app

5、打印配置信息

@Slf4j
@SpringBootApplication
public class PropertiesMigratorApplication {

  public static void main(String[] args) {
    ConfigurableApplicationContext context =
        SpringApplication.run(PropertiesMigratorApplication.class, args);
    MyProperties myProperties = context.getBean(MyProperties.class);
    log.info("myProperties.name:{}", myProperties.getName());
    log.info(
        "myProperties$app.name:{}",
        Optional.ofNullable(myProperties.getApp()).orElse(new App()).getName());
  }
}

6、打印信息如下:

2019-11-23 21:42:09.580 WARN 109408 — [ main] o.s.b.c.p.m.PropertiesMigrationListener :
The use of configuration keys that have been renamed was found in the environment:

Property source ‘applicationConfig: [classpath:/application.yml]’:
Key: my.name
Line: 4
Replacement: my.app.name
Key: server.context-path
Line: 2
Replacement: server.servlet.context-path

Each configuration key has been temporarily mapped to its replacement for your convenience. To silence this warning, please update your configuration to use the new keys.
……… myProperties.name:lw
……… myProperties\(app.name:lw ……… serverProperties\)servlet.contextPath:/app

7、效果解析
在 yml 中棄用屬性名優先級更高,棄用屬性與新屬性都使用此棄用屬性名對應的值。

參考資料

SpringBoot 2.2.1.RELEASE 源碼
公眾號:逸飛兮(專註於 Java 領域知識的深入學習,從源碼到原理,系統有序的學習)

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

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

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

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

分類
發燒車訊

Facebook改進換臉術:無需“投喂”圖片,從視頻里直接變臉

  曉查 發自 凹非寺 
  量子位 報道 公眾號 QbitAI

  近兩年來,Deepfakes 讓許多歐美明星吃盡了苦頭,面對自己的頭像被替換到各種視頻中,卻無能為力。

  比如黑寡婦就對自己的臉被替換到小電影中感到很無奈,呼籲大家停止用 AI 作惡。

  而最近,Facebook 人工智能研究院讓換臉技術再次進化。

  過去 Deepfakes 這項技術需要很多準備材料:一是被替換人臉的原視頻,二是來自換臉人面部各個角度的照片。有這兩樣東西才能造出完美無暇的換臉視頻。

  而來自 Facebook 的技術不需要照片,可以從原視頻直接生成換臉視頻,甚至能對實時視頻進行換臉。

  它讓“大表姐”變得不再熟悉。 

  這項技術的換臉實際上是毫無違和感地修改五官特徵,好讓 AI 無法識別出,因此也就不需要照片了。

  而且 Facebook 的研究人員還表示,這項技術修改后的明星臉仍然可以被人識別出來,但是 AI 卻不行。 

  Facebook 研發這項技術可不是為了換臉好玩,最近因使用人臉識別技術飽受爭議,這家公司希望通過這項新技術來保護用戶的隱私。

  人臉識別和換臉技術對普通民眾的隱私也造成了很大的威脅。比如前一陣大熱的換臉應用 ZAO,讓每個人都享受到換臉帶來的樂趣,但同時也會收集用戶圖片。

  研究人員在論文摘要中說:“人臉識別可能會導致隱私丟失,而換臉技術可能會被用於製作誤導性視頻。”Facebook 用後者來去除視頻中的隱私信息。

  Facebook 聲稱,該技術屬於業內首創,足以抵禦複雜的人臉識別系統。

  Facebook 將在下周韓國首爾舉行的國際計算機視覺國際會議(ICCV)上介紹該工作。

  本周,Facebook 還聯合微軟和亞馬遜,提供 Deepfakes 換臉挑戰數據集,希望能夠提高識別換臉視頻算法的魯棒性,以控制假視頻的傳播。

  此舉頗有些以彼之矛攻彼之盾的意味。

  原文鏈接:

  https://venturebeat.com/2019/10/25/facebook-alters-video-to-make-people-invisible-to-facial-recognition/

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

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

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

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

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

分類
發燒車訊

是什麼觸發了宇宙大爆炸?


圖片來源:Christine Daniloff, MIT, ESA/Hubble and NASA

  1.

  根據大爆炸理論,我們的宇宙誕生於 138 億年前,從一個無限小的緻密“火球”,不斷地擴張膨脹,並慢慢冷卻。漸漸地,宇宙中形成了第一批恆星、星系,以及我們如今所見到的所有形式的物質。

  物理學家相信,就在大爆炸將宇宙推向不斷膨脹的進程之前,早期宇宙還經歷了另一個更具爆炸性的階段——宇宙暴脹。這個過程持續的時間不到萬億分之一秒,但膨脹的速度卻是以指數級增長的。  


Guth 的筆記

  上個世紀 80 年代,物理學教授 Alan Guth 首次提出了宇宙膨脹理論,該理論預測宇宙最初是一個極微小的物質點,其大小可能只有質子的千億分之一。這個點中充滿了超高能物質,能量非常之大,以至於內部的壓力產生了一種排斥性的引力——這就是暴脹背後的驅動力。就像火花之於引信一樣,這種引力以前所未有的速度將新生的宇宙向外推,在不到萬億分之一秒的時間內,使宇宙膨脹到接近原始大小的 10²⁷倍。

  許多天文觀測結果都支持了大爆炸和宇宙暴脹理論。但是,這是兩個截然不同的過程,科學家們一直難以理解其中一個是如何緊隨另一個之後出現的。

  在一項新的研究中,物理學家詳細地模擬了早期宇宙中一個可能連接了宇宙暴脹和大爆炸的中間階段。這個階段被稱為“再熱”,這一過程出現在宇宙暴脹末期和大爆炸開始之前的階段,將暴脹產生的冷的、均勻的物質轉變成超熱的、複雜的物質湯。

  論文的作者David Kaiser說:“后暴脹再熱時期為大爆炸創造了條件,在某種意義上,是它啟動了‘爆炸’,正是在這個橋樑時期,所有的事物都開始鬆動,物質的行為變得非常複雜。”

  2.

  對於這個連接了宇宙暴脹和大爆炸的橋樑階段,研究人員很好奇它最初會是什麼樣子。

  Kaiser 說:“再熱的最初階段應該用共振來標記。一種高能物質佔據主導地位,它在廣闊的空間中與自身同步來回擺動,導致爆炸式地產生新的粒子。但這種行為不會永遠持續下去,一旦它開始將能量轉移到另一種形式的物質上,它自身的擺動將在空間中變得更加起伏不平。我們想要測量的是,需要多長的時間共振效應才會破裂,產生的粒子才會彼此分散並且達到某種熱平衡,讓人想起大爆炸發生時的情況。”

  他們的計算機模擬中展示了一個很大的晶格,在這個晶格上,他們繪製了多種形式的物質,並追蹤了當某些條件被改變時,能量和分佈會如何隨之在空間和時間上發生變化。模擬的初始條件是基於一個特定的暴脹模型而設置的,這個模型是一組關於早期宇宙的物質分佈在宇宙暴脹期間的行為得到一系列預測。

  他們之所以選擇這一特定的暴脹模型,是因為它的預測與高精度的宇宙微波背景測量結果非常吻合。

  3.

  在模擬中,他們研究了兩種可能在暴脹期間佔主導地位的物質的行為,這些物質與希格斯玻色子非常相似。

  在進行模擬之前,研究人員對模型中的引力描述進行了一個微小的調整。我們如今看到的普通物質在引力下的作用應正如愛因斯坦廣義相對論中所預測的那樣;但在更高的能量下,比如在宇宙暴脹期間,物質的行為或許會略有不同,與之產生相互作用的引力是由量子力學修正過的。

  在廣義相對論中,引力的強度被表示為一個常數,物理學家稱之為是最小耦合,這意味着,無論一個特定粒子的能量為何,它都會以一個由普適常數所設定的強度對引力效應做出反應。

  然而,在宇宙暴脹的高能量下,物質與引力的相互作用會以一種更為複雜的方式進行。而量子力學效應預測,當與超高能物質發生相互作用時,引力的強度在空間和時間中會發生變化,這是一種被稱為非最小耦合的現象。

  研究人員將這一個非最小耦合項納入了他們的暴脹模型中,並觀察了物質和能量的分佈是如何隨着量子效應的漲落變化的。

  最後他們發現,量子修正過的引力效應對物質的影響越強,宇宙就會越快地從暴脹中寒冷、均勻的物質過渡到更熱、更多樣的大爆炸過程中特有的物質。

  “再熱是一個瘋狂的過程,一切都不受控制。我們發現,當時物質之間的相互作用非常強烈,以至於它可以相應地快速放鬆下來,為大爆炸創造了合適的條件。我們以前並不知道會是這個樣子,但這些都是我們用已知的物理學從模擬中得出的結論。這正是讓我們興奮的地方。”

  對於新的發現,其他物理學家認為,關於造成了暴脹階段的提議有數百種,但從暴脹階段到所謂的‘熱大爆炸’之間的過渡卻是人們最不了解的部分,而這篇論文則通過包含多個獨立的場和複雜的動力學在模型中精確地模擬后暴脹階段,開闢了新的領域。這是極具挑戰性的數值模擬,併為研究早期的宇宙非線性動力學提供了最新的技術。

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

※帶您來了解什麼是 USB CONNECTOR  ?

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

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

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

分類
發燒車訊

台灣智慧移動產業協會成軍,電動機車將成下一個兆元產業

「台灣智慧移動產業協會」(SMAT) 在 8 月 21 日宣布成立,結合產官學研各界共同推動智慧運輸與乾淨能源,發展電動機車產業。

SMAT 理事長暨中華經濟研究院副院長王健全指出業者紛紛推出新款電動機車,讓 2019 年成為臺灣電動機車產業元年。期待政府能以穩定的政策持續推動產業發展,讓臺灣業者能夠在技術領先的優勢下,成為全球電動機車產業的規格制定者, 並進一步組成臺灣電動機車國家隊,搶下全球兆元市場。

行政院政務委員龔明鑫表示如何讓電動機車產業結合全台灣兩萬多家的機車行將是重要的下一步,政府已經準備好進行全台巡迴,協助機車行轉型,也讓電動機車的生態體系更加完善。立法委員趙天麟則期許 SMAT 的成立能幫助綠能運輸產業的發展,加速改善空氣環境。電動機車可以減少噪音並控制空氣汙染,能降低 PM 排放量達 49 倍。

王健全也宣布將在 9 月發表首本「 台灣電動機車產業發展白皮書」,並搶先透露白皮書的亮點。白皮書主要由工研院產業科技國際策略發展所協助撰寫,工研院指出台灣有 50 年的機車製造經驗,也是全球重要的電子產品生產國, 在兩大產業皆具備完整的研發、生產與供應鏈管理能力 。2018 年新售電動機車已佔總體機車的 11%,2019 年電動機車整車產值更可望達到 110 億元。白皮書也指出台灣在普通重型電動機車領域領先全球 3 到 5 年,未來有機會能成為下一個兆元產業。

大數據股份有限公司利用大數據網路分析與消費者民意調查,了解台灣消費者對電動機車的態度。大數據營運長林慧珍分析指出,消費者選購電動機車的三大原因是購車補助、追求環保和換電的便利性。調查發現電動機車的消費者和潛在消費者多數為年收入 60 萬人以下的民眾,因此對價格較為敏感。消費者有高達 94% 在意政府補助,67.7% 的民眾對政府補助的金額感到滿意,顯示電動機車的發展還很需要政府補助的支持。此外,64.3% 的使用者滿意換電,但僅有 42.8% 的使用者滿意充電,顯示主流消費者更偏好換電模式。

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

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

【其他文章推薦】

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

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※高價收購3C產品,價格不怕你比較

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

分類
發燒車訊

邊緣計算和多租戶數據中心發展的挑戰和機遇

  迄今為止,影響數據中心規劃和發揮作用的界限通常是明確和一致的,其服務區域通常最多只能擴展到 150 英里的地方。而在數據中心內部,計算和存儲設備等資源以及連接數據中心和訪問網絡的鏈路都是基於可預測的流量負載設計的。 

  這在多租戶數據中心(MTDC)中尤其如此,因為多租戶數據中心的物理位置通常由其租戶的延遲要求決定。例如,靠近證券交易所的多租戶數據中心(MTDC)對那些需要低延遲訪問用戶具有更高的價值。用戶的位置通常決定網絡的邊緣,而數據中心所在的位置更多地取決於網絡延遲。而如今天,這種事情正在發生改變。

 隨着 5G 和物聯網的部署正開始加速,這使得需要超可靠的低延遲(URLL)性能的應用程序有了新的發展趨勢。其中的一個影響是數據中心的服務區域日趨變小,這使得數據中心越來越接近網絡邊緣。如今,數據中心和邊緣計算二者的界限甚至角色已經開始模糊。 

  當然,這並不是一種全新的趨勢。多年來,內容提供商已在其用戶附近部署了越來越多的資源,以支持內容緩存,從而減少了延遲,節省更多成本。但是現在,其他類型的網絡正在尋求強大的用例,並且正在做同樣的事情。多租戶數據中心(MTDC)必須找到一種重新定位自己的方法,雖然一些數據中心運營商網絡因收入下降而陷入困境,但卻在不斷增加的基於邊緣計算的部署中找到了新的機會。

  一、超可靠和低延遲成為容量問題 

  物聯網的延遲要求以及數十億台物聯網設備產生的大量數據進一步推動了這一概念的發展。換句話說,增加容量至關重要。但是,可以部署的光纖數量是有限的,因此運營商必須考慮其他增加帶寬的方法。波分復用(WDM)可能是一個難題,另一個問題是必須縮減數據傳輸的距離。 還有許多縮短數據傳輸路徑的策略。從網絡設計的角度來看,運營商將需要繼續增加“東西”(本地)流量,而不是依賴於在數據中心和邊緣計算之間來回往返較長的“南北”流量。這也將需要更多的并行鏈接來滿足更高的可靠性要求。 

  最重要的是,網絡將需要繼續構建其基於邊緣計算的資源,以便在本地使用更多數據。這不僅使它們能夠滿足超可靠、低延遲(URLL)的要求,而且可以成為節省帶寬的有效策略。

  二、數據中心與雲計算集成應用

  邊緣計算服務將由高度連接的小型設施提供所有這些都會影響多租戶數據中心(MTDC)的設計,並在一定程度上影響其發揮的作用。隨着網絡服務區域的縮減,與傳統多租戶數據中心(MTDC)解決方案相比,部署在邊緣的資源將能夠更好地滿足性能要求。此外,而更低成本、更小的佔地面積、更小服務區域的要求將會進一步挑戰現有的多租戶數據中心(MTDC)業務發展。

  隨着運營商和內容服務提供商不得不適應這種新環境,雲計算將扮演關鍵角色。雲計算服務提供商主要提供大型雲平台,而規模較小的雲計算實例在邊緣計算中佔主導地位。其主要挑戰將是在許多地理位置上擴展分佈式雲計算結構,同時保持提供服務自動化和維持安全性控制。    

  雖然邊緣計算和核心數據中心之間傳輸的數據流量類型將會發生變化,但對現有軟件和控制系統影響不大。基礎設施(尤其是光纖數量)需要增加和改變,邊緣計算到核心流量將推動對更多以太網的需求。隨着不斷髮展的更高密度光纜和網絡設備解決方案能夠滿足這些要求,光纖將成為成功的關鍵。 長途大容量選項以及波分復用應用(例如粗波分復用和密集波分復用)對於帶寬提升也起到重要作用。 

  需要注意的是,下一代網絡不會從頭開始創建。通信運營商將根據自己的情況進行調整,從而構成多供應商的混合系統,這些系統融合了新的和現有的組件。但構建其基礎設施將是複雜的,如何正確實施是一項艱巨的任務。但是如果做得好,可以構建一個更高效、更簡化的網絡,並且可以不斷成長以滿足用戶更多的需求。

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

※高價收購3C產品,價格不怕你比較

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

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

3c收購,鏡頭 收購有可能以全新價回收嗎?

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