TRILL Tech Blog

国内最大級の女性向けメディアTRILLの開発ブログです。

Xcode OrganizerのScroll Hitch Rateについて

TRILL開発部の石田です。

2020年9月にXcode12がリリースされ、Scroll Hitch Rateという機能が追加されました。 今回はこの機能について紹介します。

Xcode Organizerとは

f:id:trill_tech:20201208130019j:plain

Xcode Organizerについて、Appleのドキュメントでは以下のように説明されています。

Appのクラッシュログ、エネルギーレポート、パフォーマンスに関する指標(お客様が使用した際のバッテリー消費量や起動時間など)を簡単に確認できます。

ユーザの端末からバッテリーライフやパフォーマンスデータ等の情報がAppleのサーバに送られ、それがXcodeのOrganizerに表示されます。 ただし情報を送信するのはプライバシー設定の「Appデベロッパと共有」をOnにしている端末に限られるようです。

ちなみに、Organizerでユーザの統計情報を確認するために追加の実装は不要です。

Scroll Hitch Rateとは

f:id:trill_tech:20201208130043p:plain

Scroll Hitch RateはXcode12からOrganizerに追加された機能で、アプリ内のスクロールのスムーズさを表現しています。

Scroll Hitchとはレンダリングされたフレームがスクロール中に画面に表示されないことで、これによってフレーム落ちし、スクロールが不安定な挙動となります。

iPhoneはフレッシュレートが60Hzなので、1フレームは16.67msであり、それ以上の時間がかかるとフレーム落ちし、ユーザ体験が下がります。

表示される指標

f:id:trill_tech:20201208130102p:plain

  • Hitch time: フレームが画面に表示されるのに必要な追加の時間の合計
  • Scroll duration: スクロール時間
  • Hitch rate = Hitch time / Scroll duration

Hitch rateが高ければ高いほどHitchが多く、ユーザにとって体験の悪いスクロールとなります。 逆にHitch rateが低いほどユーザ体験の良いスクロールとなります。

目指すべき数値

f:id:trill_tech:20201208130117p:plain

  • 5ms/s以下: 良いユーザ体験
  • 5ms〜10ms/s: ユーザがHitchに気づき始めるので調査すべき
  • 10ms〜: かなり使いづらいので早急に解決すべき

基本的に5ms/sを下回っていれば問題ないようです。 Xcode Organizerはアプリバージョン毎の結果が表示されるので、アプリをアップデートした際に改善しているか・悪化していないかチェックするのが良さそうです。

まとめ

Xcode12からScroll Hitch Rateという機能が追加され、スクロールのスムーズさ(ユーザの手元で起こっているもの)が定量的に判断できるようになりました。 TRILLでも定期的な確認と改善をしていき、ユーザ体験をより良いものにしたいと思います。

delyでは全方面でエンジニアを積極採用中です。 興味のある方は是非お声がけください。

join-us.dely.jp

参考

https://developer.apple.com/videos/play/wwdc2020/10076/

https://developer.apple.com/videos/play/wwdc2020/10077/

Swiftで1+1が何故2になるのか調べてみた

TRILL開発部の石田です。

この記事は「dely #2 Advent Calendar 2020」2日目の記事です。

dely #1 Advent Calendar 2020 - Adventar

dely #2 Advent Calendar 2020 - Adventar

昨日はsacoさんの記事「ノンデザイナーでも大丈夫!見やすいプレゼン資料をつくる6つの手順」でした。 デザイナーの視点から、分かりやすいプレゼンの作り方を順序立てて解説しているので是非ご覧ください。

さて、大学生のとき「1+1=2の証明」を授業で習ったのですが、小学生のとき当たり前のように教えられた自然数の足し算が大学の数学で証明され、数学の奥深さに触れた気がして今でも記憶に残っています。

そんなことを思い出して、普段書いているSwift内ではどうやって 1 + 1 が 2 であることを計算しているのか調べてみました。


Xcode上で、1 + 1 の加算演算子 + からCmd+Ctlで定義にジャンプしてみます。

f:id:trill_tech:20201106190539p:plain:w256

定義は以下のようになっています。

public protocol AdditiveArithmetic : Equatable {
  
  public static func + (lhs: Int, rhs: Int) -> Int

}

加算演算子 +Equatable に準拠した AdditiveArithmetic というプロトコルの中で定義されています。 しかしこれではインターフェースが分かっても実装が分かりません。 実装を確認するため、GitHubに公開されているSwiftのソースコードを見にいきます。

上記の加算演算子コードは Integers.swift というファイルにありました。しかしこちらもインターフェースのみで実装がありません。

どうやら実際の実装はGitHub上では見ることができず、gybファイルからビルド時に生成されるようです。 gybは Generate Your Boilerplate の略で、Pythonで記述するテンプレートシステムです。 該当のgybファイルは IntegerTypes.swift.gyb にあります。

このままでは実装が見られないので、ガイドに従ってSwiftのソースコードをビルドします。 全部ビルドしなくても gyb.py を使って IntegerTypes.swift.gyb だけをSwiftファイルに変換することもできます。

ビルドすると IntegerTypes.swift というファイルが生成されます。加算演算子 + の実装を見てみます。

@_transparent
public static func +(lhs: Int, rhs: Int) -> Int {
  var lhs = lhs
  lhs += rhs
  return lhs
}

加算演算子 + は内部的に加算代入演算子 += を使っているようです。

加算代入演算子 += の実装を見てみます。

@_transparent
public static func +=(lhs: inout Int, rhs: Int) {
  let (result, overflow) = Builtin.sadd_with_overflow_Int64(lhs._value, rhs._value, true._value)
  Builtin.condfail_message(overflow, StaticString("arithmetic overflow").unsafeRawPointer)
  lhs = Int(result)
}

それらしいコードが出てきました。 Builtin.sadd_with_overflow_Int64() という関数が実際に加算をしているようです。

この関数を使って実際に加算ができるか試してみます。

import Swift

let a: Int = 1
let b: Int = 1
let c: Builtin.Int1 = Builtin.trunc_Int8_Int1(Int8(0)._value)

let (result, overflow) = Builtin.sadd_with_overflow_Int64(a._value, b._value, c)

print(Int(result))

実行するために多少面倒な定義をしています。 Builtin を使うため -parse-stdlib オプションを付けて実行します。

$ swift -parse-stdlib addition.swift
# 2

ちゃんと 2 が出力されました。

加算演算子 + が内部的に Builtin.sadd_with_overflow_Int64() という関数を使っていることが確認できました。 しかし Builtin モジュールは組み込み関数にアクセスするものなので、これが内部で何を行っているのかが分かりません。

簡単なSwiftコードを作成し、それがLLVMの中間表現でどう書かれているかを見てみます。 LLVMはコンパイル基盤で、中間表現を経由しながら最適化を行い、最終的に機械語が生成されます。 1 + 1 だと分かりづらいので値を変えます。

let a = 1234
let b = 5678
let c = a + b

このコードを中間表現であるLLVM IRに変換します。

$ swiftc -emit-ir addition.swift

LLVM IRへの変換結果(抜粋)は以下のようになります。

...

store i64 1234, i64* getelementptr inbounds (%TSi, %TSi* @"$s8addition1aSivp", i32 0, i32 0), align 8
store i64 5678, i64* getelementptr inbounds (%TSi, %TSi* @"$s8addition1bSivp", i32 0, i32 0), align 8
%3 = load i64, i64* getelementptr inbounds (%TSi, %TSi* @"$s8addition1aSivp", i32 0, i32 0), align 8
%4 = load i64, i64* getelementptr inbounds (%TSi, %TSi* @"$s8addition1bSivp", i32 0, i32 0), align 8
%5 = call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %3, i64 %4)

...

なんとなくですが、1234と5678をstoreし、loadし、加算する流れが分かります。 この @llvm.sadd.with.overflow.i64 という命令がSwiftの Builtin.sadd_with_overflow_Int64() に相当するようです。

@llvm.sadd.with.overflow.i64 が加算していることは分かったのですが、実際にはどのように実行されているのでしょうか。

今度はSwiftをbitcodeに変換し、そこからアセンブリを出力します。

$ swiftc -emit-bc addition.swift > addition.bc
$ llc addition.bc 

llc コマンドは brew install llvm でLLVMをインストールすることで使えるようになります。

アセンブリの抜粋は以下のようになります。

...

movq $1234, _$s8addition1aSivp(%rip) ## imm = 0x4D2
movq $5678, _$s8addition1bSivp(%rip) ## imm = 0x162E
movl $1234, %eax                     ## imm = 0x4D2
addq $5678, %rax                     ## imm = 0x162E

...

addq という命令が実行され、加算されていることが分かります。 これがプロセッサの加算器で処理されるようです。

まとめ

Swiftで1+1が何故2になるのか調べました。 1+1=2というプリミティブなコードではありますが、普段iOSアプリを開発しているときには触れることの少ないSwiftのソースコードやLLVM IR、アセンブリの中身を垣間見ることができ、楽しい経験ができました。

明日はGENさんの記事「Athena(Presto) × Redash で湯婆婆を実装してみる」です!お楽しみに!

delyでは全方面でエンジニアを積極採用中です! 興味のある方は是非お声がけください!

join-us.dely.jp

TechTalkという社内のメンバーがテーマ毎に話すイベントもありますのでこちらも是非!

bethesun.connpass.com

TRILLアプリのウィジェット設定方法

f:id:trill_tech:20201015160259p:plain:w300

ウィジェットはiOS14以降で利用できる機能です。OSをアップデートのうえご利用ください。

また、App StoreよりTRILLアプリバージョン3.5.0以降にアップデートしてください。

設定方法

1. ホーム画面で何も表示されていない部分を長押しし、上部の+ボタンをタップします

f:id:trill_tech:20201015160809p:plain:w300

2. TRILLアプリを選択します

f:id:trill_tech:20201015160824p:plain:w300

3. ウィジェットの種類を選択し、「ウィジェットを追加」をタップします

f:id:trill_tech:20201015161302p:plain:w300

4. ウィジェットを配置します

f:id:trill_tech:20201015160259p:plain:w300

TRILLのPdMって何してるの?

はじめまして。TRILL開発部PdMの米田です。
主にTRILLアプリ開発のマネジメントを担当しています。
TRILLというプロダクトの開発について、非技術者の視点であれこれご紹介できればと思います。

PdMと一口に言っても様々定義がある中で、今回は「TRILLのPdMって何をしてるの?」という話をしてみようと思います。

大まかに、何をしているの?

  • サービス全体のKPI目標達成に向け、施策を考える
  • 各所のステークホルダーとあれこれすり合わせる
  • 考えた施策たちの優先度を判断する
  • エンジニアとコミュニケーションをとり、開発タスクに落とす
  • 施策の効果を検証する
  • ↑これらのスケジュールを管理する

基本的にはこのサイクルをぐるぐる回すのが仕事です。

その過程においてトラブルが発生すればその対応を行ったり、チームの決まりごとを作ったりといった細々したものは発生しますが、基本的には施策を実行してサービスを開発視点で改善していく上で舵を切ることがメインの業務です。

施策を考える

施策には大きく2種類あると考えています。

ひとつは、プロダクトの「負」を解消し体験を良くするもの。
例えばアプリにおける各挙動の速度であったり、クラッシュを減らしたりといったものです。

ユーザーが

  • アプリを開き
  • 記事に出会い
  • 記事を読み
  • 別の記事に出会い
  • また記事を読み
  • 気に入った記事をお気に入りにストックし
  • 知人等に記事やアプリを薦める

といった一連の行動をいかにストレスなく行えるかを考え、日々コツコツ「負」を解消していっています。

そしてもうひとつが、数字を積み上げるための改善施策です。

事業として定めるKPIに対し、どこに大きな課題があり、その課題をどう改善していくかを開発視点で考えていきます。

ここは開発内だけでなく定例の場や日頃のやり取りの中でマーケチームに相談するようにしています。

施策を推し進める

日々出てくる課題に対しての打ち手(施策)が出たら、それらの優先度を判断してどこから手を付けるかを意思決定します。

この判断を誤ると、事業に対する成果やコストに影響が及ぶので、PdMにとって施策の優先度判断というのは非常に重要な業務です。

また、施策の優先度判断をする上で、他部署他職種の担当者などその施策に関わるステークホルダーとの調整が発生する場合があります。
TRILLにおいては、ここの調整を行うのもPdMの仕事です。

これらを踏まえ、サービス全体を俯瞰して何を優先すればよいかを考慮する必要があるため、開発以外の状況もある程度常にキャッチアップし、適切な判断を下す必要があります。(勉強不足を痛感する日々です)

開発する

施策の方針がある程度固まったら、エンジニアと話し合い、タスクに落としていきます。

便宜上ここで初めてエンジニアが登場していますが、もちろん前段階の施策の優先度を判断するタイミングでエンジニアに意見を求めたりということも頻繁にあります。(自分が非技術者ということもあり、判断に誤りがないようエンジニアとは非常に密なコミュニケーションをとっています)

ちなみにですが、TRILLのアプリ開発は2週で1スプリントのスクラムを採用しています。

少数での開発のため、以前は特にフレームワークに則らずよしなに開発を進めていたのですが、タスクが可視化されず管理がうまくいかなかったり、それによってスケジュール調整がうまくいかなかったりという問題がありました。

これらを解消すべく、エンジニアからの提案によってスクラムのフレームワークに則って開発を進めることにしました。

現在は比較的シンプルな開発フローが実現できています。

f:id:trill_tech:20201120134458p:plain

リリース・効果検証

スプリントバックログに積んだタスクは、開発を終えると新しいバージョンに載り、リリースされます。

TRILLではリリース作業自体はエンジニアが行いますが、リリースの責任はPdMが持つため、リリース内容は申請前のタイミングで必ず目を通します。

また「負」の解消にせよ積み上げの施策にせよ、開発したものは必ずリリース後にその効果を検証して省みる必要があります。

事前に定めた良し悪しの判断軸と照らし合わせて、次のアクションを検討していきます。ここまでが施策のワンセットです。

さいごに

こうして整理してみるとPdMとしてめちゃくちゃ特別ななにかをしているわけではありませんが、プロダクト開発の方向性を示し、ひとつひとつの判断に責任を持つという点でとても意味のあるポジションだという自覚を持っています。

そしてエンジニアをはじめとしたチームメンバーと肩を組み、スピード感をもった開発ができています。

もしTRILLの開発にご興味をお持ちいただけた方は、下記よりご連絡ください。ぜひ一度お話ししましょう!

積極募集中

www.wantedly.com www.wantedly.com www.wantedly.com www.wantedly.com

社内ライブラリをSwiftPMに対応させる

TRILL開発部の石田です。

TRILLでは、Swagger Codegenで生成したAPIクライアントライブラリを使ってサーバと通信しています。 このライブラリはGitHubで管理しており、Carthage経由で利用しています。

Xcode11からSwift Package Manager (以下SwiftPM) がサポートされたということで、上記ライブラリをSwiftPMに対応させてみました。

Swagger Codegen製APIクライアントライブラリ

Swaggerは、REST APIを記述するための仕様であり、その仕様からクライアントのライブラリや、サーバのスタブを自動生成するツールがSwagger Codegenです。 TRILLのクライアントアプリでは、Swagger Codegenで生成されたAPIクライアントライブラリを使っています。 iOSのクライアントライブラリは、内部でRxSwiftとAlamofireを使っており、そのためそれらライブラリと依存関係にあります。

SwiftPM対応

SwiftPM対応は、 Package.swift がルートディレクトリに存在し、GitHubなどのリモートリポジトリ経由でライブラリが参照できれば完了です。 Package.swift は以下のコマンドを実行することで生成されます。

$ cd MyPackage
$ swift package init

生成された Package.swift を必要に応じて編集します。 上述の通りRxSwiftとAlamofireと依存関係にあるので、 dependencies の部分に記載します。 また path の部分も必要に応じて編集します。

// swift-tools-version:5.2
import PackageDescription

let package = Package(
    name: "API",
    platforms: [
      .iOS(.v11)
    ],
    products: [
        .library(name: "API", targets: ["API"])
    ],
    dependencies: [
        .package(url: "https://github.com/ReactiveX/RxSwift.git", from: "5.1.1"),
        .package(url: "https://github.com/Alamofire/Alamofire.git", from: "4.9.1")
    ],
    targets: [
        .target(
            name: "API",
            path: "Source/Path",
            dependencies: [
                "RxSwift",
                "Alamofire"
            ]
        )
    ],
    swiftLanguageVersions: [.v5]
)

Package.swift の編集が完了したら、ビルドをします。

$ swift build

Package.resolved が生成されると思います。 これらファイルをまとめてGitHubなどにアップロードします。

Xcodeからの利用

Xcodeのメニューから、File → Swift Package → Add Package Dependency... から上記のライブラリを追加します。 プライベートリポジトリの場合は認証を必要としますが、GitHubのアカウント情報を入力すればダウンロードができます。

まとめ

Carthageで管理している社内ライブラリをSwiftPM対応しました。 Xcode公式のパッケージ管理ツールなので、信頼感がありますし、ソースコードもXcodeから確認することができるので便利に利用することができます。

しかし、Carthageのように事前のビルドがないため、クリーンビルドには時間がかかってしまいます。 そのため、最終的にはSwiftPM移行を諦め、現在はCarthageでの管理を行っています。 こちらに関しては、Xcode 12/Swift 5.3で対応したBinary Frameworkに期待したいと思います。


Google Apps Scriptを使ってBigQueryのクエリ結果をSlackに投稿する

TRILL開発部の石田です。

delyでは様々な情報をSlackに流して共有しているのですが、今回はTRILLで行っているBigQueryのクエリ結果のSlack投稿について紹介します。

背景

delyでは、透明性を大事にする取り組みとして、経営指標をオープンにSlackに流しています。

参考: dely会社紹介資料 / クラシルに関わるエンジニア・デザイナー募集 / dely - Speaker Deck

経営指標に限らず、アプリのパフォーマンス結果(クラッシュ率や速度など)を開発者だけでなくビジネスチームも含めて確認しています。

課題

TRILLではGoogleAnalyticsやFirebaseを使ってログを取得しています。 取得したログの結果は、GoogleAnalyticsとFirebaseの各管理画面から確認することができます。

しかし、欲しい情報を確認するためには管理画面を深く辿らなければならないことがあります。 また、もっと細かい粒度で分析するために、rawデータを使いたいときもあります。

そこで、GoogleAnalyticsやFirebaseのrawデータを加工してSlackに投稿することで、簡単に欲しい情報を確認できるようにしました。

やったこと

GoogleAnalyticsやFirebaseをBigQueryに連携し、BigQueryにrawデータを流し、BigQueryのクエリ結果をSlackに投稿するようにしました。 BigQueryのクエリ結果はGoogleスプレッドシートに書き込み、欲しい情報を溜めていくようにしています。

全体像

全体の構成は下図のようになります。

f:id:trill_tech:20200925175442p:plain

まず、Google Apps Script (以下GAS) からBigQueryにクエリを投げ、その結果をスプレッドシートに書き込みます。 次にスプレッドシートからデータを取得し、Slackに投稿します。 スプレッドシートにはデータが溜まっているので、先週比、先月比など所望の差分データを取り出すことができます。

BigQueryのクエリ結果をスプレッドシートに書き込む

まず、GASからBigQueryにアクセスできるようにする必要があります。 メニューの [リソース] → [Googleの拡張サービス] を選択し、BigQueryを有効にします。

以下のコードは、BigQueryのクエリ結果をスプレッドシートに書き込むサンプルとなります。 スプレッドシートのセルA1に、 COUNT(*) の結果が書き込まれます。

function runQuery() {
  var projectId = 'GCPのプロジェクトID';
  var sql = '\
    #standardSQL\n\
    SELECT COUNT(*)\
    FROM "BigQueryのテーブル名"';
  var resource = {query: sql};
  var queryResults = BigQuery.Jobs.query(resource, projectId);
  var jobId = queryResults.getJobReference().getJobId();

  while (!queryResults.getJobComplete()) {
    queryResults = BigQuery.Jobs.getQueryResults(projectId, jobId);
    Utilities.sleep(1000);
  }

  var spreadsheetId = 'スプレッドシートのID';
  var spreadsheet = SpreadsheetApp.openById(spreadsheetId);
  var sheet = spreadsheet.getSheetByName('シート名');
  sheet.getRange('A1').setValue(queryResults.rows[0].f[0].v);
}

GASから実行するとスプレッドシートとBigQueryのアクセス許可ダイアログが表示され、許可すると対象のスプレッドシートにクエリ結果が書き込まれます。

スプレッドシートの値をSlackに投稿する

以下のコードは、スプレッドシートの値をSlackに投稿するサンプルとなります。 実行するとセルA1に書き込んだ値をSlackに投稿します。

function post() {
  var spreadsheetId = 'スプレッドシートのID';
  var spreadsheet = SpreadsheetApp.openById(spreadsheetId);
  var sheet = spreadsheet.getSheetByName('シート名');
  var result = sheet.getRange('A1').getValue();

  var data = {
    'text': result
  };
  var options = {
    'method' : 'post',
    'contentType': 'application/json',
    'payload' : JSON.stringify(data)
  };
  var webhookUrl = 'SlackのWebhook URL';
  UrlFetchApp.fetch(webhookUrl, options);
}

実際には、日毎にデータを集計しており、先週比、先月比でデータがどう変化したかを投稿しています。

まとめ

Google Apps Scriptを使ってBigQueryのクエリ結果をSlackに投稿する方法について紹介しました。 欲しい情報をrawデータから加工し、毎日Slackへ自動的に投稿することで、誰でも簡単に情報を取得することが出来ます。

delyでは全方面でエンジニアを積極採用中です。 興味のある方は是非お声がけください。

com.google.gms:oss-licenses でライセンス表記を実装してみた

どうも、Android担当の永井です。

TRILLでは、OSSのライセンス表記をHTMLに張り付けてWebViewに流し込むような運用をしていたけど、ライブラリ追加削除するたびにいちいち変更がめんどう!
とういうことで、Google謹製の com.google.gms:oss-licenses を導入してライセンス表記の編集作業とおさらばしました!

詳しい手順はこちら 
developers.google.comdevelopers.google.com
 
実作業はかんたん。
依存関係追加してActivityを呼び出すだけ。
あとは勝手にライセンス情報を取得してリスト表示してくれます。

・依存関係の追加
ルートレベルのbuild.gradleにoss-licensesプラグインを追加。

buildscript {
    repositories {
        google()
    }
    dependencies {
        classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2'
}

 
・アプリレベルのbuild.gradleでプラグインを適用

apply plugin: 'com.google.gms.oss.licenses.plugin'

これで準備OK。
ビルドするとpomから依存するライブラリのライセンス情報を取得して一覧化してくれます。

あとは適当なところで画面を呼び出すだけ。
setActivityTitleでActionBarに表示するタイトルを変更できます。

    @OnClick(R.id.activity_information_title_license_tv)
    void onClickLicense() {
        startActivity(new Intent(this, OssLicensesMenuActivity.class));
        OssLicensesMenuActivity.setActivityTitle(getString(R.string.activity_setting_license_title));
    }


・画面のカスタマイズ
OssLicensesMenuActivity使うのであればできることはだいぶ少なくタイトル設定とテーマ変更くらいしかできなさそう。

マニフェストにテーマ指定して、

        <activity
            android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
            android:theme="@style/LicenseTheme" />
        <activity
            android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
            android:theme="@style/LicenseTheme" />

テーマで指定すればOK。
ActionBarいらないならこれで。
自分はこれにしました。

    <style name="LicenseTheme" parent="Theme.AppCompat.Light">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
        <item name="android:statusBarColor">@color/gray_medium_light</item>
        <item name="android:textSize">@dimen/font_size_tiny</item>
    </style>

つかうならたぶんこんな感じである程度デザイン揃えられそう。

    <style name="LicenseTheme" parent="Theme.AppCompat.Light">
        <item name="windowActionBar">true</item>
        <item name="windowNoTitle">false</item>
        <item name="actionBarStyle">@style/LicenseTheme.ActionBar</item>
        <item name="android:actionBarStyle">@style/LicenseTheme.ActionBar</item>
        <item name="android:statusBarColor">@color/gray_medium_light</item>
        <item name="android:textSize">@dimen/font_size_tiny</item>
    </style>

    <style name="LicenseTheme.ActionBar" parent="Widget.AppCompat.Light.ActionBar">
        <item name="background">@color/white</item>
        <item name="android:background">@color/white</item>
        <item name="titleTextStyle">@style/LicenseTheme.ActionBar.TextStyle</item>
        <item name="android:titleTextStyle">@style/LicenseTheme.ActionBar.TextStyle</item>
    </style>

    <style name="LicenseTheme.ActionBar.TextStyle" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
        <item name="android:textSize">@dimen/font_size_tiny</item>
    </style>
</resources>


もっと細かくデザイン合わせた画面作りたければ、
ライセンス情報自体は、app/build/generated/third_party_licenses/res/raw ディレクトリに、third_party_licenses、third_party_license_metadataとして出力されているので、これを読み取って表示すればいろいろできそうです!