最后
光有这些思路和搞懂单个知识的应用是还远远不够的,在Android开源框架设计思想中的知识点还是比较多的,想要搞懂还得学会整理和规划:我们常见的**Android热修复框架、插件化框架、组件化框架、图片加载框架、网络访问框架、RxJava响应式编程框架、IOC依赖注入框架、最近架构组件Jetpack等等Android第三方开源框架,**这些都是属于Android开源框架设计思想的。如下图所示:
这位阿里P8大佬针对以上知识点,熬夜整理出了一本长达1042页的完整版如何解读开源框架设计思想PDF文档,内容详细,把Android热修复框架、插件化框架、组件化框架、图片加载框架、网络访问框架、RxJava响应式编程框架、IOC依赖注入框架、最近架构组件Jetpack等等Android第三方开源框架这些知识点从源码分析到实战应用都讲的简单明了。
由于文档内容过多,篇幅受限,只能截图展示部分
整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~
你的支持,我的动力;祝各位前程似锦,offer不断!!!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
Future_loadCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int counter = (prefs.getInt(‘counter’) ?? 0);
return counter;
}
//递增写入SharedPreferences中key为counter的值
Future_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int counter = (prefs.getInt(‘counter’) ?? 0) + 1;
prefs.setInt(‘counter’, counter);
}
3.3 数据库
需要引入: sqflite: ^1.2.1
dbDemo() async {
final Future database = openDatabase(
//join是拼接路径分隔符
join(await getDatabasesPath(), ‘student_database.db’),
onCreate: (db, version) => db.execute(
“CREATE TABLE students(id TEXT PRIMARY KEY,name TEXT,score INTEGER)”),
onUpgrade: (db, oldVersion, newVersion) {
//dosth for 升级
},
version: 1,
);
Future insertStudent(Student std) async {
final Database db = await database;
await db.insert(
‘students’,
std.toJson(),
//插入冲突策略,新的替换旧的
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
//插入3个
await insertStudent(student1);
await insertStudent(student2);
await insertStudent(student3);
Future<List> students() async {
final Database db = await database;
final List<Map<String, dynamic>> maps = await db.query(‘students’);
return List.generate(maps.length, (i) => Student.fromJson(maps[i]));
}
读取出数据库中插入的Student对象集合
students().then((list) => list.forEach((s) => print(s.name)));
//释放数据库资源
final Database db = await database;
db.close();
}
-
用AS单独打开Flutter项目中的Android工程,写代码,每次写完代码rebuild一下.然后想让Flutter代码能调到Android这边的代码,得重新运行.
-
如果AS run窗口不展示任何消息,可以使用 命令
flutter run lib/native/invoke_method.dart
执行dart,然后看错误消息. -
Flutter发起方法调用请求开始,请求经由唯一标识符指定的方法通道到达原生代码宿主,而原生代码宿主则通过注册对应方法实现,响应并处理调用请求.最后将执行结果通过消息通道,回传至Flutter.
-
方法通道是非线程安全的,需要在UI线程(Android或iOS的主线程)回调.
-
数据持久化,推送,摄像头,蓝牙等,都需要平台支持
-
轻量级解决方案: 方法通道机制 Method Channel
-
调用示例:
class _MyHomePageState extends State {
//声明MethodChannel
static const platform = MethodChannel(‘com.xfhy.basic_ui/util’);
handleButtonClick() async {
bool result;
//捕获 万一失败了呢
try {
//异步等待,可能很耗时 等待结果
result = await platform.invokeMethod(‘isEmpty’, “have data”);
} catch (e) {
result = false;
}
print(‘result : $result’);
}
}
//Android代码
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
//参考: https://flutter.dev/docs/development/platform-integration/platform-channels
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, “com.xfhy.basic_ui/util”).setMethodCallHandler { call, result ->
//判断方法名是否支持
if (call.method == “isEmpty”) {
val arguments = call.arguments
result.success(StringUtil.isEmpty(arguments as? String))
print(“success”)
} else {
//方法名暂不支持
result.notImplemented()
print(“fail”)
}
}
}
}
- Android或者iOS的数据会被序列化成一段二进制格式的数据在通道中传输,当该数据传递到Flutter后,又会被反序列化成Dart语言中的类型.
-
除去地图、WebView、相机等涉及底层方案的特殊情况外,大部分原生代码能够实现的 UI 效果,完全可以用 Flutter 实现.
-
使用这种方式对性能造成非常大的影响且不方便维护.
-
方法通道: 原生逻辑复用
-
平台视图: 原生视图复用
官网地址: https://flutter.dev/docs/development/add-to-app
-
FlutterEngine 文档: https://github.com/flutter/flutter/wiki/Experimental:-Reuse-FlutterEngine-across-screens
-
FlutterView 文档: https://github.com/flutter/flutter/wiki/Experimental:-Add-Flutter-View
-
API一会儿就过时了,得去官网看最新的才行.
-
可以在Android App中开启Flutter的Activity,Flutter的Activity是在另外一个进程,第一次进入特别慢.也可以加入Flutter的View和Fragment
-
在Android工程下新建一个Flutter的module比较简单直接
-
Android跳转Flutter,依赖FlutterView.Flutter在FlutterView中建立了自己的导航栈.
-
通常会将Flutter容器封装成一个独立的Activity或者ViewController. 这样打开一个普通的Activity既是打开Flutter界面了
-
Flutter页面跳转原生界面,需要利用方法通道,然后用原生去打开响应的界面.
-
Flutter实例化成本非常高,每启动一个Flutter实例,就会创建一套新的渲染机制,即Flutter Engine,以及底层的Isolate.而这些实例之间的内存是不相互共享的,会带来较大的系统资源消耗.
-
实际开发中,尽量用Flutter去开发闭环的业务模块.原生跳转过去就行,剩下的全部由Flutter内部完成. 尽量避免Flutter页面回到原生页面,原生页面又启动新的Flutter实例的情况.
-
Dart的一个库,可以实现在StatelessWidget中刷新数据.跨组件传递数据.全局共享数据.依赖注入
-
使用Provider后,我们就再也不需要StalefullWidget了.
-
Provider以InheritedWidget语法糖的方法,通过数据资源封装,数据注入,和数据读写这3个步骤,为我们实现了跨组件(跨页面)之间的数据共享
-
我们既可以用Provider来实现静态的数据读传递,也可以使用ChangeNotifierProvider来实现动态的数据读写传递,还用通过MultiProvider来实现多个数据资源的共享
-
Provider.of和Consumer都可以实现数据的读取,并且Consumer还可以控制UI刷新的粒度,避免与数据无关的组件的无谓刷新
-
封装数据
//定义需要共享的数据模型,通过混入ChangeNotifier管理听众
class CounterModel with ChangeNotifier {
int _count = 0;
//读方法
int get counter => _count;
//写方法
void increment() {
_count++;
notifyListeners();//通知听众刷新
}
}
- 放数据
尽量把数据放到更高的层级
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//通过Provider组件封装数据资源
//因Provider是InheritedWidget的语法糖,所以它是一个Widget
//ChangeNotifierProvider只能搞一个
//MultiProvider可以搞多个
return MultiProvider(
providers: [
//注入字体大小 下个界面读出来
Provider.value(value: 30.0),
//注入计数器实例
ChangeNotifierProvider.value(value: CounterModel())
],
child: MaterialApp(
home: FirstPage(),
),
);
}
}
- 读数据
//示例: 读数据
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//取出资源 类型是CounterModel
//获取计时器实例
final _counter = Provider.of(context);
//获取字体大小
final textSize = Provider.of(context);
/*
//使用Consumer2获取两个数据资源
Consumer2<CounterModel,double>(
//builder函数以参数的形式提供了数据资源
builder: (context, CounterModel counter, double textSize, _) => Text(
‘Value: ${counter.counter}’,
style: TextStyle(fontSize: textSize))
)
-
我们最多可以使用到 Consumer6,即共享 6 个数据资源。
-
*/
return Scaffold(
body: Center(
child: Text(
‘Counter: ${_counter.counter}’,
style: TextStyle(fontSize: textSize),
),
),
floatingActionButton: FloatingActionButton(
child: Text(‘Go’),
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => SecondPage())),
),
);
}
}
//示例: 读和写数据
//使用Consumer 可以精准刷新发生变化的Widget
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//取出数据
//final _counter = Provider.of(context);
return Scaffold(
//使用Consumer来封装counter的读取
body: Consumer(
//builder函数可以直接获取到counter参数
//Consumer 中的 builder 实际上就是真正刷新 UI 的函数,它接收 3 个参数,即 context、model 和 child
builder: (context, CounterModel counter, _) => Center(
child: Text(‘Value: ${counter.counter}’),
),
),
floatingActionButton: Consumer(
builder: (context, CounterModel counter, child) => FloatingActionButton(
onPressed: counter.increment,
child: child,
),
child: Icon(Icons.add),
),
);
}
}
-
Flutter中平时写控件的尺寸,其实有点类似于Android中的dp
-
只能是通过
MediaQuery.of(context).size.width
获得屏幕宽度来加载什么布局 -
竖屏时用什么布局,横屏时用什么布局.可以根据屏幕宽度才判断.
-
如需适配空间等的大小,则需要以切图为基准,算出当前设备的缩放系数,在布局的时候乘一下.
-
根据kReleaseMode这个编译常数可以判断出当前是release环境还是debug环境.
-
还可以用个断言判断,release编译的时候会将断言全部移除.
-
通过使用InheritedWidget为应用中可配置部分进行抽象封装(比如接口域名,app名称等),通过配置多入口方式为应用的启动注入配置环境
-
使用kReleaseMode能判断,但是另一个环境的代码虽然不能执行到,但是会被打入二进制包中.会增大包体积,尽量使用断言.或者打release包的时候把kReleaseMode的另一个逻辑注释掉.
if (kReleaseMode) {
//正式环境
text = “release”;
} else {
//测试环境 debug
text = “debug”;
}
配置一些app的通用配置
///配置抽象
class AppConfig extends InheritedWidget {
//主页标题
final String appName;
//接口域名
final String apiBaseUrl;
AppConfig(
{@required this.appName,
@required this.apiBaseUrl,
-
@required Widget child})
- super(child: child);
//方便其子Widget在Widget树中找到它
static AppConfig of(BuildContext context) {
return context.inheritFromWidgetOfExactType(AppConfig);
}
//判断是否需要子Widget更新.由于是应用入口,无需更新
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return false;
}
}
///为不同的环境创建不同的应用入口
//main_dev.dart 这个是正式环境的入口
void main() {
var configuredApp = AppConfig(
appName: ‘dev’, //主页标题
apiBaseUrl: ‘http://dev.example.com/’, //接口域名
child: MyApp(),
);
runApp(configuredApp);
}
//main.dart 这个是测试环境的入口
/*void main(){
var configuredApp = AppConfig(){
appName: ‘example’,//主页标题
apiBaseUrl: ‘http://api.example.com/’,//接口域名
child: MyApp(),
}
runApp(configuredApp);
}*/
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
var config = AppConfig.of(context);
return MaterialApp(
title: config.appName,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var config = AppConfig.of(context);
return Scaffold(
appBar: AppBar(
title: Text(config.appName),
),
body: Center(
child: Text(config.apiBaseUrl),
),
);
}
}
//运行开发环境应用程序
//flutter run -t lib/main_dev.dart
//运行生产环境应用程序
//flutter run -t lib/main.dart
/*
//打包开发环境应用程序
flutter build apk -t lib/main_dev.dart
flutter build ios -t lib/main_dev.dart
//打包生产环境应用程序
flutter build apk -t lib/main.dart
flutter build ios -t lib/main.dart
- */
-
Flutter的热重载是基于JIT编译模式的代码增量同步.由于JIT属于动态编译,能够将Dart代码编译成生成中间代码,让Dart VM在运行时解释执行,因此可以通过动态更新中间代码实现增量同步.
-
热重载流程分为5步:
-
扫描工程改动
-
增量编译
-
推送更新
-
代码合并
-
Widget树重建
-
Flutter接收到代码变更,不会重新启动App,只会触发Widget树的重新绘制…因此可以保持之前的状态
-
由于涉及到状态保存与恢复,因此涉及状态兼容和状态初始化的场景,热重载是无法支持的.(比如改动前后Widget状态无法兼容,全局变量与静态属性的更改,main方法里面的更改,initState方法里面更改,枚举和泛型的更改等)
-
如果遇到了热重载无法支持的场景,可以点击工程面板左下角的热重启(Hot Restart)按钮,也很快
- debugPrint函数同样会将消息打印至控制台,但与print不同的是,它提供了定制打印的能力.正式环境的时候将debugPrint函数定义为一个空函数体,就可以一键实现取消打印的功能了.
// 正式环境 将debugPrint指定为空的执行体, 所以它什么也不做
debugPrint = (String message, {int wrapWidth}) {};
debugPrint(‘test’);
最后
考虑到文章的篇幅问题,我把这些问题和答案以及我多年面试所遇到的问题和一些面试资料做成了PDF文档
喜欢的朋友可以关注、转发、点赞 感谢!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
态兼容和状态初始化的场景,热重载是无法支持的.(比如改动前后Widget状态无法兼容,全局变量与静态属性的更改,main方法里面的更改,initState方法里面更改,枚举和泛型的更改等)
- 如果遇到了热重载无法支持的场景,可以点击工程面板左下角的热重启(Hot Restart)按钮,也很快
- debugPrint函数同样会将消息打印至控制台,但与print不同的是,它提供了定制打印的能力.正式环境的时候将debugPrint函数定义为一个空函数体,就可以一键实现取消打印的功能了.
// 正式环境 将debugPrint指定为空的执行体, 所以它什么也不做
debugPrint = (String message, {int wrapWidth}) {};
debugPrint(‘test’);
最后
考虑到文章的篇幅问题,我把这些问题和答案以及我多年面试所遇到的问题和一些面试资料做成了PDF文档
[外链图片转存中…(img-ekx9hOkV-1715794074308)]
[外链图片转存中…(img-oVQ6nzjF-1715794074308)]
喜欢的朋友可以关注、转发、点赞 感谢!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!