İçeriğe geç
academia.sh

Ders 19 / 19

Derleme ve Yayın

Bir uygulama yazıldıktan sonra kullanıcıya ulaşana dek derlenir, imzalanır ve bir kanaldan dağıtılır. Ders bu yolu platform başına sıralar; aynı sürümü telefon ve tablet için ölçer ve on dokuz dersin boyutlanmasını tek tabloda toplar.

Yayın Güncelleme

Bu derste

  • Hata ayıklama, profil ve sürüm derleme kiplerinin amacını ayırt eder
  • Android ve iOS için sürüm çıktısını, imzalamayı ve sürüm kanalını sıralar
  • Aynı sürümü telefon ve tablet genişliğinde ölçüp boyutlanmaları karşılaştırır
  • Kursun on dokuz dersinin boyutlanmasını kapanış tablosundan okur
İçindekiler

Önceki ders paketleri ele aldı. Geriye uygulamayı kullanıcıya ulaştırmak kalıyor: derlemek, imzalamak ve bir kanaldan dağıtmak. Bu ders önce bu yolun parçalarını sıralar, sonra aynı sürümü telefon ve tablet için ölçer ve aynı görselin iki hedefte de kırpılıp kırpılmadığına bakar. Ders kursun son dersidir; sonunda on dokuz dersin boyutlanması tek tabloda toplanır.

Derleme Kipleri

Çerçevenin belgesine göre üç derleme kipi vardır. Hata ayıklama kipi geliştirme içindir: sıcak yeniden yükleme yalnız bu kipte çalışır ve doğrulama ifadeleri açıktır. Profil kipi başarım çözümlemesi içindir; belge bu kipin emülatörde ve simülatörde kapalı olduğunu söyler, çünkü oradaki davranış gerçek başarımı temsil etmez. Sürüm kipi dağıtım içindir: doğrulama ifadeleri kapalıdır, hata ayıklama bilgisi çıkarılır ve derleme hızlı açılış ile küçük paket için en iyilenir.

Platform Başına Çıktı

Her platform kendi paket biçimini ister. Android belgesi, uygulama mağazasının uygulama paketi biçimini tercih ettiğini söyler; paketi desteklemeyen mağazalar için işlemci mimarisi başına bölünmüş kurulum dosyaları önerilir. iOS derlemesi bir arşiv ve mağazaya yüklenecek bir paket üretir. Sürüm numarası iki platformda da tek yerde bildirilir: noktalı üç sayı sürümü, artı işaretinden sonraki sayı derleme numarasını verir.

# Platform kodu: sürüm numarası
version: 1.4.0+12

1.4.0 kullanıcıya görünen sürümdür, 12 derleme numarasıdır. iOS belgesi, mağazaya yapılan her yüklemenin benzersiz bir derleme numarası istediğini söyler.

İmzalama

İmzalama, paketin kimden geldiğini ve yolda değişmediğini kanıtlar. Android belgesi iki anahtarı ayırır: geliştiricinin yüklerken kullandığı yükleme anahtarı ve kullanıcıya ulaşan paketi imzalayan uygulama imzalama anahtarı. Yükleme anahtarının saklandığı dosya ve ona anahtar bilgisini veren yapılandırma dosyası gizli tutulur; belge ikisinin de herkese açık sürüm denetimine eklenmemesini ister. iOS tarafında imzalama ve hazırlık profilini geliştirme ortamı yönetebilir; belgeye göre bu ayar varsayılan olarak açıktır ve geliştirici kayıtlı hesabına bağlı bir takım seçer.

Sürüm Kanalı

İmzalı paket doğrudan herkese gitmek zorunda değildir. İki mağazanın yayın konsolu da paketi önce küçük bir gruba açan bir iç sınama kanalı sunar. Grup sürümü beğenirse paket inceleme için gönderilir ve onaydan sonra yayımlanır. İnceleme süresi bu derste yazılmaz.

Kanal, aynı derlemenin kime ulaştığını belirler. Hangi genişlikte çizildiğini belirlemez; onu hedefin ekranı ve ağaçtaki kısıtlar belirler.

Ölçülen Katman

Kursun her dersi aynı düz Dart modelini kullanır. Model bir bileşen ağacına ekran genişliğini verir ve her düğümün genişliğini kimin belirlediğini dört adla kaydeder. Aşağıdaki dosya çıktı üretmez; ölçülen bloklar onu içe alır.

// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];

class Limit {
  final int min;
  final int? max; // null: sinirsiz
  const Limit(this.min, this.max);

  bool get tight => max == min;
  Limit loosen() => Limit(0, max);
  Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
  String bounds() => '$min..${max ?? 'sonsuz'}';
}

int _floor(int value) => value < 0 ? 0 : value;

class Part {
  final String kind;
  final String name;
  final int? width; // null: alabildigi kadar genis
  final List<Part> children;
  final int factor;
  final bool tightFlex;
  const Part(this.kind, this.name, this.width, this.children,
      {this.factor = 0, this.tightFlex = true});
}

Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
    Part('flex', name, null, [child], factor: factor, tightFlex: tight);

class Sizing {
  final String sizing;
  final String path;
  final int width;
  final Limit limit;
  const Sizing(this.sizing, this.path, this.width, this.limit);
}

Sizing classify(String path, int? desire, Limit limit) {
  if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
  if (desire == null) {
    return limit.max == null
        ? Sizing('kirpilan', path, limit.min, limit)
        : Sizing('esnetilen', path, limit.max!, limit);
  }
  if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
  if (limit.max != null && desire > limit.max!) {
    return Sizing('kirpilan', path, limit.max!, limit);
  }
  return Sizing('secilen', path, desire, limit);
}

int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
  final here = '$path/${part.name}';
  final below = <Sizing>[];
  if (part.kind == 'fixed') {
    final own = classify(here, part.width, limit);
    layout(part.children.first, Limit(own.width, own.width), below, here);
    log..add(own)..addAll(below);
    return own.width;
  }
  int? desire;
  switch (part.kind) {
    case 'leaf':
      desire = part.width;
    case 'pad':
      final space = 2 * part.width!;
      desire = layout(part.children.first, limit.deflate(space), below, here) + space;
    case 'center':
      final inner = layout(part.children.first, limit.loosen(), below, here);
      desire = limit.max == null ? inner : null;
    case 'flex':
      desire = layout(part.children.first, limit, below, here);
    case 'column':
      var widest = 0;
      for (final child in part.children) {
        final width = layout(child, limit.loosen(), below, here);
        if (width > widest) widest = width;
      }
      desire = widest;
    case 'row':
      final logs = [for (final _ in part.children) <Sizing>[]];
      final bounded = limit.max != null;
      var used = 0;
      var factors = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind == 'flex' && bounded) {
          factors += child.factor;
        } else {
          used += layout(child, const Limit(0, null), logs[i], here);
        }
      }
      final left = bounded ? _floor(limit.max! - used) : 0;
      var given = 0;
      var seen = 0;
      for (var i = 0; i < part.children.length; i++) {
        final child = part.children[i];
        if (child.kind != 'flex' || !bounded) continue;
        seen += child.factor;
        final share = (seen == factors ? left : left * seen ~/ factors) - given;
        given += share;
        final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
        used += layout(child, room, logs[i], here);
      }
      for (final entries in logs) {
        below.addAll(entries);
      }
      desire = bounded ? null : used;
  }
  final own = classify(here, desire, limit);
  log..add(own)..addAll(below);
  return own.width;
}

List<Sizing> measure(Part root, int screen) {
  final log = <Sizing>[];
  layout(root, Limit(screen, screen), log);
  return log;
}

void printSizes(List<Sizing> log) {
  for (final entry in log) {
    print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
  }
}

void countSizings(List<Sizing> log) {
  for (final name in sizings) {
    print('$name: ${log.where((entry) => entry.sizing == name).length}');
  }
  print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}

Bu derste model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Modelde sürüm, iki ekran genişliğinde ölçülen bir ağaçtır.

İki Hedef

Sürüm iki hedef için ölçülür: 360 birimlik telefon ve 768 birimlik tablet. Ağaçta 900 birim isteyen büyük bir Hero görseli vardır. İşlev derleme kipini alır ve yalnız geçerli olup olmadığına bakar; kip ağacı değiştirmez.

// release_model.dart — surum hedefine gore ayni agaci kurar
import 'sizing.dart';

const modes = ['debug', 'profile', 'release'];
const targetOrder = ['telefon', 'tablet'];
const targets = {'telefon': 360, 'tablet': 768};

Part buildRelease(String mode) {
  if (!modes.contains(mode)) throw ArgumentError('bilinmeyen kip');
  return pad('Screen', 16, column('Body', [
    leaf('Title', 120),
    leaf('Banner', 500),
    leaf('Hero', 900),
    row('Toolbar', [
      leaf('Back', 48),
      flex('Search', 1, fill('Field')),
      leaf('Menu', 48),
    ]),
  ]));
}

İlk ölçüm telefonu tüm kayıtla basar.

// main.dart
import 'sizing.dart';
import 'release_model.dart';

void main() {
  printSizes(measure(buildRelease('release'), targets['telefon']!));
}
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Body genislik=328 kisit=328..328
secilen: /Screen/Body/Title genislik=120 kisit=0..328
kirpilan: /Screen/Body/Banner genislik=328 kisit=0..328
kirpilan: /Screen/Body/Hero genislik=328 kisit=0..328
esnetilen: /Screen/Body/Toolbar genislik=328 kisit=0..328
secilen: /Screen/Body/Toolbar/Back genislik=48 kisit=0..sonsuz
dayatilan: /Screen/Body/Toolbar/Search genislik=232 kisit=232..232
dayatilan: /Screen/Body/Toolbar/Search/Field genislik=232 kisit=232..232
secilen: /Screen/Body/Toolbar/Menu genislik=48 kisit=0..sonsuz

Ekrandan iç boşluk düşünce gövdeye 328..328 iner. 120 isteyen Title aralığa sığar, seçilen olur. 500 isteyen Banner ile 900 isteyen Hero 328’e çekilir, kırpılan olur. Araç çubuğu istek bildirmez ve yayılır, esnetilen olur.

Şimdi aynı ağaç tablet genişliğinde ölçülür.

// main.dart
import 'sizing.dart';
import 'release_model.dart';

void main() {
  printSizes(measure(buildRelease('release'), targets['tablet']!));
}
dayatilan: /Screen genislik=768 kisit=768..768
dayatilan: /Screen/Body genislik=736 kisit=736..736
secilen: /Screen/Body/Title genislik=120 kisit=0..736
secilen: /Screen/Body/Banner genislik=500 kisit=0..736
kirpilan: /Screen/Body/Hero genislik=736 kisit=0..736
esnetilen: /Screen/Body/Toolbar genislik=736 kisit=0..736
secilen: /Screen/Body/Toolbar/Back genislik=48 kisit=0..sonsuz
dayatilan: /Screen/Body/Toolbar/Search genislik=640 kisit=640..640
dayatilan: /Screen/Body/Toolbar/Search/Field genislik=640 kisit=640..640
secilen: /Screen/Body/Toolbar/Menu genislik=48 kisit=0..sonsuz

Gövde 736 olur. Banner bu kez 500 ile aralığa sığar, seçilen olur. Hero yine aralığı aşar ve 736’ya çekilir. Ağaç da kural da aynıdır; değişen yalnız kısıttır.

İki Hedefin Sayımı

Sayım iki hedefte ayrı basılır.

// main.dart
import 'sizing.dart';
import 'release_model.dart';

void main() {
  for (final name in targetOrder) {
    print('hedef=$name');
    countSizings(measure(buildRelease('release'), targets[name]!));
  }
}
hedef=telefon
dayatilan: 4
secilen: 3
kirpilan: 2
esnetilen: 1
ebeveyn: 7
hedef=tablet
dayatilan: 4
secilen: 4
kirpilan: 1
esnetilen: 1
ebeveyn: 6

Telefonda kırpılan 2 düğüm vardır, tablette 1; ebeveyn 7’den 6’ya iner. Fark Banner’dan gelir. Hero’nun sınıfı iki hedefte de değişmez.

Derleme Kipi

Üç kip için aynı ağaç iki hedefte ölçülür; her kayıt hata ayıklama kipininkiyle karşılaştırılır.

// main.dart
import 'sizing.dart';
import 'release_model.dart';

List<String> lines(List<Sizing> log) =>
    [for (final entry in log) '${entry.sizing} ${entry.path} ${entry.width}'];

void main() {
  for (final name in targetOrder) {
    final base = lines(measure(buildRelease('debug'), targets[name]!));
    for (final mode in modes) {
      final same = lines(measure(buildRelease(mode), targets[name]!));
      print('hedef=$name kip=$mode kayit=${same.length} ayni=${same.join('|') == base.join('|')}');
    }
  }
}
hedef=telefon kip=debug kayit=10 ayni=true
hedef=telefon kip=profile kayit=10 ayni=true
hedef=telefon kip=release kayit=10 ayni=true
hedef=tablet kip=debug kayit=10 ayni=true
hedef=tablet kip=profile kayit=10 ayni=true
hedef=tablet kip=release kayit=10 ayni=true

Her kipte ve her hedefte kayıt, hata ayıklama kipininkiyle aynıdır.

Yaygın Yanılgı

Sürüm kipinde derlemek bileşenlerin genişliğini değiştirir. Modelde değiştirmez: kuralın girdisi kısıt ile istektir, kip değil. Sınıfı değiştiren, hedefin ekran genişliğidir.

Sınırlayıcı Ölçüm

Son ölçüm yalnız Hero düğümünü iki hedefte basar.

// main.dart
import 'sizing.dart';
import 'release_model.dart';

void main() {
  for (final name in targetOrder) {
    final log = measure(buildRelease('release'), targets[name]!);
    final hero = log.firstWhere((entry) => entry.path == '/Screen/Body/Hero');
    print('hedef=$name ${hero.sizing} genislik=${hero.width} kisit=${hero.limit.bounds()}');
  }
}
hedef=telefon kirpilan genislik=328 kisit=0..328
hedef=tablet kirpilan genislik=736 kisit=0..736

Odak düğüm /Screen/Body/Hero. 900 birimlik istek telefonda 0..328, tablette 0..736 aralığını aşar. İki hedefte de genişliği görselin isteği değil, aralığın üst sınırı belirler. Dersin boyutlanması bu yüzden kırpılandır.

KY1. Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Modelde derleme kipi yalnız bir addır. Gerçek derleme, imzalama, paket biçimleri ve mağaza yüklemesi modelde yoktur; ayrıntısı çerçevenin ve mağazaların belgelerindedir.

Kapanış Tablosu

Tablo kursun on dokuz dersini sırayla toplar; boyutlanmalar kaynak derslerin özetlerinden alınmıştır.

Ders Ölçülen düzen Boyutlanma Sınırlayıcı ölçüm
Dart Temelleri Başlık, afiş ve araç çubuğu kırpılan Banner 400 ister, 360 ekranda 328 alır
Eşzamansız Dart Değer gelmeden ve geldikten sonra kurulan ağaç seçilen Avatar 200 gelince isteğini seçer
Bileşen Kavramı İç boşluğun içinde kart dayatılan Card 200 ister, 328..328 alır
Durumsuz ve Durumlu Bileşenler Kapalı ve açık arama kutusu esnetilen Açık Search 480’de 448, 320’de 288 olur
Üç Ağaç Modeli Ortalanmış logo seçilen Logo iki ekranda da 96’yı seçer
Düzen Bileşenleri Sıkı ve gevşek esnek çocuk esnetilen Gevşek çocuğun metni 360’ta 280, 480’de 400, 320’de 240 olur
Platform Bileşen Aileleri Sabit yuvada eylem düğmesi dayatılan Button iki ailede de 120 alır
Durum Yönetimi Yaklaşımları Başlıkta sayaç rozeti seçilen Badge 30’dan 66’ya büyür, seçilen kalır
Gezinme ve Yönlendirme Ekran genişliğinde gezinme kabı dayatılan Orders 200 ister, 360 alır
Ağ ve Dizileştirme Sunucudan gelen afiş kırpılan 1200 isteyen Banner 328’e çekilir
Yerel Depolama Tercihle kurulan kart esnetilen full tercihinde Card 328 alır
Varlıklar ve Yazı Tipleri Ölçekli düğme kırpılan Ölçek 2’de Button 344 ister, 328 alır
Animasyonlar Beş karede ilerleyen panel seçilen Son karede Panel 240’ı seçer
Platform Kanalları Gömülü platform görünümünün yuvası dayatılan PlatformView iletideki isteğe bakmadan 240 alır
Test Türleri 400 ve 320 birimlik test yüzeyi kırpılan Chart 320’de 300 ister, 288 alır
Geliştirici Araçları Center içindeki Chip esnetilen Center 328’e yayılır, Chip 80’i seçer
Başarım Ayarı Sabit panelde sayaç dayatılan Counter 200 alır, değişen düğüm 0
Paket Yönetimi İki sürümlü derecelendirme seçilen Rating yeni sürümde 112’yi seçer
Derleme ve Yayın Telefon ve tablet için aynı sürüm kırpılan Hero 900 ister, 328 ve 736 alır

Dersler arasında dağılım şöyledir: dayatılan 5, seçilen 5, kırpılan 5, esnetilen 4. Toplam on dokuz derstir.

Bu sayım kursun açılışındaki sayımla aynı şey değildir. Açılışta çekirdeğin tek bir düzeni dokuz kayıtla basılmıştı: dayatılan 4, seçilen 3, kırpılan 1, esnetilen 1 ve ebeveyn 6. Çekirdek tek düzenin dokuz kaydını, tablo on dokuz dersi sayar.

Özet

  • Bu dersin boyutlanması: kırpılan. Odak düğüm /Screen/Body/Hero‘dur: 900 birim isteyen görsel telefonda 328’e, tablette 736’ya çekilir.
  • Hata ayıklama kipi geliştirme, profil kipi başarım çözümlemesi, sürüm kipi dağıtım içindir; platform başına paket, imzalama ve sürüm kanalı sürümü kullanıcıya ulaştırır.
  • Aynı sürüm iki hedefte ölçüldü: telefonda kırpılan 2, tablette 1. Banner telefonda kırpılırken tablette seçilen oldu, Hero iki hedefte de kırpılan kaldı.
  • Üç derleme kipinde de kayıt hata ayıklama kipininkiyle aynı çıktı.
  • Kapanış tablosunda on dokuz dersin dağılımı dayatılan 5, seçilen 5, kırpılan 5, esnetilen 4’tür.

Sonraki Adım

Bu ders kursun son dersidir. On dokuz ders boyunca tek soru sorduk: bu bileşenin genişliğini kim belirliyor. Sıradaki konu alanı çapraz platform üretim pratiğidir.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat