Chuyển đến nội dung chính

Bài đăng

Hiển thị các bài đăng có nhãn Hiệu năng

Tìm rò rỉ bộ nhớ trong Flutter: năm loại đối tượng không bao giờ được dispose

Rò rỉ bộ nhớ trong Flutter hiếm khi tự khai báo. Ứng dụng chạy được, test xanh, rồi ai đó chuyển qua lại giữa hai màn hình bốn mươi lần và tiến trình đứng ở mức 900 MB. Trên một máy Android tầm trung, kết cục là bị hệ thống giết vì hết bộ nhớ, và bạn nhận được báo cáo “app tự đóng” mà không có stack trace nào. Dart có bộ thu gom rác, nên rò rỉ ở đây chỉ có đúng một nghĩa: vẫn còn thứ gì đó giữ tham chiếu tới một đối tượng bạn đã dùng xong . Toàn bộ cuộc điều tra là đi tìm cái “thứ gì đó” ấy. Năm mô-típ Trên thực tế, gần như mọi vụ rò rỉ tôi từng truy trong ứng dụng Flutter đều thuộc một trong số này. 1. Controller không bao giờ được dispose. AnimationController , TextEditingController , ScrollController , TabController , PageController — cái nào cũng giữ listener và, với trường hợp animation, còn giữ một ticker đã đăng ký với bộ lập lịch. class _EditorState extends State < Editor > with SingleTickerProviderStateMixin { late final _text = TextEditingController ()...

Vì sao ListView của bạn chậm, và bốn cách sửa thật sự có tác dụng

Đội Flutter nào cũng gặp chuyện này. Danh sách chạy ngon với hai mươi mục lúc phát triển, phát hành, rồi một người dùng có tám trăm mục đã lưu báo rằng cuộn bị khựng và ứng dụng cảm giác nặng nề. Phản xạ đầu tiên là đổ lỗi cho ListView , cho Flutter, hoặc cho cái máy. ListView không có lỗi. Trong mọi danh sách chậm tôi từng phân tích, nguyên nhân là một trong bốn thứ, và phân biệt chúng mất khoảng mười phút. Trước tiên: xem luồng nào đang trễ Trước khi đổi bất cứ thứ gì, hãy chạy ở chế độ profile trên thiết bị thật và mở performance view của DevTools. Mỗi frame được vẽ thành hai thanh: Luồng UI dài → build và layout đang đắt. itemBuilder của bạn làm quá nhiều việc. Luồng raster dài → vẽ đang đắt. Bóng đổ, làm mờ, lớp opacity, saveLayer, ảnh lớn. Phân biệt đó loại bỏ ngay một nửa số cách sửa khả dĩ. Thêm RepaintBoundary vào một danh sách mà nút thắt nằm ở luồng UI chẳng có tác dụng gì; đơn giản hoá cấu trúc widget trong một danh sách mà luồng raster đang bão hoà vì Backdro...

Đường ống ảnh trong Flutter: từ một URL tới pixel trên màn hình

Image.network(url) là một trong những API thân thiện nhất của Flutter và cũng là một trong những cái dễ đánh bại bạn nhất. Nó chạy được ngay, rồi một tháng sau bạn có những tấm ảnh loé trắng mỗi lần rebuild, một thư viện ảnh tải lại khi cuộn ngược, và 400 MB bộ nhớ trên một máy 3 GB. Cả ba đều là cùng một hiểu nhầm: Image là widget, nhưng việc cache và giải mã diễn ra trong một đối tượng mà nó uỷ thác — ImageProvider . Biết đối tượng đó đánh khoá theo cái gì, và cache cái gì, sẽ giải thích được tất cả. Năm giai đoạn Khoá. ImageProvider sinh ra một khoá — với NetworkImage là URL cộng với scale. Hai provider có khoá bằng nhau là cùng một ảnh dưới góc nhìn của cache. Tải. Byte đến từ mạng, từ asset bundle, từ file, hoặc từ bộ nhớ. Giải mã. Byte đã nén trở thành một dart:ui.Image : pixel thô, rộng × cao × 4 byte. Cache. Ảnh đã giải mã đi vào PaintingBinding.instance.imageCache , đánh khoá theo bước 1. Vẽ. Widget vẽ nó ra, áp dụng fit , alignment , và mọi bộ lọc màu. Hệ ...

Thu nhỏ ứng dụng Flutter: megabyte thật sự nằm ở đâu

Câu chuyện về kích thước ứng dụng thường bắt đầu sau khi một người có tiếng nói so sánh file APK với đối thủ và hỏi vì sao một app ghi chú việc cần làm lại nặng 40 MB. Phản ứng của đội kỹ thuật thường là bắt đầu xoá package — vốn vừa là cần gạt đau đớn nhất, vừa thường không phải cần gạt quan trọng. Ứng dụng Flutter có một sàn kích thước: engine, runtime Dart, Skia hoặc Impeller, và dữ liệu ICU. Cái sàn đó là thật và bạn không gỡ được. Mọi thứ nằm trên nó là của bạn, và trong hầu hết ứng dụng tôi từng xem, phần lớn cái “của bạn” đó là asset và mã native được đóng gói nhưng không dùng, chứ không phải Dart. Nên nước đi đầu tiên không phải là xoá gì cả. Mà là đo. Đo trước: --analyze-size flutter build apk --release --analyze-size flutter build appbundle --release --analyze-size flutter build ipa --release --analyze-size Lệnh này in ra một bản tóm tắt và ghi một file JSON. Hãy mở file đó bằng công cụ app size trong DevTools — nó cho bạn một treemap với các ô tỉ lệ theo s...

Stream trong Dart chuyên sâu: backpressure, broadcast và những rò rỉ ở giữa

Bad state: Stream has already been listened to. là lỗi đẩy phần lớn mọi người lên Stack Overflow, và câu trả lời họ tìm thấy — “dùng .asBroadcastStream() ” — thường là cách sửa sai. Nó làm lỗi biến mất và âm thầm thay đổi ngữ nghĩa phân phối dữ liệu của bạn. Hai loại stream, và vì sao mặc định là loại nghiêm ngặt Stream đơn đăng ký chỉ được nghe đúng một lần. Nó đệm sự kiện cho tới khi có listener, và giao mọi sự kiện cho listener đó. Đọc file, thân phản hồi HTTP và các hàm async* đều sinh ra loại này. Stream broadcast có bao nhiêu listener cũng được, chỉ giao sự kiện cho những listener đang đăng ký, và không đệm gì cả. Listener tới muộn thì đơn giản là lỡ mất những gì đã qua. final single = StreamController < int >(); // đơn đăng ký final bus = StreamController < int >. broadcast (); Cụm “không đệm gì cả” là mấu chốt. Chuyển một stream đơn đăng ký sang broadcast để làm im lỗi nghĩa là các sự kiện phát ra trước khi listener thứ hai gắn vào sẽ mất — kh...

Extension type trong Dart: một cái tên mới cho giá trị cũ

Mọi codebase vượt qua một kích thước nhất định đều dính lỗi này ít nhất một lần: void transfer ( String fromUserId, String toAccountId, int cents) { ... } transfer (accountId, userId, 500 ); // biên dịch ngon lành, sai lúc chạy Cả hai đều là String , nên hệ thống kiểu không có gì để nói. Cách sửa thường thấy là một class bọc, và nó tốn một lần cấp phát cho mỗi ID bạn chạm vào. Extension type là đúng cách sửa đó nhưng không cấp phát. extension type UserId ( String value) {} extension type AccountId ( String value) {} void transfer ( UserId from, AccountId to, int cents) { ... } transfer (accountId, userId, 500 ); // lỗi biên dịch Lúc chạy, UserId chính là một String . Không có đối tượng bọc, không có truy cập trường gián tiếp, không cấp phát gì cả. Sự phân biệt chỉ tồn tại trong hệ thống kiểu tĩnh, và bị xoá đi trước khi chương trình chạy. ”Bị xoá” thật sự nghĩa là gì Đây là phần quyết định extension type có hợp với bài toán của bạn hay không. Trình biên dịch ...