​一幀圖像的Android之旅:SurfaceFlinger初見

2021-02-21 小馬Dev
#BEGIN#

上一篇文章學到了應用層請求Vsync的邏輯,其中SF[1]部分由於涉及流程太長跳過了,今天這篇文章就是SF篇了,在接著上章的流程跟蹤之前,需要介紹一些前置信息,一些關鍵部分是如何被初始化的以及他們之間的大概關係,了解了這些之後去跟蹤流程才會相對輕鬆一些.

在Android顯示管道中,SF主要負責其中的一環:接受緩衝區,對它們進行合成,然後發送到屏幕.另外它還負責處理HWVsync[2] 並生產SWVsync[3] 分發給關注此事件的消費者.

從bp文件中,我們可以看到入口文件,所以我們也就從這裡著手開始跟蹤:

filegroup {
    name: "surfaceflinger_binary_sources",
    srcs: ["main_surfaceflinger.cpp"],
}

cc_binary {
    name: "surfaceflinger",
    defaults: ["libsurfaceflinger_binary"],
    init_rc: ["surfaceflinger.rc"],
    srcs: [":surfaceflinger_binary_sources"],
    shared_libs: [
        "libsurfaceflinger",
        "libSurfaceFlingerProp",
    ],
}

main
int main(int, char**) {
    signal(SIGPIPE, SIG_IGN);

    hardware::configureRpcThreadpool(1 /* maxThreads */,
            false /* callerWillJoin */);

    startGraphicsAllocatorService();

    ProcessState::self()->setThreadPoolMaxThreadCount(4);

    sp<ProcessState> ps(ProcessState::self());
    ps->startThreadPool();

    sp<SurfaceFlinger> flinger = surfaceflinger::createSurfaceFlinger();
...
    flinger->init();

    sp<IServiceManager> sm(defaultServiceManager());
    sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,
                   IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);
...
    // run surface flinger in this thread
    flinger->run();

    return 0;
}

mian函數執行時主要執行的大都是一些線程參數以及服務的配置操作:

實例化SurfaceFlinger,並調用init函數.調用SurfaceFlinger.run,進入消息隊列循環.init
class SurfaceFlinger : public BnSurfaceComposer,
                       public PriorityDumper,
                       public ClientCache::ErasedRecipient,
                       private IBinder::DeathRecipient,
                       private HWC2::ComposerCallback,
                       private ISchedulerCallback;

void SurfaceFlinger::init() {
    Mutex::Autolock _l(mStateLock);

    // Get a RenderEngine for the given display / config (can't fail)
    int32_t renderEngineFeature = 0;
    renderEngineFeature |= (useColorManagement ?
                            renderengine::RenderEngine::USE_COLOR_MANAGEMENT : 0);
    renderEngineFeature |= (useContextPriority ?
                            renderengine::RenderEngine::USE_HIGH_PRIORITY_CONTEXT : 0);
    renderEngineFeature |=
            (enable_protected_contents(false) ? renderengine::RenderEngine::ENABLE_PROTECTED_CONTEXT
                                              : 0);
    ...
    mCompositionEngine->setRenderEngine(
            renderengine::RenderEngine::create(static_cast<int32_t>(defaultCompositionPixelFormat),
                                               renderEngineFeature, maxFrameBufferAcquiredBuffers));

    mCompositionEngine->setHwComposer(getFactory().createHWComposer(getBE().mHwcServiceName));
    mCompositionEngine->getHwComposer().registerCallback(this, getBE().mComposerSequenceId);
    // Process any initial hotplug and resulting display changes.
    processDisplayHotplugEventsLocked();
    const auto display = getDefaultDisplayDeviceLocked();
    ...
    if (useVrFlinger) {
        ...
    }
    // initialize our drawing state
    mDrawingState = mCurrentState;

    // set initial conditions (e.g. unblank default device)
    initializeDisplays();

    getRenderEngine().primeCache();
    ...
}

初始化CompositionEngine的各項參數,包括Render與HWC服務.註冊HWC的Listener,當註冊成功後,HWC會首先回調一次onHotplugReceived,SF會調用processDisplayHotplugEventsLocked來處理該事件.進行Shader緩存,這個流程在部分設備上會佔用非常久的時間,但該流程並不是必須的,你可以選擇不在這裡進行Shader緩存,這可以一定程度加速你的系統啟動流程.記住initializeDisplays這裡會向隊列中添加一個消息用於執行onInitializeDisplays,但是由於隊列還未循環起來,所以該事件會在後面執行(SurfaceFlinger.run).onHotplugReceived
void SurfaceFlinger::onHotplugReceived(int32_t sequenceId, hwc2_display_t hwcDisplayId,
                                       HWC2::Connection connection) {
    ...
    ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);

    mPendingHotplugEvents.emplace_back(HotplugEvent{hwcDisplayId, connection});

    if (std::this_thread::get_id() == mMainThreadId) {
        // Process all pending hot plug events immediately if we are on the main thread.
        processDisplayHotplugEventsLocked();
    }
    ...
}

void SurfaceFlinger::processDisplayHotplugEventsLocked() {
    for (const auto& event : mPendingHotplugEvents) {
        const std::optional<DisplayIdentificationInfo> info =
                getHwComposer().onHotplug(event.hwcDisplayId, event.connection);
        if (!info) {
            continue;
        }
        if (event.connection == HWC2::Connection::Connected) {
            if (!mPhysicalDisplayTokens.count(info->id)) {
                ALOGV("Creating display %s", to_string(info->id).c_str());
                if (event.hwcDisplayId == getHwComposer().getInternalHwcDisplayId()) {
                    initScheduler(info->id);
                }
                mPhysicalDisplayTokens[info->id] = new BBinder();
                DisplayDeviceState state;
                state.displayId = info->id;
                state.isSecure = true; // All physical displays are currently considered secure.
                state.displayName = info->name;
                mCurrentState.displays.add(mPhysicalDisplayTokens[info->id], state);
                mInterceptor->saveDisplayCreation(state);
            }
        } else {
            ...
        }
        processDisplayChangesLocked();
    }
    mPendingHotplugEvents.clear();
}

前面我們註冊HWC listener後,HWC通常會迅速上報一個Connected事件.在該事件回調後,針對內置的物理顯示器我們會執行Scheduler的創建初始化工作.initScheduler

從名字也可以大概看出來他的作用啦,主要負責初始化Scheduler,這裡會創建並運行多條線程,主要是如下:

DispSyncThread 用於根據HWVsync生成對應的SWVsync.EventControlThread 用於開關HWVsync.後續還會創建至少兩條線程,分別為EventThread(sf),EventThread(app) 用於處理回調註冊在各自線程上的DisplayEventReceiver.

他們之間的關係如下:

void SurfaceFlinger::initScheduler(DisplayId primaryDisplayId) {
    if (mScheduler) {
        return;
    }
    ...
    // start the EventThread
    mScheduler =
            getFactory().createScheduler([this](bool enabled) { setPrimaryVsyncEnabled(enabled); },
                                         *mRefreshRateConfigs);
    mAppConnectionHandle =
            mScheduler->createConnection("app", mVsyncModulator.getOffsets().app,
                                         mPhaseOffsets->getOffsetThresholdForNextVsync(),
                                         impl::EventThread::InterceptVSyncsCallback());
    mSfConnectionHandle =
            mScheduler->createConnection("sf", mVsyncModulator.getOffsets().sf,
                                         mPhaseOffsets->getOffsetThresholdForNextVsync(),
                                         [this](nsecs_t timestamp) {
                                             mInterceptor->saveVSyncEvent(timestamp);
                                         });

    mEventQueue->setEventConnection(mScheduler->getEventConnection(mSfConnectionHandle));
    mVsyncModulator.setSchedulerAndHandles(mScheduler.get(), mAppConnectionHandle.get(),
                                           mSfConnectionHandle.get());

    mRegionSamplingThread =
            new RegionSamplingThread(*this, *mScheduler,
                                     RegionSamplingThread::EnvironmentTimingTunables());

    mScheduler->setChangeRefreshRateCallback(
            [this](RefreshRateType type, Scheduler::ConfigEvent event) {
                Mutex::Autolock lock(mStateLock);
                setRefreshRateTo(type, event);
            });
}


void MessageQueue::setEventConnection(const sp<EventThreadConnection>& connection) {
    if (mEventTube.getFd() >= 0) {
        mLooper->removeFd(mEventTube.getFd());
    }

    mEvents = connection;
    mEvents->stealReceiveChannel(&mEventTube);
    mLooper->addFd(mEventTube.getFd(), 0, Looper::EVENT_INPUT, MessageQueue::cb_eventReceiver,
                   this);
}

創建Scheduler,注意傳入的第一個參數,是個Lambda表達式,供Scheduler內進行回調,對應的函數為setPrimaryVsyncEnabled(enabled),其真正作用是控制HWVsync的開關.通過Scheduler構造兩個ConnectionHandle[4],分別為sf和app,用來處理不同的Connection.(SWVsync都是通過Connection來通知消費者的).setEventConnection這個函數實現是不是很熟悉的感覺,是的,這裡作為sf Vsync的消費者,其實現與上一章的DisplayEventReceiver裡的處理步驟是完全一樣的.創建Scheduler
Scheduler::Scheduler(impl::EventControlThread::SetVSyncEnabledFunction function,
                     const scheduler::RefreshRateConfigs& refreshRateConfig)
      : mHasSyncFramework(running_without_sync_framework(true)),
        mDispSyncPresentTimeOffset(present_time_offset_from_vsync_ns(0)),
        mPrimaryHWVsyncEnabled(false),
        mHWVsyncAvailable(false),
        mRefreshRateConfigs(refreshRateConfig) {
    // Note: We create a local temporary with the real DispSync implementation
    // type temporarily so we can initialize it with the configured values,
    // before storing it for more generic use using the interface type.
    auto primaryDispSync = std::make_unique<impl::DispSync>("SchedulerDispSync");
    primaryDispSync->init(mHasSyncFramework, mDispSyncPresentTimeOffset);
    mPrimaryDispSync = std::move(primaryDispSync);
    mEventControlThread = std::make_unique<impl::EventControlThread>(function);

    ...
}

在Scheduler被構造時,它會同時構造創建如下關鍵類:

創建並啟動EventControlThread,注意這裡將我們之前傳入的callback直接給到了EventControlThread.真正調用callback的地方是EventControlThread.創建DispSync
DispSync::DispSync(const char* name) : mName(name), mRefreshSkipCount(0) {
    // This flag offers the ability to turn on systrace logging from the shell.
    char value[PROPERTY_VALUE_MAX];
    property_get("debug.sf.dispsync_trace_detailed_info", value, "1");
    mTraceDetailedInfo = atoi(value);
    mThread = new DispSyncThread(name, mTraceDetailedInfo);
}

void DispSync::init(bool hasSyncFramework, int64_t dispSyncPresentTimeOffset) {
    mIgnorePresentFences = !hasSyncFramework;
    mPresentTimeOffset = dispSyncPresentTimeOffset;
    mThread->run("DispSync", PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE);

    // set DispSync to SCHED_FIFO to minimize jitter
    struct sched_param param = {0};
    param.sched_priority = 2;
    if (sched_setscheduler(mThread->getTid(), SCHED_FIFO, &param) != 0) {
        ALOGE("Couldn't set SCHED_FIFO for DispSyncThread");
    }

    android_set_rt_ioprio(mThread->getTid(), 1);

    beginResync();

    if (mTraceDetailedInfo && kEnableZeroPhaseTracer) {
        mZeroPhaseTracer = std::make_unique<ZeroPhaseTracer>();
        addEventListener("ZeroPhaseTracer", 0, mZeroPhaseTracer.get(), 0);
    }
}

DispSync的構成也比較簡單,其內部會維護一條線程DispSyncThread.在init啟動並設置DispSyncThread.beginResync的作用是為後續通過HWVsync更新模型參數,重置所有參數,並unlock sync模型.線程工作起來後,將會執行到threadLoop函數.具體其工作時的流程我們後面再細跟.創建EventControlThread
EventControlThread::EventControlThread(EventControlThread::SetVSyncEnabledFunction function)
      : mSetVSyncEnabled(function) {
    pthread_setname_np(mThread.native_handle(), "EventControlThread");

    pid_t tid = pthread_gettid_np(mThread.native_handle());
    setpriority(PRIO_PROCESS, tid, ANDROID_PRIORITY_URGENT_DISPLAY);
    set_sched_policy(tid, SP_FOREGROUND);

    android_set_rt_ioprio(tid, 1);
}

EventControlThread::~EventControlThread() {
    {
        std::lock_guard<std::mutex> lock(mMutex);
        mKeepRunning = false;
        mCondition.notify_all();
    }
    mThread.join();
}

void EventControlThread::setVsyncEnabled(bool enabled) {
    std::lock_guard<std::mutex> lock(mMutex);
    mVsyncEnabled = enabled;
    mCondition.notify_all();
}

// Unfortunately std::unique_lock gives warnings with -Wthread-safety
void EventControlThread::threadMain() NO_THREAD_SAFETY_ANALYSIS {
    auto keepRunning = true;
    auto currentVsyncEnabled = false;

    while (keepRunning) {
        mSetVSyncEnabled(currentVsyncEnabled);

        std::unique_lock<std::mutex> lock(mMutex);
        mCondition.wait(lock, [this, currentVsyncEnabled, keepRunning]() NO_THREAD_SAFETY_ANALYSIS {
            return currentVsyncEnabled != mVsyncEnabled || keepRunning != mKeepRunning;
        });
        currentVsyncEnabled = mVsyncEnabled;
        keepRunning = mKeepRunning;
    }
}

這條線程的作用和代碼比較簡單,一眼就可以全部看完,就是負責調用mSetVSyncEnabled,開關HWVsync.mSetVSyncEnabled就是我們在initScheduler內傳入的Lambda:setPrimaryVsyncEnabled(enabled).調用setVsyncEnabled函數等價於調用SurfaceFlinger.setPrimaryVsyncEnabled.創建EventThread
sp<Scheduler::ConnectionHandle> Scheduler::createConnection(
        const char* connectionName, nsecs_t phaseOffsetNs, nsecs_t offsetThresholdForNextVsync,
        impl::EventThread::InterceptVSyncsCallback interceptCallback) {
    const int64_t id = sNextId++;
    ALOGV("Creating a connection handle with ID: %" PRId64 "\n", id);

    std::unique_ptr<EventThread> eventThread =
            makeEventThread(connectionName, mPrimaryDispSync.get(), phaseOffsetNs,
                            offsetThresholdForNextVsync, std::move(interceptCallback));

    auto eventThreadConnection =
            createConnectionInternal(eventThread.get(), ISurfaceComposer::eConfigChangedSuppress);
    mConnections.emplace(id,std::make_unique<Connection>(new ConnectionHandle(id),
                                                      eventThreadConnection,
                                                      std::move(eventThread)));
    return mConnections[id]->handle;
}

std::unique_ptr<EventThread> Scheduler::makeEventThread(
        const char* connectionName, DispSync* dispSync, nsecs_t phaseOffsetNs,
        nsecs_t offsetThresholdForNextVsync,
        impl::EventThread::InterceptVSyncsCallback interceptCallback) {
    std::unique_ptr<VSyncSource> eventThreadSource =
            std::make_unique<DispSyncSource>(dispSync, phaseOffsetNs, offsetThresholdForNextVsync,
                                             true, connectionName);
    return std::make_unique<impl::EventThread>(std::move(eventThreadSource),
                                               std::move(interceptCallback), connectionName);
}

EventThread::EventThread(VSyncSource* src, std::unique_ptr<VSyncSource> uniqueSrc,
                         InterceptVSyncsCallback interceptVSyncsCallback, const char* threadName)
      : mVSyncSource(src),
        mVSyncSourceUnique(std::move(uniqueSrc)),
        mInterceptVSyncsCallback(std::move(interceptVSyncsCallback)),
        mThreadName(threadName) {
    if (src == nullptr) {
        mVSyncSource = mVSyncSourceUnique.get();
    }
    mVSyncSource->setCallback(this);
    mThread = std::thread([this]() NO_THREAD_SAFETY_ANALYSIS {
        std::unique_lock<std::mutex> lock(mMutex);
        threadMain(lock);
    });
    pthread_setname_np(mThread.native_handle(), threadName);
...
}

上面scheduler的創建流程執行結束,那麼接下來就是創建eventThread.針對單個Connection可以傳入不同的偏移量,即Vsync偏移,該值具體作用以後細看.首先需要創建DispSyncSource,注意這裡我們將vsync偏移設置給你它.這個類的作用實際上是將EventThread和DispSyncThread聯繫起來.接下來創建EventThread,需要將之前創建的DispSyncSource以及connectionName(sf/app)傳入,其內部會調用DispSyncSource的setCallBack將自己設置為callback.以上創建的兩個對象,會被封裝為Connection,以id為key,裝入mConnections保存起來.

以上幾條線程正常工作時的關係大致如下圖所示,圖中的細節我們的文章後面都會慢慢涉及到:

onInitializeDisplays

以上,SurfaceFlinger的初始化工作就基本完成了,但是還有關鍵的一點主線程循環還未開始,並且由於顯示設備還未打開HWVsync還未上報,所以DispSync也還未進入正常循環.記得我們前面有說到的,initializeDisplays裡面post了一個消息到隊列中,而當flinger.run執行到,主線程循環就進入消息隊列循環了,這個消息也會首先得到執行:

void SurfaceFlinger::onInitializeDisplays() {
...
    setPowerModeInternal(display, HWC_POWER_MODE_NORMAL);
...
}

void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, int mode) {
    ...
    if (currentMode == HWC_POWER_MODE_OFF) {
        // Turn on the display
        getHwComposer().setPowerMode(*displayId, mode);
        if (display->isPrimary() && mode != HWC_POWER_MODE_DOZE_SUSPEND) {
            setVsyncEnabledInHWC(*displayId, mHWCVsyncPendingState);
            mScheduler->onScreenAcquired(mAppConnectionHandle);
            mScheduler->resyncToHardwareVsync(true, getVsyncPeriod());
        }

        mVisibleRegionsDirty = true;
        mHasPoweredOff = true;
        repaintEverything();

        struct sched_param param = {0};
        param.sched_priority = 1;
        if (sched_setscheduler(0, SCHED_FIFO, &param) != 0) {
            ALOGW("Couldn't set SCHED_FIFO on display on");
        }
    } else if (mode == HWC_POWER_MODE_OFF) {
    ...
}

這個函數作用很關鍵,但是流程很簡單,就是打開顯示設備以及開啟該設備的HWVsync

首先判斷當前display的開關狀態,若為關閉狀態則通過HWC進入打開流程.設置合成引擎的狀態,以及調用resyncToHardwareVsync開啟HWVsync,並且傳入一個period,這個值是從hal獲取到的config中取到的,表示顯示設備的刷新周期,例如60HZ的顯示設備,該值為16.666666.resyncToHardwareVsync
void Scheduler::resyncToHardwareVsync(bool makeAvailable, nsecs_t period) {
    {
        std::lock_guard<std::mutex> lock(mHWVsyncLock);
        if (makeAvailable) {
            mHWVsyncAvailable = makeAvailable;
        } else if (!mHWVsyncAvailable) {
            return;
        }
    }
    if (period <= 0) {
        return;
    }
    setVsyncPeriod(period);
}

void Scheduler::setVsyncPeriod(const nsecs_t period) {
    std::lock_guard<std::mutex> lock(mHWVsyncLock);
    mPrimaryDispSync->setPeriod(period);

    if (!mPrimaryHWVsyncEnabled) {
        mPrimaryDispSync->beginResync();
        mEventControlThread->setVsyncEnabled(true);
        mPrimaryHWVsyncEnabled = true;
    }
}

首先,這裡會將之前獲取到的顯示設備刷新周期設置到DispSync中保存在mPendingPeriod中.另外根據咱們之前對各個線程的了解,我們已經知道setVsyncEnabled會喚醒EventControlThread打開HWVsync,而HWVsync是通過之前註冊到HWC的ComposerCallback(SurfaceFlinger)來通知的.

到這裡,SurfaceFlinger的線程進入循環準備接受HWVsync並設置其他線程依賴的參數使其他線程也可以進入正常的循環中.所以下一篇預計將要學習一下SurfaceFlinger是如何處理HWVsync信號的.DispSync是按照怎樣的一個模型生成SWVsync的,生成的SWVsync又是如何和我們第一篇分析的流程接上的.

參考資料[1]

SF: SurfaceFlinger

[2]

HWVsync: 指由顯示設備產生的垂直同步信號。

[3]

SWVsync: 指軟體模擬產生的垂直同步信號。

[4]

ConnectionHandle: 一個不透明句柄,用於標識一個Connection,

相關焦點

  • Android Systrace 基礎知識(5) - SurfaceFlinger 解讀
    App 一幀的流程從 SurfaceFlinger 的角度來看,App 部分主要負責生產 SurfaceFlinger 合成所需要的 Surface。應用收到這個信號後,開始一幀的渲染準備MessageQueue::INVALIDATE --- 主要是執行 handleMessageTransaction 和 handleMessageInvalidate 這兩個方法MessageQueue::REFRESH --- 主要是執行 handleMessageRefresh 方法frameworks/native/services/surfaceflinger
  • Android Systrace 基礎知識(7) - Vsync 解讀
    文章會從 Systrace 的角度來看 Android 系統如何基於 Vsync 每一幀的展示。Vsync 是 Systrace 中一個非常關鍵的機制,雖然我們在操作手機的時候看不見,摸不著,但是在 Systrace 中我們可以看到,Android 系統在 Vsync 信號的指引下,有條不紊地進行者每一幀的渲染、合成操作,使我們可以享受穩定幀率的畫面。
  • android 播放gif動態圖片
    android不推薦使用gif圖片,一般都是png的,對於gif的圖片解析比較消耗資源,但是對於一些動態gif圖片的播放,如果比較小的話還是可以的,要是大的話,建議還是把gif圖片轉換成一幀一幀的png圖片,然後通過animation播放。
  • Android SVG使用之AnimatedVectorDrawable
    android:alpha 該圖片的透明度屬性<group>設置路徑做動畫的關鍵屬性的android:name 定義group的名字android:rotation 定義該group的路徑旋轉多少度android:pivotX 定義縮放和旋轉該group時候的X參考點。
  • Android Systrace 基礎知識(9)-MainThread 和 RenderThread 解讀
    Systrace 基礎知識 - MainThread 和 RenderThread 解讀[9]Systrace 基礎知識 - Binder 和鎖競爭解讀[10]Systrace 基礎知識 - Triple Buffer 解讀[11]Systrace 基礎知識 - CPU Info 解讀[12]正文這裡以滑動列表為例 ,我們截取主線程和渲染線程「一幀
  • Android圖形渲染原理(上)
    屏幕圖像顯示原理在講解Android圖像的顯示之前,我會先講一下屏幕圖像的顯示原理,畢竟我們圖像,最終都是在手機屏幕上顯示出來的,了解這一塊的知識會讓我們更容易的理解Android在圖像顯示上的機制。剛剛說了,屏幕上的像素數據是從左到右,從上到下進行同步的,當這個過程完成了,就表示一幀繪製完成了,於是會開始下一幀的繪製,大部分的顯示屏都是以60HZ的頻率在屏幕上繪製完一幀,也就是16ms,並且每次繪製新的一幀時,都會發出一個垂直同步信號(VSync)。
  • 社區營造文化之旅:金門初見 兩岸學子共敲和平鍾
    本期體驗營以「社區營造文化之旅」為主題,在金門開營,兩岸30名大學生營員通過參訪金門大學、瓊林社區、燕南書院、臺北社區大學、三峽老街、北埔社區、鹿港民俗文化館、埔裡桃米社區等地,一窺臺灣的社區營造文化。
  • Android相機開發詳解
    為了在預覽和拍攝時,圖像不會出現拉伸現象,預覽幀的長寬比最好和顯示控制項的長寬比一致,並且拍攝幀的長寬比也和預覽幀和顯示控制項的長寬比一致,總之三者的長寬比最好是一致的,才會有最好的預覽和拍攝效果因為手機預覽控制項的圖像 是由 相機預覽幀 根據 控制項大小 縮放得來的,當長寬比不一致時必然會導致預覽圖像變形。
  • 【學習】Android入門開發​​2-5RadioButton
    package com.example.helloworld;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.widget.RadioButton;import android.widget.RadioGroup;import
  • android是什麼意思 Android組件和布局介紹
    六大布局簡述①LinarLayout---線性布局:容器中的組件一個挨一個排列,通過控制android:orientation屬性,可控制各組件是橫向排列還是縱向排列。③FrameLayout---幀布局:為每個加入其中的組件創建一個空白的區域(稱為一幀),每個子組件佔據一幀,這些幀會根據gravity屬性執行自動對齊。④RelativeLayout---相對布局:該布局是子布局相對於父布局的相對位置。
  • 面試官問我:Android APP中如何測試FPS?看我如何分析京東,拼多多App的FPS.
    6.打開D:\test.text文檔的內容,如圖所示:com.victor.intercitycarpool/com.victor.intercitycarpool.ui.account.RegisterOneActivity/android.view.ViewRootImpl
  • 《我和我的家鄉》張家口版!​每一幀都想截屏
    《我和我的家鄉》張家口版!​每一幀都想截屏 2020-10-09 09:33 來源:澎湃新聞·澎湃號·媒體
  • Android開發之:Toast和Notification
    package com.a3gs.toast;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.EditText
  • 張嘉芸:《初見》 | 科中之聲
    序「人生若如只初見,何事秋風悲畫扇?」納蘭性德的這句詞,極盡婉轉傷感之韻味,短短一句勝過千言萬語,人生種種不可言說的複雜滋味都仿佛因這一句而湧上心頭,叫人感慨萬千。我與播音的初見是在一年前,點開一條「荔枝FM」的推送,戴上耳機,主播的聲音立即浸入耳蝸,一個個音節,一個個故事,無不扣人心弦。
  • Android中的窗口——Activity
    > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">android:text="Activity1" android:onClick= "onClick_Activity1" />
  • Android基礎 - 如何做魯棒性更高的布局
    典型的「盒子」即繼承自父類「ViewGroup」的相對布局 RelativeLayout 以及線性布局 LinearLayout,當然了還有滾動布局 ScrollView、網格布局 GridLayout 等十分的多,而我們日常使用到的按鈕 Button、圖像ImageView、文本TextView則不是「盒子」了,他們只能作為子布局放在盒子內,以上,就是盒子模型的簡單陳述了。
  • 放蕩不羈SVG講解與實戰——Android高級UI
    ><vector xmlns:android="http://schemas.android.com/apk/res/android"    android:width="xxdp"    android:height="yydp"    android:viewportWidth="xx"    android:viewportHeight="yy
  • Android之Binder底層原理詳解必讀
    asBinder(){return this;}@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{switch (code){case INTERFACE_TRANSACTION
  • Android之自定義EditText光標和下劃線顏色
    app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height