Androidにおける画面フリーズ・ブラックアウトのデバッグ手法

Androidシステム開発において、画面が更新されなくなる「フリーズ」や完全に黒くなる「ブラックアウト」は頻出する深刻な問題の一つである。これらの現象は、グラフィックスサブシステムのどこかでデータの流れが途絶えていることを示しており、原因特定にはSurfaceFlingerを中心としたレンダリングパイプラインの理解が不可欠である。

特にAndroid 12以降のバージョンでは、CompositionEngineを介したレイヤー合成処理が主要なパスとなっている。画面が黒い状態でもバックライトが点いており、明確に「スクリーンオフ」ではない場合、アプリケーションから送られた描画内容が最終的にディスプレイに出力されていない可能性が高い。このようなケースでは、SurfaceFlingerのリフレッシュループ内でどのレイヤーが可視と判定されているかを調査することが有効である。

以下に、可視レイヤーの収集処理における重要なコードパスを示す(Android 12ベース):

void SurfaceFlinger::onMessageRefresh() {
    mCompositionEngine->present(refreshArgs);
}

void CompositionEngine::present(CompositionRefreshArgs& args) {
    for (const auto& output : args.outputs) {
        output->prepare(args, latchedLayers);
    }
}

void Output::prepare(const compositionengine::CompositionRefreshArgs& refreshArgs,
                     LayerFESet& geomSnapshots) {
    rebuildLayerStacks(refreshArgs, geomSnapshots);
}

void Output::collectVisibleLayers(const compositionengine::CompositionRefreshArgs& refreshArgs,
                                  compositionengine::Output::CoverageState& coverage) {
    for (auto layer : reversed(refreshArgs.layers)) {
        ensureOutputLayerIfVisible(layer, coverage);
    }
}

void Output::ensureOutputLayerIfVisible(sp<compositionengine::LayerFE>& layerFE,
                                        compositionengine::Output::CoverageState& coverage) {
    auto result = ensureOutputLayer(prevOutputLayerIndex, layerFE);
    if (result.has_value()) {
        layerFE->logHierarchy();
    }
}

上記のensureOutputLayerIfVisible内で、実際に可視と判断されたレイヤーに対して階層情報を出力するように拡張することで、現在画面に表示されるべきレイヤー構造を把握できる。このためには、LayerFEインターフェースに仮想関数を追加し、各実装クラスで親子関係を再帰的にトレースする必要がある:

class LayerFE : public virtual RefBase {
public:
    virtual void logHierarchy() = 0;
};

class BufferLayer : public Layer, public compositionengine::LayerFE {
public:
    void logHierarchy() override {
        if (sp<Layer> parent = getParent()) {
            parent->logHierarchy();
        }
        ALOGI("VisibleLayer: %s", mName.c_str());
    }
};

この修正により、リフレッシュ時に可視とされたすべてのレイヤーとその親階層がログに出力される。例えば次のような出力が得られる:

04-12 14:37:57.714 I VisibleLayer: Root#0
04-12 14:37:57.715 I VisibleLayer: ScreenDecorOverlay#0
04-12 14:37:57.715 I VisibleLayer: WindowToken{6e71384 android.os.BinderProxy@26cb1d8}#0
04-12 14:37:57.715 I VisibleLayer: Leaf:24:25#0

もし期待されるアプリケーションウィンドウがこのリストに含まれていない場合、そのレイヤーが非表示と判定されている(例:Z-orderが低すぎる、visibilityフラグがfalse、bounds外など)ことが原因と考えられる。逆に、リストに含まれているにもかかわらず画面が黒い場合は、バッファの内容が空(black frame)であったり、HWC/HALレイヤーでの合成エラーが疑われる。

タグ: Android SurfaceFlinger GraphicsSystem CompositionEngine DisplayDebug

6月14日 18:36 投稿