前言

这是笔者作为一个Android工程师入门Flutter的学习笔记,笔者不想经过一种安分守己的方法来学习:先学Dart言语,然后学习Flutter的根本运用,再到实践运用这样的过程。这样的方法有点无趣且效率较低。

笔者觉得对于已经有Android根底的来说,经过类比Android的方法来学习Flutter,掌握核心根底概念后,直接开发实践运用,在这个过程中去学习其间的常识比如Dart语法、深入的常识点。这是笔者的一次学习尝试,并将其记录下来:

给Android工程师的Flutter入门手册(一)

本篇是该系列的第二篇,主要内容是:

(1)工程结构和资源文件:在哪里放置分辨率相关的图片文件?字符串存储在哪里?

(2)Activity和Fragment

(3)数据库和本地存储

资源文件和财物文件

Android 中是区别对待资源文件 (resources) 和财物文件 (assets)的。

Flutter 运用只要财物文件 (assets)。所有原本在 Android 中应该放在res/drawable-*文件夹中的资源文件,在 Flutter 中都放在一个assets文件夹中。

Flutter放置图片资源

Flutter 遵循一个简略的类似iOS的密度相关的格局。文件可所以一倍 (1x)、两倍 (2x)、三倍 (3x) 或其它的恣意倍数。
Flutter 没有dp单位,可是有逻辑像素尺度,根本和设备无关的像素尺度是相同的。

区别逻辑像素和设备像素:

逻辑像素也称为与设备无关或与分辨率无关的像素。设备像素也称为物理像素。

devicePixelRatio表示在单一逻辑像素规范下设备物理像素的份额。或者能够理解成,显现此视图屏幕的每个逻辑像素的设备像素数。
devicePixelRatio 回来的值最终是从硬件自身、设备驱动程序或存储在操作体系或固件中的硬编码值取得的,而且或许不准确,有时差错很大。
PS:Flutter 结构以逻辑像素为单位进行操作,因而很少需求直接处理该属性。

Android的密度分类与 Flutter 像素份额的对照表如下:

Android的密度 Flutter 像素份额
ldpi 0.75x
mdpi 1.0x
hdpi 1.5x
xhdpi 2.0x
xxhdpi 3.0x
xxxhdpi 4.0x

假如在 Flutter 项目中增加一个新的叫my_icon.png的图片资源,而且将其放入咱们随便起名的叫做images的恣意文件夹中。Flutter 没有预先界说好的文件夹结构。你在pubspec.yaml文件中界说文件(包括方位信息),Flutter 担任找到它们。

你需求将根底图片(1.0x)放在images文件夹中,并将其它倍数的图片放入以特定倍数作为名称的子文件夹中:

images/my_icon.png       // Base: 1.0x image
images/2.0x/my_icon.png  // 2.0x image
images/3.0x/my_icon.png  // 3.0x image

而且需求在pubspec.yaml文件中界说这些图片:

flutter:
  assets:  
    - images/my_image.jpg

PS:这里注意assets的缩进格局,缩进有问题会呈现Unable to load asset问题

假如要包括一个目录下的所有 assets,需求在目录名称的结尾加上/

flutter:
  assets:  
    - images/

假如想要增加子文件夹中的文件,请为每个目录创立一个条目。

字符串储存在哪里?

Flutter 当下并没有一个特定的办理字符串的资源办理体系。现在来讲,最好的办法是将字符串作为静态域存放在类中,并经过类拜访它们。例如:

class Strings {
  static String welcomeMessage = 'Welcome To Flutter';
}

接着在代码中能够这样拜访字符串:

Text(Strings.welcomeMessage);

当然,这样只能处理一些本地化的言语,假如想处理多言语场景,这种方法处理起来就很费劲了。走国际化路线能够运用intl 包进行国际化和本地化。关于这部分不是本篇博客要害,后续有需求单独开一篇博客介绍。

Gradle 文件的对应物是什么?我该怎么增加依靠?

AndroidGradle非常重要,咱们在 Gradle 构建脚本中增加依靠。
那么在Flutter中,咱们是运用 Dart 自己的构建体系以及 Pub 包办理器。构建东西会将原生 Android iOS 壳运用的构建代理给对应的构建体系。

尽管在Flutter 项目的android文件夹下有 Gradle 文件,可是它们只用于给对应渠道的集成增加原生依靠。能够在pubspec.yaml文件中界说在 Flutter 里运用的外部依靠。

Activiy和Fragment

Activity 和 Fragment 在 Flutter 中的对应什么?

在 Android 中,一个Activity代表用户能够完成的一件独立任务。一个Fragment代表一个行为或者用户界面的一部分。 Fragment 用于模块化你的代码,为大屏组合复杂的用户界面,并适配运用的界面。
正如 Flutter 中一切皆为 Widget,这两个概念也都对应于Widget

怎么监听Android Activity 的生命周期事情

Android 中,能够经过覆写Activity的生命周期方法来监听其生命周期,也能够在Application上注册ActivityLifecycleCallbacks

Flutter 中,这两种方法都没有,可是你能够经过绑定WidgetsBinding调查者并监听didChangeAppLifecycleState()的变化事情来监听生命周期。

一个监听生命周期的代码示例:

import 'package:flutter/widgets.dart';
class LifecycleWatcher extends StatefulWidget {
  const LifecycleWatcher({super.key});
  @override
  State<LifecycleWatcher> createState() => _LifecycleWatcherState();
}
class _LifecycleWatcherState extends State<LifecycleWatcher>
    with WidgetsBindingObserver {
  AppLifecycleState? _lastLifecycleState;
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }
  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    setState(() {
      _lastLifecycleState = state;
    });
  }
  @override
  Widget build(BuildContext context) {
    if (_lastLifecycleState == null) {
      return const Text(
        'This widget has not observed any lifecycle changes.',
        textDirection: TextDirection.ltr,
      );
    }
    return Text(
      'The most recent lifecycle state this widget observed was: $_lastLifecycleState.',
      textDirection: TextDirection.ltr,
    );
  }
}
void main() {
  runApp(const Center(child: LifecycleWatcher()));
}

尽管FlutterActivity在内部捕获了简直所有的 Activity 生命周期事情并将它们发送给 Flutter 引擎,可是它们大部分都向你屏蔽了。

能够被调查的生命周期事情有:

  • inactive:运用处于非活泼状态而且不接收用户输入。
  • detached:运用仍然保存 flutter engine,可是悉数宿主 view 均已脱离。
  • paused:运用当时对用户不可见,无法响运用户输入,并运行在后台。这个事情对应于 Android 中的onPause()
  • resumed :运用对用户可见而且能够响运用户的输入。这个事情对应于 Android 中的onPostResume()
  • suspending:运用暂时被挂起。这个事情对应于 Android 中的onStop; iOS 上因为没有对应的事情,因而不会触发此事情。

Flutter 为你办理引擎的启动和停止,在大部分情况下没有理由要在 Flutter 一端监听 Activity 的生命周期。
假如你需求经过监听生命周期来获取或开释原生的资源,是应该在原生端做这件事的。

数据库和本地存储

本地存储

Android 中,能够运用 SharedPreferences API 来存储少量的键值对。

Flutter 中,运用Shared_Preferences 插件完成此功用。这个插件同时包装了 Shared PreferencesNSUserDefaults(iOS 渠道对应 API)的功用。可是数据或许会异步耐久化到磁盘,不确保写入回来后必定会耐久化到磁盘,所以这个插件必定不要用于存储要害数据。

举个简略的演示代码:增加一个按钮计数,每次在存储值的根底上+1

import 'dart:async';
import 'package:flutter/material.dart';  
import 'package:shared_preferences/shared_preferences.dart';  
void main() {  
  runApp(  
    const MaterialApp(  
      home: Scaffold(  
        body: Center(  
          child: ElevatedButton(  
            onPressed: _incrementCounter,  
            child: Text('Increment Counter'),  
          ),  
        ),  
      ),  
    ),  
  );  
}  
Future<void> _incrementCounter() async {  
  SharedPreferences prefs = await SharedPreferences.getInstance();  
  int counter = (prefs.getInt('counter') ?? 0) + 1;  
  debugPrint("counter: $counter");  
  await prefs.setInt('counter', counter);  
}

最终在data文件夹的运用包名下有个FlutterSharedPreferences.xml文件,存储了SP的数据

这是我的路径:
/data/data/com.example.flutter_enter_door/shared_prefs/FlutterSharedPreferences.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>  
<map>  
    <long name="flutter.counter" value="6" />  
</map>

数据库

Android 中,你会运用 SQLite 来存储能够经过 SQL 进行查询的结构化数据。

Flutter 中,运用SQFlite插件完成此功用。该插件支撑支撑 iOS、Android 和 MacOS:

  • 支撑业务和批处理
  • 翻开时主动进行版别办理
  • 刺进/查询/更新/删去查询的帮手
  • 在 iOS 和 Android 的后台线程中执行的数据库操作

此外,其他渠道支撑:

  • 运用 sqflite_common_ffi 的 Linux/Windows/DartVM 支撑
  • 运用 sqflite_common_ffi_web 的实验性 Web 支撑。

举个狗子相关数据库的例子:

界说狗子bean:

class Dog {
  final int id;  
  final String name;  
  final int age;  
  const Dog({  
    required this.id,  
    required this.name,  
    required this.age,  
  });  
  Map<String, dynamic> toMap(){  
    return {  
      'id': id,  
      'name': name,  
      'age': age,  
    };  
  }  
  @override  
  String toString() {  
    return 'Dog{id: $id, name: $name, age: $age}';  
  }  
}

创立数据库和狗子表:

void createDogTable() async {
  WidgetsFlutterBinding.ensureInitialized();  
  database = openDatabase(  
    // 创立数据库  
    join(await getDatabasesPath(), 'doggie_database.db'),  
    onCreate: (db, version) {  
      // 创立dogs表  
      return db.execute(  
        'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',  
      );  
    },  
    version: 1,  
  );  
}

狗子数据的增修改查:

// 新增一条狗子数据  
Future<void> insertDog(Dog dog) async {  
  final db = await database;  
  await db.insert(dogTableName, dog.toMap(),  
      conflictAlgorithm: ConflictAlgorithm.replace);  
}  
// 查询狗子数据  
Future<List<Dog>> getDogs() async {  
  final db = await database;  
  final List<Map<String, dynamic>> maps = await db.query(dogTableName);  
  return List.generate(maps.length, (i) {  
    return Dog(id: maps[i]['id'], name: maps[i]['name'], age: maps[i]['age']);  
  });  
}  
// 删去狗子数据  
Future<void> deleteDog(int id) async {  
  // Get a reference to the database.  
  final db = await database;  
  // Remove the Dog from the database.  
  await db.delete(  
    dogTableName,  
    // Use a `where` clause to delete a specific dog.  
    where: 'id = ?',  
    // Pass the Dog's id as a whereArg to prevent SQL injection.  
    whereArgs: [id],  
  );  
}  
// 更新狗子数据  
Future<void> updateDog(Dog dog) async {  
  // Get a reference to the database.  
  final db = await database;  
  // Update the given Dog.  
  await db.update(  
    dogTableName,  
    dog.toMap(),  
    // Ensure that the Dog has a matching id.  
    where: 'id = ?',  
    // Pass the Dog's id as a whereArg to prevent SQL injection.  
    whereArgs: [dog.id],  
  );  
}

参考

1.字符串部分

intl包:intl

2.生命周期部分

AppLifecycleState: AppLifecycleStateenum

3.数据存储部分:

用 SQLite 做数据耐久化

运用 Database Inspector 调试数据库