jzoom

jzoom 查看完整档案

厦门编辑吉林大学  |  计算机科学与技术 编辑best-flutter  |  创始人 编辑 github.com/best-flutter 编辑
编辑

A simple way to solving problems is using tools like docker/Spring boot/React Native/React/Vue… Technology should not become a bottleneck in thinking.

个人动态

jzoom 赞了文章 · 2019-07-16

npm 和 yarn 缓存策略对比

前几天 npm@5 发布,其中最大的改进是对缓存策略的更新,本文对比一下 npm@5 和 yarn 的缓存策略。

npm

缓存命令

npm cache 提供了三个命令,分别是npm cache add, npm cache clean, npm cache verify

npm cache add

官方解释说这个命令主要是 npm 内部使用,但是也可以用来手动给一个指定的 package 添加缓存。(This command is primarily intended to be used internally by npm, but it can provide a way to add data to the local installation cache explicitly.)

npm cache clean

删除缓存目录下的所有数据。从 npm@5 开始,为了保证缓存数据的有效性和完整性,需要加上 --force 参数。

npm cache verify

验证缓存数据的有效性和完整性,清理垃圾数据。

缓存策略

npm 的缓存目录是通过 cache 变量指定的,一般默认是在 ~/.npm 文件夹(Windows 系统在 %AppData%/npm-cache 文件夹),可以执行下面的命令查看

npm config get cache

在 npm@5 以前,每个缓存的模块在 ~/.npm 文件夹中以模块名的形式直接存储,例如 koa 模块存储在 ~/.npm/koa 文件夹中。而 npm@5 版本开始,数据存储在 ~/.npm/_cacache 中,并且不是以模块名直接存放。

npm 的缓存是使用 pacote 模块进行下载和管理,基于 cacache 缓存存储。由于 npm 会维护缓存数据的完整性,一旦数据发生错误,就回重新获取。因此不推荐手动清理缓存,除非需要释放磁盘空间,这也是要强制加上 --force 参数的原因。

目前没有提供用户自己管理缓存数据的命令,随着你不断安装新的模块,缓存数据也会越来越多,因为 npm 不会自己删除数据。

离线安装

npm 提供了离线安装模式,使用 --offline, --prefer-offline, --prefer-online 可以指定离线模式。

--prefer-offline / --prefer-online

“离线优先/网络优先”模式。

  • 如果设置为 --prefer-offline 则优先使用缓存数据,如果没有匹配的缓存数据,则从远程仓库下载。

  • 如果设置为 --prefer-online 则优先使用网络数据,忽略缓存数据,这种模式可以及时获取最新的模块。

--offline

完全离线模式,安装过程不需要网络,直接使用匹配的缓存数据,一旦缓存数据不存在,则安装失败。

yarn

缓存命令

yarn cache 提供了三个命令,分别是yarn cache ls, yarn cache dir, yarn cache clean

yarn cache ls

列出当前缓存的包列表。

yarn cache dir

显示缓存数据的目录。

yarn cache clean

清除所有缓存数据。

缓存策略

官方文档没有详细介绍缓存策略,不过进入缓存目录也可以看出一些端倪。在 ~/Library/Caches/Yarn 文件夹中,每个缓存的模块被存放在独立的文件夹,文件夹名称包含了模块名称、版本号等信息。

离线安装

yarn 默认会使用 “prefer-online” 的模式,也就是先尝试从远程仓库下载,如果连接失败则尝试从缓存读取。yarn 也提供了 --offline 参数,即通过 yarn add --offline 安装依赖。

另外 yarn 还支持配置离线镜像,通过以下命令设置离线缓存仓库。具体细节参照官方博客《Running Yarn offline》

yarn config set yarn-offline-mirror ./npm-packages-offline-cache
查看原文

赞 27 收藏 26 评论 1

jzoom 发布了文章 · 2019-06-04

flutter web初探

距离flutter正式发布已经有半年了。目前flutter发展如日中天,从新加qq群的朋友数量就可以看出来。flutter早已经支持了桌面版的开发,前一段时间又出来了web版。看来离flutter一统江湖,哦不,一统前端的时间已经不远了:)。现在就差小程序这么个前端领域没有被flutter触及到了,对于国人的diy能力,我是深信的。如果真有那么一天,相信flutter也能翻译成小程序的版本,看看taro就知道了。

好了闲话说完,下面正式进入主题:

环境

安装flutter

安装flutter的最新版本,还是建议直接从官网直接下载,目前地址:

https://storage.googleapis.co...

下载完毕后解压缩到一个目录中,并且配置一下环境变量,这个在我们的环境安装篇里面已经讲过了,这里不再重复。

完毕之后运行一下

flutter precache

配置web版环境

按照官方的要求:https://github.com/flutter/fl...

运行一下这个命令:

flutter pub global activate webdev

clipboard.png

这个步骤应该没有毛病,相当顺利。 不过出现了这么个警告:

clipboard.png

我们依照这里的指令,将export PATH="$PATH":"$HOME/working/flutter/.pub-cache/bin"加入配置。

我们把官方的demo clone下来,

git clone https://github.com/flutter/flutter_web.git

进入到example中的hello_world,运行下flutter pub upgrade

cd examples/hello_world
flutter pub upgrade

clipboard.png

然后运行webdev serve

clipboard.png

这里提示了dart: command not found

显然,这里是少了dart这个命令,之前没有将dart sdk加入环境变量中,经过一番搜索,发现原来在这里:

clipboard.png

flutter/bin/cache/dart-sdk/bin

我们将这个加入到环境变量

再次运行:

clipboard.png

显然需要运行:

pub get

clipboard.png

再次运行:

clipboard.png

效果:

clipboard.png

来个复杂点的例子:

进入examples/gallery

运行

pub get
webdev serve

效果:
clipboard.png

查看原文

赞 1 收藏 0 评论 0

jzoom 评论了文章 · 2019-04-17

flutter使用binding_helper获取元素大小、位置

上一篇文章中,我们了解了如何获取元素大小的原理。那么如何在程序中直接获取本元素的大小和位置呢?

在android中,我们可以直接在元素渲染完成时获取元素大小,直接通过View.getWidth,View.getHeight方法,ios也类似,可以在渲染完成的时候获取到大小。

那么在flutter中,我们获取大小也必须在元素渲染完成的时候才行,而有些应用场景要求在第一时间获取到这个元素的大小。那么怎么在第一时间判断元素渲染完成呢?flutter中的WidgetsBuiding这个类就可以用来判断:

WidgetsBinding.instance.addPostFrameCallback

官方注释

Schedule a callback for the end of this frame.
This callback is run during a frame, just after the persistent frame callbacks (which is when the main rendering pipeline has been flushed).
Post-frame callbacks cannot be unregistered. They are called exactly once.

这个方法在一帧的最后调用,并且只调用一次。

使用这个方法就可以在判断渲染完成,并获取到元素的大小。

 @override
  void didChangeDependencies() {
    WidgetsBinding.instance.addPostFrameCallback(_onAfterRendering);
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(T oldWidget) {
    WidgetsBinding.instance.addPostFrameCallback(_onAfterRendering);
    super.didUpdateWidget(oldWidget);
  }


  void _onAfterRendering(Duration timeStamp){
      //这里编写获取元素大小和位置的方法
  }

为什么在这两个方法增加事件绑定,可以参考这里,这里不再累述。

获取元素大小:

RenderObject renderObject = context.findRenderObject();
Size size = renderObject.paintBounds.size;

获取元素位置:

 Vector3 vector3 = renderObject.getTransformTo(null)?.getTranslation();
//位置(vector3.x,vector3.y)

封装方便可用的库

上面的代码如果在每一个需要获取元素大小的应用场景都重复编写,是没有任何意义的,所以我们封装了一个库:

https://github.com/best-flutt...

使用方法:

1、增加依赖

binding_helper:

2、使用GetRectMinxin获取元素大小

class MyState extends State<MyWidget> width GetRectMinxin<MyWidget>{

    // Rect 为元素大小和位置
    @override
    void onGetRect(Rect rect) {
       
    }

}

3、使用RectProvider

 new RectProvider(child: myWidget,onGetRect: (Rect rect){
           //rect为元素大小和位置
          _rect = rect;

},)

效果:

clipboard.png

交流qq群: 854192563

查看原文

jzoom 发布了文章 · 2019-04-09

在原生ios项目中集成flutter

概述

本文不想写一个全篇步骤式的文章来描写怎么集成flutter,而是期望用一种探索的方式来追寻答案。

原理分析

我们首先看下flutter项目和一般原生项目的大概区别。

为了跳转方便,原生项目的入口一般是UINavigationController

而我们看下flutter默认给我们创建的模板为:

clipboard.png

clipboard.png

这里我们来看下flutter的引擎源码,看下这段代码做了什么工作,源码路径为:https://github.com/flutter/en...

我们首先看下`FlutterAppDelegate

https://github.com/flutter/en...

- (instancetype)init {
  if (self = [super init]) {
    _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
  }
  return self;
}

....

- (BOOL)application:(UIApplication*)application
    willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  return [_lifeCycleDelegate application:application willFinishLaunchingWithOptions:launchOptions];
}
....

所以这里可以看到,FlutterAppDelegate完全是调用了FlutterPluginAppLifeCycleDelegate的所有方法。假设你的项目原先就有一个AppDelegate的实现类,那么可以参考FlutterAppDelegate的源码,创建一个FlutterPluginAppLifeCycleDelegate,并在所有方法中调用这个类实例的方法。

原生项目中创建根ViewControler的方式可以使用StoryBoard,也可以使用代码创建。而flutter模板给我们创建的项目为StoryBoard的方式

clipboard.png

clipboard.png

从这里我们可以发现,flutter默认项目模板是将FlutterViewController作为根ViewController。

项目实战

创建项目

原理分析完毕,我们可以创建一个工程项目了.

我们这里选择创建一个最常见的SingleViewApp

clipboard.png

clipboard.png

改成不使用StoryBoard,而是代码创建根ViewController

clipboard.png

为了演示方便,我们创建一个controller
clipboard.png

修改一下启动代码:


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
    UIViewController* main = [[MainViewController alloc]initWithNibName:@"MainViewController" bundle:nil];
    UINavigationController* root = [[UINavigationController alloc]initWithRootViewController:main];
    self.window.backgroundColor = [UIColor whiteColor];
    self.window.rootViewController = root;
    [self.window makeKeyAndVisible];
    return YES;
}

在MainViewController中,我们摆上两个按钮:

clipboard.png

创建flutter模块

我们使用flutter自带命令创建一个flutter模块项目

flutter create -t module my_flutter

把创建出来的所有文件一起拷贝到上面ios原生项目的同一级目录中:

clipboard.png

使用pod初始化一下项目:

cd myproject
pod init

这样就生成了Podfile

clipboard.png

我们打开修改一下,以便将flutter包括在里面



platform :ios, '9.0'
target 'myproject' do

end

#新添加的代码
flutter_application_path = '../'
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)

运行下pod安装

pod install

clipboard.png

我们可以看到,与刚才相比,新增加了workspace文件,我们关掉原来的项目,并打开workspace

clipboard.png

然后我们可以看到项目结构如下:

clipboard.png

编译一下:

ld: '/Users/jzoom/SourceCode/myproject/myproject/DerivedData/myproject/Build/Products/Debug-iphoneos/FlutterPluginRegistrant/libFlutterPluginRegistrant.a(GeneratedPluginRegistrant.o)' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. file '/Users/jzoom/SourceCode/myproject/myproject/DerivedData/myproject/Build/Products/Debug-iphoneos/FlutterPluginRegistrant/libFlutterPluginRegistrant.a' for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

出现了这个错误

打开项目编译配置,并搜索bit,出现下面结果:

clipboard.png

修改下Enable Bitcode为No

clipboard.png

此时编译ok。

至此,在原生项目中配置flutter完毕,我们开始开发功能。

修改AppDelegate

由于我们的AppDelegate不是FlutterAppDelegate,所以我们按照前面分析的路子,改成如下:

//
//  AppDelegate.m
//  myproject
//
//  Created by JZoom on 2019/4/9.
//  Copyright © 2019 JZoom. All rights reserved.
//

#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
#import <Flutter/Flutter.h>
#import "MainViewController.h"


@interface AppDelegate()<FlutterPluginRegistry>

@end

@implementation AppDelegate{
    FlutterPluginAppLifeCycleDelegate* _lifeCycleDelegate;
}


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
    UIViewController* main = [[MainViewController alloc]initWithNibName:@"MainViewController" bundle:nil];
    UINavigationController* root = [[UINavigationController alloc]initWithRootViewController:main];
    self.window.backgroundColor = [UIColor whiteColor];
    self.window.rootViewController = root;
    [self.window makeKeyAndVisible];
    
    [GeneratedPluginRegistrant registerWithRegistry:self];
   return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
}




- (instancetype)init {
    if (self = [super init]) {
        _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
    }
    return self;
}

- (void)dealloc {
    _lifeCycleDelegate = nil;
}

- (BOOL)application:(UIApplication*)application
willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
    return [_lifeCycleDelegate application:application willFinishLaunchingWithOptions:launchOptions];
}



// Returns the key window's rootViewController, if it's a FlutterViewController.
// Otherwise, returns nil.
- (FlutterViewController*)rootFlutterViewController {
    UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
    if ([viewController isKindOfClass:[FlutterViewController class]]) {
        return (FlutterViewController*)viewController;
    }
    return nil;
}

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
    [super touchesBegan:touches withEvent:event];
    
    // Pass status bar taps to key window Flutter rootViewController.
    if (self.rootFlutterViewController != nil) {
        [self.rootFlutterViewController handleStatusBarTouches:event];
    }
}

- (void)applicationDidEnterBackground:(UIApplication*)application {
    [_lifeCycleDelegate applicationDidEnterBackground:application];
}

- (void)applicationWillEnterForeground:(UIApplication*)application {
    [_lifeCycleDelegate applicationWillEnterForeground:application];
}

- (void)applicationWillResignActive:(UIApplication*)application {
    [_lifeCycleDelegate applicationWillResignActive:application];
}

- (void)applicationDidBecomeActive:(UIApplication*)application {
    [_lifeCycleDelegate applicationDidBecomeActive:application];
}

- (void)applicationWillTerminate:(UIApplication*)application {
    [_lifeCycleDelegate applicationWillTerminate:application];
}

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
- (void)application:(UIApplication*)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
    [_lifeCycleDelegate application:application
didRegisterUserNotificationSettings:notificationSettings];
}
#pragma GCC diagnostic pop

- (void)application:(UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
    [_lifeCycleDelegate application:application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

- (void)application:(UIApplication*)application
didReceiveRemoteNotification:(NSDictionary*)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
    [_lifeCycleDelegate application:application
       didReceiveRemoteNotification:userInfo
             fetchCompletionHandler:completionHandler];
}

- (void)application:(UIApplication*)application
didReceiveLocalNotification:(UILocalNotification*)notification {
    [_lifeCycleDelegate application:application didReceiveLocalNotification:notification];
}

- (void)userNotificationCenter:(UNUserNotificationCenter*)center
       willPresentNotification:(UNNotification*)notification
         withCompletionHandler:
(void (^)(UNNotificationPresentationOptions options))completionHandler
API_AVAILABLE(ios(10)) {
    if (@available(iOS 10.0, *)) {
        [_lifeCycleDelegate userNotificationCenter:center
                           willPresentNotification:notification
                             withCompletionHandler:completionHandler];
    }
}

- (BOOL)application:(UIApplication*)application
            openURL:(NSURL*)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
    return [_lifeCycleDelegate application:application openURL:url options:options];
}

- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
    return [_lifeCycleDelegate application:application handleOpenURL:url];
}

- (BOOL)application:(UIApplication*)application
            openURL:(NSURL*)url
  sourceApplication:(NSString*)sourceApplication
         annotation:(id)annotation {
    return [_lifeCycleDelegate application:application
                                   openURL:url
                         sourceApplication:sourceApplication
                                annotation:annotation];
}

- (void)application:(UIApplication*)application
performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
  completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) {
    [_lifeCycleDelegate application:application
       performActionForShortcutItem:shortcutItem
                  completionHandler:completionHandler];
}

- (void)application:(UIApplication*)application
handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
  completionHandler:(nonnull void (^)())completionHandler {
    [_lifeCycleDelegate application:application
handleEventsForBackgroundURLSession:identifier
                  completionHandler:completionHandler];
}

- (void)application:(UIApplication*)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
    [_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler];
}

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000
- (BOOL)application:(UIApplication*)application
continueUserActivity:(NSUserActivity*)userActivity
 restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>>* __nullable
                              restorableObjects))restorationHandler {
#else
    - (BOOL)application:(UIApplication*)application
continueUserActivity:(NSUserActivity*)userActivity
restorationHandler:(void (^)(NSArray* __nullable restorableObjects))restorationHandler {
#endif
    return [_lifeCycleDelegate application:application
                      continueUserActivity:userActivity
                        restorationHandler:restorationHandler];
}
    
#pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController
    
- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
    UIViewController* rootViewController = _window.rootViewController;
    if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
        return
        [[(FlutterViewController*)rootViewController pluginRegistry] registrarForPlugin:pluginKey];
    }
    return nil;
}

- (BOOL)hasPlugin:(NSString*)pluginKey {
    UIViewController* rootViewController = _window.rootViewController;
    if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
        return [[(FlutterViewController*)rootViewController pluginRegistry] hasPlugin:pluginKey];
    }
    return false;
}

- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
    UIViewController* rootViewController = _window.rootViewController;
    if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
        return [[(FlutterViewController*)rootViewController pluginRegistry]
                valuePublishedByPlugin:pluginKey];
    }
    return nil;
}

#pragma mark - FlutterAppLifeCycleProvider methods

- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate {
    [_lifeCycleDelegate addDelegate:delegate];
}
@end

创建FlutterViewController

编辑下MainViewController

- (IBAction)launchFlutter1:(id)sender {
    
    FlutterViewController* c = [[FlutterViewController alloc]init];
    [self.navigationController pushViewController:c animated:YES];
    
}

编译下,运行点击按钮调取flutter视图,发现一片空白,并出现如下错误:

2019-04-09 13:18:18.500285+0800 myproject[57815:1968395] [VERBOSE-1:callback_cache.cc(132)] Could not parse callback cache, aborting restore
2019-04-09 13:18:36.554643+0800 myproject[57815:1968395] Failed to find assets path for "Frameworks/App.framework/flutter_assets"
2019-04-09 13:18:36.658247+0800 myproject[57815:1969776] [VERBOSE-2:engine.cc(116)] Engine run configuration was invalid.
2019-04-09 13:18:36.659545+0800 myproject[57815:1969776] [VERBOSE-2:FlutterEngine.mm(294)] Could not launch engine with configuration.
2019-04-09 13:18:36.816199+0800 myproject[57815:1969793] flutter: Observatory listening on http://127.0.0.1:50167/

我们看看和flutter自己创建的项目比,还差了什么

clipboard.png

如图:有三个地方,我们把这些文件copy一份放到我们的项目中,并且设置一下编译选项:

clipboard.png

修改下项目的配置,增加一个脚本

clipboard.png

/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" thin

clipboard.png

放到Copy Bundle Resources下面

结果:

clipboard.png

进行优化

在上面的步骤里面,我们通过直接文件拷贝将.ios目录下的flutter生成文件拷贝到了原生项目里面,显然我们不能每一次都手动这么做,我们可以添加一个命令来做这件事。

rm -rf ${SOURCE_ROOT}/Flutter/Generated.xcconfig
cp -ri ../.ios/Flutter/Generated.xcconfig ${SOURCE_ROOT}/Flutter/Generated.xcconfig
rm -rf ${SOURCE_ROOT}/Flutter/App.framework
cp -ri ../.ios/Flutter/App.framework ${SOURCE_ROOT}/Flutter/App.framework

我们把这个命令放到前面去
clipboard.png

问题

Q : 如何调用flutter的不同页面?

A : 我们首先定义一下路由

clipboard.png

然后我们可以这么调用

 /// flutter的路由视图
    FlutterViewController* c = [[FlutterViewController alloc]init];
    [c setInitialRoute:@"page2"];
    [self.navigationController pushViewController:c animated:YES];

Q : 如何在原生项目中调试flutter?
A : 首先在命令行启动flutter的监听

flutter attach

如果有多台设备,需要选择一下设备

flutter attach -d 设备标志

然后就可以在xcode中启动调试运行项目

clipboard.png

改动代码之后按下键盘上面的r键就可以了。

查看原文

赞 6 收藏 4 评论 7

jzoom 评论了文章 · 2019-01-05

flutter中的生命周期

前言

和其他的视图框架比如android的Activity一样,flutter中的视图Widget也存在生命周期,生命周期的回调函数提现在了State上面。理解flutter的生命周期,对我们写出一个合理的控件至关重要。组件State的生命周期整理如下图所示:

大致可以看成三个阶段

  • 初始化(插入渲染树)
  • 状态改变(在渲染树中存在)
  • 销毁(从渲染树种移除)

各个函数

构造函数

这个函数不属于生命周期,因为这个时候State的widget属性为空,如果要在构造函数中访问widget的属性是行不通的。但是构造函数必然是要第一个调用的。

initState

/// Called when this object is inserted into the tree.

当插入渲染树的时候调用,这个函数在生命周期中只调用一次。这里可以做一些初始化工作,比如初始化State的变量。

didChangeDependencies

/// Called when a dependency of this [State] object changes.

这个函数会紧跟在initState之后调用,并且可以调用BuildContext.inheritFromWidgetOfExactType,那么BuildContext.inheritFromWidgetOfExactType的使用场景是什么呢?最经典的应用场景是

new DefaultTabController(length: 3, child: new TabBar(
      tabs: [ "主页","订单","我的" ]
      .map( (data)=>new Text(data) ).toList(),

TabBar本来需要定义一个TabController,但是在外面套一层DefaultTabController就不需要定义TabContrller了,看下源码:

@override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _updateTabController();
    _initIndicatorPainter();
  }

void _updateTabController() {
    final TabController newController = widget.controller ?? DefaultTabController.of(context);
    ...

注意到这里DefaultTabController.of(context)

 static TabController of(BuildContext context) {
    final _TabControllerScope scope = context.inheritFromWidgetOfExactType(_TabControllerScope);
    return scope?.controller;
  }

实际上就是调用BuildContext.inheritFromWidgetOfExactType,也就说在didChangeDependencies中,可以跨组件拿到数据。

didUpdateWidget

/// Called whenever the widget configuration changes.

当组件的状态改变的时候就会调用didUpdateWidget,比如调用了setState.

实际上这里flutter框架会创建一个新的Widget,绑定本State,并在这个函数中传递老的Widget。

这个函数一般用于比较新、老Widget,看看哪些属性改变了,并对State做一些调整。

需要注意的是,涉及到controller的变更,需要在这个函数中移除老的controller的监听,并创建新controller的监听。

比如还是TabBar:

deactivate

/// Called when this object is removed from the tree.

在dispose之前,会调用这个函数。

dispose

/// Called when this object is removed from the tree permanently.

一旦到这个阶段,组件就要被销毁了,这个函数一般会移除监听,清理环境。

还是TabBar:

总结一下

阶段调用次数是否支持setState
构造函数1
initState1无效(使用setState和不使用一样)
didChangeDependencies>=1无效
didUpdateWidget>=1无效
deactivate>=1
dispose1
查看原文

jzoom 评论了文章 · 2018-12-15

flutter中使用redux之基础

本文详细讲述怎样在flutter中集成和使用redux,关于redux的概念、原理和实现,读者可自行百度,本文不做累述。

redux库

https://pub.flutter-io.cn/pac...

集成redux

修改项目根目录下的pubsepc.yaml,添加依赖

flutter_redux: ^0.5.2

clipboard.png

基本集成

创建项目

先使用flutter命令

flutter create flutter_use_redux

创建一个flutter默认的Helloworld项目

目标:改造flutter的Helloworld项目使用redux实现

原来的代码如下,去掉了注释,以便显得更短...

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
  }
}
这一步的目标是将原来的程序改成用redux来实现。

我们知道redux是整个应用程序使用唯一一个store来管理整个应用程序的状态的。那么这里我们先定义一个store,上述的程序里面的“状态”为一个计数器,所以我们先定义一个int类型的状态,初始值为0:

import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';

int mainReducer(int state, dynamic action){
  return state;
}

void main() {
  Store<int> store = new Store<int>(mainReducer,initialState: 0);
  runApp(new MyApp());
}

这里的mainReducer为应用程序的主reducer,我们知道,reducer是一个函数,它接受一个状态(State),并且“必须”返回一个状态(State),如果状态没有变化,那么应该返回原来的状态。

传递State

按照上述程序的逻辑,当点击下面的按钮的时候,计数器+1。

那么我们首先我们需要将这个计数器的状态传递给中间的Text,当点击按钮的时候,需要修改状态。

在Redux中修改状态,实际上是使用Store.dispatch这个方法,分发一个“Action",由reducer这个函数对”Action“进行解析,并返回新的State。

传递状态,使用StoreConnector这个Widget

继续修改我们的程序:



enum Actions{
  Increase,
}

int mainReducer(int state, dynamic action){

  if(Actions.Increase==action){
    return state + 1;
  }

  return state;
}

void main() {
  Store<int> store = new Store<int>(mainReducer,initialState: 0);
  runApp(new MyApp(store: store,));
}

class MyApp extends StatelessWidget {

  final Store<int> store;

  MyApp({this.store});

  @override
  Widget build(BuildContext context) {
    return new StoreProvider(store: store, child: new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home:  new StoreConnector(builder: (BuildContext context,int counter){
        return new MyHomePage(title: 'Flutter Demo Home Page',counter:counter);
      }, converter: (Store<int> store){
        return store.state;
      }) ,
    ));
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key, this.title,this.counter}) : super(key: key);
  final String title;
  final int counter;

  @override
  Widget build(BuildContext context) {

    return new Scaffold(
      appBar: new AppBar(
        title: new Text(title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$counter',
              style: Theme
                  .of(context)
                  .textTheme
                  .display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: () {

        },
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
  }
}


这步跨度有点大,解释一下:

  • 在mainReducer中增加对action的处理:
  if(Actions.Increase==action){
    return state + 1;
  }
  • 原来的MaterialApp外面套一层StoreProvider
    这一步是官方要求的,如果不做会报错。
  • 将MyHomePage改成 StatelessWidget
    由于将状态放在了Store中,所以无需使用StatefulWidget
  • 传递状态
new StoreConnector(builder: (BuildContext context,int counter){
        return new MyHomePage(title: 'Flutter Demo Home Page',counter:counter);
      }, converter: (Store<int> store){
        return store.state;
      }) 

这里实现了真正的将Store中的状态传递到组件MyHomePage中,一定需要使用converter将Store中的状态转变成组件需要的状态,这里的builder函数的第二个参数就是converter函数的返回值。

绑定Action
floatingActionButton: new StoreConnector<int,VoidCallback>(builder: ( BuildContext context,VoidCallback callback ){
        return new FloatingActionButton(
          onPressed:callback,
          tooltip: 'Increment',
          child: new Icon(Icons.add),
        );

      }, converter: (Store<int> store){
        return ()=>store.dispatch(Actions.Increase);
      }),

这里使用StoreConnector<int,VoidCallback>这个Widget绑定Action,注意泛型必须写对,不然会报错,第一个泛型为Store存储的类型,第二个泛型为converter的返回值得类型。

到这里,就改造完毕了,和官方的helloworld功能一样。

clipboard.png

敢不敢复杂一点?例子:登录

上面这个例子中,状态为一个int,实际项目当然没有那么简单,我们需要规划整个app的状态,这里我们使用AppState这个类来管理


/// 这个类用来管理登录状态
class AuthState{
  bool isLogin;     //是否登录
  String account;   //用户名
  AuthState({this.isLogin:false,this.account});
}

/// 管理主页状态
class MainPageState{
  int counter;
  MainPageState({this.counter:0});
}

/// 应用程序状态
class AppState{
  AuthState auth;     //登录
  MainPageState main; //主页

  AppState({this.main,this.auth});
}

那么下面的程序要跟着将所有的int替换成AppState,并修改reducer的代码,这里就只贴重要的代码了:


AppState mainReducer(AppState state, dynamic action){

  if(Actions.Increase==action){
    state.main.counter += 1;
  }

  return state;
}

void main() {
  Store<AppState> store = new Store<AppState>(mainReducer,initialState: new AppState(
    main: new MainPageState(),
    auth: new AuthState(),
  ));
  runApp(new MyApp(store: store,));
}
增加登录逻辑

改造一下MyHomePage,新怎一些状态

 MyHomePage({Key key, this.title,this.counter,this.isLogin,this.account}) : super(key: key);
  final String title;
  final int counter;
  final bool isLogin;
  final String account;

新增登录ui的判断

    /// 有登录,展示你好:xxx,没登录,展示登录按钮
            isLogin ?  new RaisedButton(onPressed: (){

            },child: new Text("您好:$account,点击退出"),) :new RaisedButton(onPressed: (){

            },child: new Text("登录"),)

为了快速看到效果,这里将登录输入用户名省略掉。。

改造reducer


AppState mainReducer(AppState state, dynamic action){


  print("state charge :$action ");
  if(Actions.Increase==action){
    state.main.counter+=1;
  }

  if(Actions.LogoutSuccess == action){

    state.auth.isLogin = false;
    state.auth.account = null;
  }

  if(action is LoginSuccessAction){
    state.auth.isLogin = true;
    state.auth.account = action.account;
  }

  print("state changed:$state");

  return state;
}

改造build:

Widget loginPane;
    if(isLogin){
      loginPane = new StoreConnector(
          key:new ValueKey("login"),
          builder: (BuildContext context,VoidCallback logout){
        return new RaisedButton(onPressed: logout,child: new Text("您好:$account,点击退出"),);
      }, converter: (Store<AppState> store){
        return ()=>
            store.dispatch(
                Actions.LogoutSuccess
            );
      });
    }else{
      loginPane =  new StoreConnector<AppState,VoidCallback>(
          key:new ValueKey("logout"),
          builder: (BuildContext context,VoidCallback login){
        return new RaisedButton(onPressed:login,child: new Text("登录"),);
      }, converter: (Store<AppState> store){
        return ()=>
            store.dispatch(
                new LoginSuccessAction(account: "xxx account!")
            );
      });
    }

附件:

代码:
https://github.com/jzoom/flut...

如有疑问,请加qq群854192563讨论

查看原文

jzoom 评论了文章 · 2018-12-15

flutter中使用redux之基础

本文详细讲述怎样在flutter中集成和使用redux,关于redux的概念、原理和实现,读者可自行百度,本文不做累述。

redux库

https://pub.flutter-io.cn/pac...

集成redux

修改项目根目录下的pubsepc.yaml,添加依赖

flutter_redux: ^0.5.2

clipboard.png

基本集成

创建项目

先使用flutter命令

flutter create flutter_use_redux

创建一个flutter默认的Helloworld项目

目标:改造flutter的Helloworld项目使用redux实现

原来的代码如下,去掉了注释,以便显得更短...

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
  }
}
这一步的目标是将原来的程序改成用redux来实现。

我们知道redux是整个应用程序使用唯一一个store来管理整个应用程序的状态的。那么这里我们先定义一个store,上述的程序里面的“状态”为一个计数器,所以我们先定义一个int类型的状态,初始值为0:

import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';

int mainReducer(int state, dynamic action){
  return state;
}

void main() {
  Store<int> store = new Store<int>(mainReducer,initialState: 0);
  runApp(new MyApp());
}

这里的mainReducer为应用程序的主reducer,我们知道,reducer是一个函数,它接受一个状态(State),并且“必须”返回一个状态(State),如果状态没有变化,那么应该返回原来的状态。

传递State

按照上述程序的逻辑,当点击下面的按钮的时候,计数器+1。

那么我们首先我们需要将这个计数器的状态传递给中间的Text,当点击按钮的时候,需要修改状态。

在Redux中修改状态,实际上是使用Store.dispatch这个方法,分发一个“Action",由reducer这个函数对”Action“进行解析,并返回新的State。

传递状态,使用StoreConnector这个Widget

继续修改我们的程序:



enum Actions{
  Increase,
}

int mainReducer(int state, dynamic action){

  if(Actions.Increase==action){
    return state + 1;
  }

  return state;
}

void main() {
  Store<int> store = new Store<int>(mainReducer,initialState: 0);
  runApp(new MyApp(store: store,));
}

class MyApp extends StatelessWidget {

  final Store<int> store;

  MyApp({this.store});

  @override
  Widget build(BuildContext context) {
    return new StoreProvider(store: store, child: new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home:  new StoreConnector(builder: (BuildContext context,int counter){
        return new MyHomePage(title: 'Flutter Demo Home Page',counter:counter);
      }, converter: (Store<int> store){
        return store.state;
      }) ,
    ));
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key, this.title,this.counter}) : super(key: key);
  final String title;
  final int counter;

  @override
  Widget build(BuildContext context) {

    return new Scaffold(
      appBar: new AppBar(
        title: new Text(title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$counter',
              style: Theme
                  .of(context)
                  .textTheme
                  .display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: () {

        },
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
  }
}


这步跨度有点大,解释一下:

  • 在mainReducer中增加对action的处理:
  if(Actions.Increase==action){
    return state + 1;
  }
  • 原来的MaterialApp外面套一层StoreProvider
    这一步是官方要求的,如果不做会报错。
  • 将MyHomePage改成 StatelessWidget
    由于将状态放在了Store中,所以无需使用StatefulWidget
  • 传递状态
new StoreConnector(builder: (BuildContext context,int counter){
        return new MyHomePage(title: 'Flutter Demo Home Page',counter:counter);
      }, converter: (Store<int> store){
        return store.state;
      }) 

这里实现了真正的将Store中的状态传递到组件MyHomePage中,一定需要使用converter将Store中的状态转变成组件需要的状态,这里的builder函数的第二个参数就是converter函数的返回值。

绑定Action
floatingActionButton: new StoreConnector<int,VoidCallback>(builder: ( BuildContext context,VoidCallback callback ){
        return new FloatingActionButton(
          onPressed:callback,
          tooltip: 'Increment',
          child: new Icon(Icons.add),
        );

      }, converter: (Store<int> store){
        return ()=>store.dispatch(Actions.Increase);
      }),

这里使用StoreConnector<int,VoidCallback>这个Widget绑定Action,注意泛型必须写对,不然会报错,第一个泛型为Store存储的类型,第二个泛型为converter的返回值得类型。

到这里,就改造完毕了,和官方的helloworld功能一样。

clipboard.png

敢不敢复杂一点?例子:登录

上面这个例子中,状态为一个int,实际项目当然没有那么简单,我们需要规划整个app的状态,这里我们使用AppState这个类来管理


/// 这个类用来管理登录状态
class AuthState{
  bool isLogin;     //是否登录
  String account;   //用户名
  AuthState({this.isLogin:false,this.account});
}

/// 管理主页状态
class MainPageState{
  int counter;
  MainPageState({this.counter:0});
}

/// 应用程序状态
class AppState{
  AuthState auth;     //登录
  MainPageState main; //主页

  AppState({this.main,this.auth});
}

那么下面的程序要跟着将所有的int替换成AppState,并修改reducer的代码,这里就只贴重要的代码了:


AppState mainReducer(AppState state, dynamic action){

  if(Actions.Increase==action){
    state.main.counter += 1;
  }

  return state;
}

void main() {
  Store<AppState> store = new Store<AppState>(mainReducer,initialState: new AppState(
    main: new MainPageState(),
    auth: new AuthState(),
  ));
  runApp(new MyApp(store: store,));
}
增加登录逻辑

改造一下MyHomePage,新怎一些状态

 MyHomePage({Key key, this.title,this.counter,this.isLogin,this.account}) : super(key: key);
  final String title;
  final int counter;
  final bool isLogin;
  final String account;

新增登录ui的判断

    /// 有登录,展示你好:xxx,没登录,展示登录按钮
            isLogin ?  new RaisedButton(onPressed: (){

            },child: new Text("您好:$account,点击退出"),) :new RaisedButton(onPressed: (){

            },child: new Text("登录"),)

为了快速看到效果,这里将登录输入用户名省略掉。。

改造reducer


AppState mainReducer(AppState state, dynamic action){


  print("state charge :$action ");
  if(Actions.Increase==action){
    state.main.counter+=1;
  }

  if(Actions.LogoutSuccess == action){

    state.auth.isLogin = false;
    state.auth.account = null;
  }

  if(action is LoginSuccessAction){
    state.auth.isLogin = true;
    state.auth.account = action.account;
  }

  print("state changed:$state");

  return state;
}

改造build:

Widget loginPane;
    if(isLogin){
      loginPane = new StoreConnector(
          key:new ValueKey("login"),
          builder: (BuildContext context,VoidCallback logout){
        return new RaisedButton(onPressed: logout,child: new Text("您好:$account,点击退出"),);
      }, converter: (Store<AppState> store){
        return ()=>
            store.dispatch(
                Actions.LogoutSuccess
            );
      });
    }else{
      loginPane =  new StoreConnector<AppState,VoidCallback>(
          key:new ValueKey("logout"),
          builder: (BuildContext context,VoidCallback login){
        return new RaisedButton(onPressed:login,child: new Text("登录"),);
      }, converter: (Store<AppState> store){
        return ()=>
            store.dispatch(
                new LoginSuccessAction(account: "xxx account!")
            );
      });
    }

附件:

代码:
https://github.com/jzoom/flut...

如有疑问,请加qq群854192563讨论

查看原文

jzoom 收藏了文章 · 2018-12-07

搭建socks5代理

推荐使用CentOS作为代理服务器server

准备工作

安装go环境

yum install go

创建GOPATH

mkdir /usr/local/go

修改/etc/bashrc, 添加以下两行


export GOPATH=/usr/local/go
export PATH=$PATH:$GOPATH/bin

重载bashrc


$ source /etc/bashrc

安装 shadowsocks-server

go get github.com/shadowsocks/shadowsocks-go/cmd/shadowsocks-server

运行 shadowsocks-server -v 若出现以下信息则安装成功:

Usage of shadowsocks-server:
  -A    anonymize client ip addresses in all output
  -c string
        specify config file (default "config.json")
  -core int
        maximum number of CPU cores to use, default is determinied by Go runtime
  -d    print debug message
  -k string
        password
  -m string
        encryption method, default: aes-256-cfb
  -manager-address string
        shadowsocks manager listening address
  -p int
        server port
  -t int
        timeout in seconds (default 300)
  -u    UDP Relay
  -version
        print version
    

运行socks5代理服务

shadowsocks-server -m aes-256-cfb -p 30100 -t 600 -k hellovpn -u > ./log &

说明:

-m 加密方法
-p 服务端口
-t 超时时间
-k 密码
-u 开启UDP转发

客户端下载

MacOS

Linux

Windows

Android

iOS

客户端秘钥串生成方法参见 [Quick Guide
](https://shadowsocks.org/en/co... 文档中 URI and QR code 部分
查看原文

jzoom 收藏了文章 · 2018-12-07

搭建socks5代理

推荐使用CentOS作为代理服务器server

准备工作

安装go环境

yum install go

创建GOPATH

mkdir /usr/local/go

修改/etc/bashrc, 添加以下两行


export GOPATH=/usr/local/go
export PATH=$PATH:$GOPATH/bin

重载bashrc


$ source /etc/bashrc

安装 shadowsocks-server

go get github.com/shadowsocks/shadowsocks-go/cmd/shadowsocks-server

运行 shadowsocks-server -v 若出现以下信息则安装成功:

Usage of shadowsocks-server:
  -A    anonymize client ip addresses in all output
  -c string
        specify config file (default "config.json")
  -core int
        maximum number of CPU cores to use, default is determinied by Go runtime
  -d    print debug message
  -k string
        password
  -m string
        encryption method, default: aes-256-cfb
  -manager-address string
        shadowsocks manager listening address
  -p int
        server port
  -t int
        timeout in seconds (default 300)
  -u    UDP Relay
  -version
        print version
    

运行socks5代理服务

shadowsocks-server -m aes-256-cfb -p 30100 -t 600 -k hellovpn -u > ./log &

说明:

-m 加密方法
-p 服务端口
-t 超时时间
-k 密码
-u 开启UDP转发

客户端下载

MacOS

Linux

Windows

Android

iOS

客户端秘钥串生成方法参见 [Quick Guide
](https://shadowsocks.org/en/co... 文档中 URI and QR code 部分
查看原文

jzoom 收藏了文章 · 2018-12-06

那些让程序员崩溃又想笑的程序命名...

本文旨在用最通俗的语言讲述最枯燥的基本知识

===================1===================

到一家创业公司上班的第一天,老员工刘XX给我看了公司他负责的项目,奇怪的是,命名是“LiuQXProject”,刘XX看着惊愕的我说:“怎么了?有什么错吗?”

===================2===================

给同事做双十一活动相关代码的review,学到到了很多中英混血单词
,获取双十一拼团活动数据的接口叫做“get_ShuangShiYi_GroupTuan_activity_data”,特等奖的命名:TeDeng_price....更气人的是,我们活动奖等有十级,他就虔诚地继续OneDeng_price、TwoDeng_price
直到JiuDeng_price。。。噢,no!!好气啊!!而且他还把”奖“的单词prize写成了price,怎么说呢?好难受..


===================3===================
公司来了个刚毕业的小伙子,自诩前端未来之星,喜欢研读源码,对开源充满热爱,一个月后,无意间打开他写的一个js文件,让我惊讶的是:变量从a到z全部用完,更气人的是,26个字母用完之后,他竟然丧心病狂的用起来了双拼,var aa=1,var ab=“12”,var ac=null...我问他为什么这样命名,他说你没研读jQuery源码吗?人家就是这样做的,简洁大气上档次!


===================4===================
因为微信昵称经常有带有一些乱七八糟的表情或者字符,在正常情况下utf-8编码的数据库是存不进去的,因此让同事帮忙写个把微信昵称转换成正常的字符串的一个工具函数,最终我拿到了这个工具函数,名字叫做:convertingWechatNicknameintoNormalCharacters(String nickName)


===================5===================
实习小伙子来的头一天就搞的满身大汗,我说怎么了,他说我明明写了main方法,为什么运行不了,我一看代码,我噻~main写成了mian,怎么可能跑得起来啊!更残暴的是:苹果手机是apple_sj,Android手机是android_sj,哈哈~

以上的种种让人哭笑不得的命名问题..相信很多小伙伴也会碰过这样,有些是因为经验不足,有些是因为一直没有对自己写的代码做一些规范化的工作,有的是因为被老项目、前辈带出来的坏习惯...这些都是编程世界里非常不好的行为,拒不完全统计:在一个项目中,程序员80%的时间都是在和变量、函数、方法打交道,因此一个好的命名习惯,比注释或一份详细的开发文档都重要。针对于此,小编特意根据行业标准---阿里开发文档,做了一些参考和摘抄,整理出一份关于命名方面的规范,给需要的你作参考。

争取多写漂亮代码,少写注释!!!

文章提纲:

  1. 整体规范
  2. 包规范
  3. 类规范
  4. 方法规范
  5. OOP的一些强制规范

1. 整体规范

  1. 所有的命名必须以英文意译,不能以中文拼音意译,如:获取我的消息接口,可以写:myMessage;但不能写:myXiaoXi
  2. 尽量用精简的英文命名,但要完整表达其意义,杜绝int a ,int a1 int aa这种毫无意义的简化写法。
  3. 所有命名不能以特殊符号开始,如:_age,_username
  4. 常量用全大写定义,单词之间用下划线分割语义,如:public final int REDIS_MAX_IDLE=5;

2. 包规范

  1. 包名全小写,不能用特殊符号或者驼峰写法如:com.courseLog.uitl_con是不合规范的。
  2. 包名要符合包的作用,比如数据层要写dao,工具包要写util等

3. 类规范

  1. 类名风格为大写开头的驼峰命名方式,如:ApiController、TestController等
  2. 异常类命名使用Exception结尾,如:CustomerException
  3. 抽象类命名使用Abstract开头,如:AbstractCustomer
  4. 测试类命名以它要测试的类的名称开始,以 Test 结尾,如:CustomerControllerTest
  5. 枚举类命名要以Enum结尾,如果CustomerRoleEnum
  6. 其它类型的类命名,在描述类作用的同时,也尽可能表达出类所用的一些设计模式

4. 方法规范

  1. 方法名使用驼峰写法,以小写字母开头,如:getUserCourse();
  2. 方法内的参数名、成员变量、局部变量均使用驼峰写法,以小写字母开头,如:int userName;
  3. 接口类的方法和属性不要加上任何修饰符,保证代码的简介。
  4. 方法定义必须要有注释,包括(方法作用、参数名、返回类型、创建时间等)
  5. Service/DAO层方法命名规约:
1) 获取单个对象的方法用get做前缀。
2) 获取多个对象的方法用list做前缀。
3) 获取统计值的方法用count做前缀。
4) 插入的方法用save/insert做前缀。
5) 删除的方法用remove/delete做前缀。
6) 修改的方法用update做前缀。

5. OOP的一些强制规范

  1. 尽量避免使用可变参数编程,相同参数类型,相同业务含义,才可以使用 Java 的可变参数,避免使用 Object
  2. 接口过时必须加@Deprecated 注解
  3. 不能使用过时的类或方法
  4. 所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较
  5. 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法。

觉得本文对你有帮助?请分享给更多人
关注「编程无界」,提升装逼技能

查看原文

认证与成就

  • 获得 177 次点赞
  • 获得 7 枚徽章 获得 1 枚金徽章, 获得 1 枚银徽章, 获得 5 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2018-02-04
个人主页被 2.3k 人浏览