├── .nojekyll ├── 游戏 ├── README.md └── Web 2D │ └── README.md ├── .meta ├── ABOUT.md └── SUMMARY.md ├── INTRODUCTION.md ├── 移动应用 ├── WebView │ ├── Ionic │ │ └── README.md │ ├── README.md │ └── Cordova │ │ └── iOS.md ├── Flutter │ ├── README.md │ └── 引擎与运行时 │ │ └── README.md ├── React Native │ ├── Expo │ │ └── README.md │ ├── 组件优化 │ │ └── 列表优化.md │ ├── 语法基础 │ │ ├── 路由导航.md │ │ ├── 组件基础.md │ │ ├── 布局基础.md │ │ ├── 环境搭建与工具链.md │ │ ├── 组件基础.old.md │ │ ├── 界面开发.md │ │ └── 事件与手势.md │ ├── 工程实践 │ │ └── 调试与测试.md │ ├── 架构原理 │ │ ├── 启动流程.md │ │ ├── README.md │ │ ├── Bridge.md │ │ ├── 原理与机制.md │ │ └── 实践技巧.md │ └── README.md ├── Android │ ├── 工程实践 │ │ ├── Android Development : Some of the best practices.md │ │ ├── 应用加固.md │ │ ├── Apk 解析.md │ │ └── Best Practices for Android Developer Productivity.md │ ├── README.md │ ├── 资源处理 │ │ └── 字符串.md │ ├── 界面开发 │ │ ├── AdapterView.md │ │ ├── Activity.md │ │ ├── Gallery.md │ │ ├── RecycleView.md │ │ └── 界面开发基础.md │ └── 系统功能 │ │ ├── 高性能 OkHttp.md │ │ └── 网络请求.md └── iOS │ ├── README.md │ ├── 界面开发 │ ├── TableGrid.md │ ├── 事件与手势.md │ └── UITableView.md │ ├── 系统功能 │ ├── 数据存储.md │ ├── 网络请求.md │ ├── HealthCheck.md │ └── 系统进程.md │ └── AppStore │ └── README.md ├── 虚拟现实 └── README.md ├── 图形与可视化 └── README.link ├── Web └── README.md ├── .gitbook.yaml ├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── custom.md │ ├── feature_request.md │ └── bug_report.md ├── .gitignore ├── README.md ├── _sidebar.md └── index.html /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /游戏/README.md: -------------------------------------------------------------------------------- 1 | # 游戏开发 2 | -------------------------------------------------------------------------------- /.meta/ABOUT.md: -------------------------------------------------------------------------------- 1 | # About 2 | -------------------------------------------------------------------------------- /INTRODUCTION.md: -------------------------------------------------------------------------------- 1 | # 本篇导读 2 | -------------------------------------------------------------------------------- /移动应用/WebView/Ionic/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /虚拟现实/README.md: -------------------------------------------------------------------------------- 1 | # MR,虚拟现实与增强现实 2 | -------------------------------------------------------------------------------- /移动应用/Flutter/README.md: -------------------------------------------------------------------------------- 1 | # Flutter 2 | -------------------------------------------------------------------------------- /移动应用/React Native/Expo/README.md: -------------------------------------------------------------------------------- 1 | # 基于 Expo 快速构建 RN 应用 2 | -------------------------------------------------------------------------------- /图形与可视化/README.link: -------------------------------------------------------------------------------- 1 | https://github.com/wx-chevalier/CG-Notes 2 | -------------------------------------------------------------------------------- /移动应用/Android/工程实践/Android Development : Some of the best practices.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Web/README.md: -------------------------------------------------------------------------------- 1 | # Web 开发 2 | 3 | 本篇迁移到了 [Web Series](https://github.com/wx-chevalier/Web-Notes) 4 | -------------------------------------------------------------------------------- /.gitbook.yaml: -------------------------------------------------------------------------------- 1 | root: ./ 2 | 3 | structure: 4 | readme: ./README.md 5 | summary: ./.meta/SUMMARY.md 6 | -------------------------------------------------------------------------------- /游戏/Web 2D/README.md: -------------------------------------------------------------------------------- 1 | # 2D 动画 2 | 3 | # Links 4 | 5 | - https://zhuanlan.zhihu.com/p/366660167 所有前端都要看的 2D 游戏化互动入门基础知识 6 | -------------------------------------------------------------------------------- /移动应用/React Native/组件优化/列表优化.md: -------------------------------------------------------------------------------- 1 | # 列表优化 2 | 3 | # Flat List 4 | 5 | # 下拉刷新 6 | 7 | # 无限加载 8 | 9 | # 离线支持 10 | -------------------------------------------------------------------------------- /移动应用/React Native/语法基础/路由导航.md: -------------------------------------------------------------------------------- 1 | # 路由导航 2 | 3 | 我们一层一层抽丝剥茧地 4 | 5 | # 抽屉菜单 6 | 7 | # 页卡浏览 8 | 9 | # 堆叠布局 10 | -------------------------------------------------------------------------------- /移动应用/Flutter/引擎与运行时/README.md: -------------------------------------------------------------------------------- 1 | # Flutter 引擎与运行时 2 | 3 | # Links 4 | 5 | - https://mp.weixin.qq.com/s/S_Unh-UYbb0cYL-bWrBmgQ 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.xmind filter=lfs diff=lfs merge=lfs -text 2 | *.zip filter=lfs diff=lfs merge=lfs -text 3 | *.pdf filter=lfs diff=lfs merge=lfs -text 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | -------------------------------------------------------------------------------- /移动应用/iOS/README.md: -------------------------------------------------------------------------------- 1 | # iOS 开发基础与工程实践 2 | 3 | ![](https://parg.co/bDY) ![](https://parg.co/bDm) 4 | 5 | # 前言 6 | 7 | ## 参考 8 | 9 | ## 版权 10 | 11 | 笔者所有文章遵循 [知识共享 署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh),欢迎转载,尊重版权。如果觉得本系列对你有所帮助,欢迎给我家布丁买点狗粮(支付宝扫码)~ 12 | 13 | # 目录 14 | 15 | > 因为本系列文章还处于不断完善中,目录中链接可能存在错误,请直接前往对应目录浏览相关章节。 16 | -------------------------------------------------------------------------------- /移动应用/Android/README.md: -------------------------------------------------------------------------------- 1 | # Android 开发基础与工程实践 2 | 3 | # 前言 4 | 5 | ## 参考 6 | 7 | ## 版权 8 | 9 | `Copyright © 2017 王下邀月熊` 10 | 11 | ![](https://parg.co/bDY) ![](https://parg.co/bDm) 12 | 13 | 笔者所有文章遵循 [知识共享 署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh),欢迎转载,尊重版权。如果觉得本系列对你有所帮助,欢迎给我家布丁买点狗粮(支付宝扫码)~ 14 | 15 | # 目录 16 | 17 | > 因为本系列文章还处于不断完善中,目录中链接可能存在错误,请直接前往对应目录浏览相关章节。 18 | -------------------------------------------------------------------------------- /移动应用/Android/工程实践/应用加固.md: -------------------------------------------------------------------------------- 1 | # 应用加固 2 | 3 | - 第一代:落地加载:其原理是基于 Java 虚拟机提供的动态加载技术。缺点是 Dex 落地加载,破解成本非常低。 4 | 5 | - 第二代:内存加载:其原理是基于 inline hook 技术对系统 read 和 write 函数添加额外的加解密处理,或直接调用 Dalvik 虚拟机提供的函数,以字节流方式加载。该加固方案虽然避免文件落地的缺陷,但缺点是解密后的 Dex 连续完整的在内存中存在。 6 | 7 | - 第三代:指令抽离:其原理是在类被执行时才进行函数修复,解决了 Dex 内存连续的问题。但由于指令抽离技术方案使用了大量虚拟机内部结构的特性,再加上 Android ROM 厂商复杂的定制,带来了大量的兼容性问题。 8 | 9 | - 第四代:Java2C:Java2C 是目前 Dex 加固保护中安全性最高的方案,与前三代 Dex 加固不同的是它脱离了虚拟提供的动态加载技术。其原理是将 Java 代码翻译成 C 并生对应的二进制文件。 10 | -------------------------------------------------------------------------------- /移动应用/Android/工程实践/Apk 解析.md: -------------------------------------------------------------------------------- 1 | # Apk 解析 2 | 3 | 系统为每个 APK 创建进程时,都会通过 PathClassLoader 类进行加载,同时开发者也可以通过 DexClassLoader 动态加载额外的 Dex 文件,有点类似于 dlopen 和 dlsym 函数的作用。PathClassLoader 和 DexClassLoader 两者都承继自 BaseDexClassLoader,最终都是通过 DexFile 完成对 dex 的加载。一般情况下每个 ClassLoader 对应一个 DexFile,但其本身是可以包含多个 DexFile 的,当要加载一个 Class 时,会遍历各个 DexFile。 4 | 5 | 类 DexFile 底层是通过 JNI 方式实现的,针对 APK 文件(包括 jar 和 zip)和二进制字节流,系统分别提供了 dvmDexFileOpenFromFd 和 dvmDexFileOpenPartial 两个函数进行处理。这两个流程最终目的都是构造出 DexOrJar 结构体,并通过 JNI 接口把该结构体的地址保存到 DexFile 的私有成员变量 mCookie。DexOrJar 结构主要由 RawDexFile 和 JarFile 组成。 6 | -------------------------------------------------------------------------------- /移动应用/WebView/README.md: -------------------------------------------------------------------------------- 1 | # 混合开发 2 | 3 | - ReactNative: ReactNative 是 Facebook 在 2015 年正式开源的一款基于 React 生态的移动端跨端开发框架,目标是在移动端开发中做到:“Learn once, write anywhere”,目前 GitHub 上的 star 数是 94k,Facebook 官方一直在对其进行重点建设和维护,社区活跃度较高。 4 | 5 | - Weex: Weex 是阿里在 2016 年开源的一款基于 Vue 生态的移动端跨端开发框架,目标是在移动端开发中做到:“Write Once, Run Everywhere”,在 2016 年年底,阿里将其捐赠给了 Apache 基金会,后来因为种种原因几乎停止了维护,开源社区也逐渐冷清,目前 GitHub 上的 star 数是 14k。 6 | 7 | - Flutter: Flutter 是 Google 在 2017 年的 I/O 大会上正式对外发布的一款全新的移动端跨端开发框架,其最显著的特点是自带了 Skia 渲染引擎和渲染控件以替代两端系统控件,做到了很好的平台一致性和性能。目前 GitHub 上的 star 数已有 117k,是 Google 官方重点维护的一个跨端开发项目,社区十分活跃。 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Smartphone (please complete the following information):** 33 | 34 | - Device: [e.g. iPhone6] 35 | - OS: [e.g. iOS8.1] 36 | - Browser [e.g. stock browser, safari] 37 | - Version [e.g. 22] 38 | 39 | **Additional context** 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all 2 | * 3 | 4 | # Unignore all with extensions 5 | !*.* 6 | 7 | # Unignore all dirs 8 | !*/ 9 | 10 | .DS_Store 11 | 12 | # Logs 13 | logs 14 | *.log 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Runtime data 20 | pids 21 | *.pid 22 | *.seed 23 | *.pid.lock 24 | 25 | # Directory for instrumented libs generated by jscoverage/JSCover 26 | lib-cov 27 | 28 | # Coverage directory used by tools like istanbul 29 | coverage 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules/ 48 | jspm_packages/ 49 | 50 | # TypeScript v1 declaration files 51 | typings/ 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | 71 | # next.js build output 72 | .next 73 | -------------------------------------------------------------------------------- /移动应用/Android/资源处理/字符串.md: -------------------------------------------------------------------------------- 1 | > 本文主要讲解 Android 开发中常见的 strings.xml 的使用技巧。 2 | 3 | # 避免过度复用 4 | 5 | > 不要复用不同页面上的字符串 6 | 7 | 1.假设你现在在 Sign In 与 Sign Up 两个界面上都设置了一个加载提示框,因为两个都是用于提示正在加载中的,因此你打算使用相同的提示语:`R.string.loading` 8 | 9 | 不过后来 PM 说这样实在太不友好了,因此你打算用两个更具体点的提示语来代替,因此你将你的`strings.xml`修改为了: 10 | 11 | 2.对于一个国际化的项目,你并不知道应用最后需要支持多少种语言,可能你需要在某些语言中表述某些情景是相同的词汇,而某些语言中某些情景压根不同,譬如: 12 | 13 | 注意,在英文版本中的 strings.xml 中对于*R.string.download_file_yes* 与 _R.string.terms_of_use_yes_ 都使用了 Yes,而 Ukrainian 版本中则是两个单词。 14 | 15 | # 合理分割 16 | 17 | > 对于相同页面中的字符串以前缀与注释分割 18 | 19 | 1.添加前缀到字符串上有助于辅助快速地识别字符串是属于哪个页面的。2.清晰的字符串的区分也有助于未来对国际化的支持,可以按照页面来逐个地进行多语言支持。 20 | 21 | # Format 22 | 23 | > 使用 Resources#getString(int id, Object...formatArgs) 来格式化字符串 24 | 25 | 尽量避免使用`+`来连接字符串,因为在不同的语言里对于词汇的划分是不一致的。 26 | 27 | 正确的方法是使用:[_Resources#getString(int id, Object… formatArgs)_](https://developer.android.com/reference/android/content/res/Resources.html#getString%28int,%20java.lang.Object...%29)_._ 28 | 29 | # Plurals:复数处理 30 | 31 | > 使用`Resources#getQuantityString (int id, int quantity)`来处理复数字符串 32 | 33 | 尽量不要在 Java 代码中判断是否需要使用复数表述,同样是因为不同的语言对于复数表述的文法规则是不一样的。 34 | 35 | 正确的方法是使用`Resources#getQuantityString (int id, int quantity)`来进行处理: 36 | 37 | # Words highlighting:单词高亮 38 | 39 | > 使用 HTML 文法规范来进行单词高亮 40 | 41 | 如果我们希望在 TextView 中修改一些单次的颜色,可能`ForegroundColorSpan`不一定是最佳的选择,同样是因为基于下标的高亮策略可能在不同的语言中出现问题。最好的呢,也还是使用 HTML 的字体颜色标签进行控制,譬如你希望在`Discover and play games`这一段话中需要将`Discover`与`play`进行高亮展示: 42 | -------------------------------------------------------------------------------- /移动应用/Android/界面开发/AdapterView.md: -------------------------------------------------------------------------------- 1 | # Custom AdapterView 2 | 3 | ```java 4 | public class MyAdapterView 5 | extends AdapterView 6 | implements AdapterView.OnItemClickListener { 7 | private ImageAdapter mAdapter; 8 | 9 | public MyAdapterView(Context context, AttributeSet attrs, int defStyle) { 10 | super(context, attrs, defStyle); 11 | initThings(); 12 | } 13 | 14 | @Override 15 | public ImageAdapter getAdapter() { 16 | // TODO Auto-generated method stub 17 | return mAdapter; 18 | } 19 | 20 | @Override 21 | public void setAdapter(ImageAdapter adapter) { 22 | // TODO Auto-generated method stub 23 | requestLayout(); 24 | } 25 | 26 | View obtainView(int position) { 27 | View child = mAdapter.getView(position, null, this); 28 | return child; 29 | } 30 | 31 | @Override 32 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 33 | super.onLayout(changed, l, t, r, b); 34 | for (int i = 0; i < mAdapter.getCount(); i++) { 35 | View child = obtainView(i); 36 | child.layout(10, 70 * i, 70, 70); 37 | addViewInLayout(child, i, null, true); 38 | } 39 | this.invalidate(); 40 | } 41 | 42 | @Override 43 | public void onItemClick( 44 | AdapterView parent, 45 | View v, 46 | int position, 47 | long id 48 | ) { 49 | Log.d("MYEXAMPLES", "Clicked an item!"); 50 | } 51 | } 52 | ``` 53 | 54 | ## Event 55 | 56 | ### Click 57 | 58 | ```java 59 | public boolean performItemClick(View view, 60 | int position, long id) 61 | { 62 | if (mOnItemClickListener != null) 63 | { 64 | // ... 65 | mOnItemClickListener.onItemClick(this, view, position, id); 66 | return true; 67 | } 68 | return false; 69 | } 70 | ``` 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 大前端与人机交互 2 | 3 | 随着 Web 技术的迅猛发展,ReactNative, Weex, Weapp 为代表的混合式开发日趋成为与 Android、iOS 原生开发并肩的开发模式之一;而在可预见的未来,VR、AR、WebAssembly 等一系列技术粉墨登场,原本前端之间的隔阂逐渐消亡,我们慢慢进入了大前端的时代。 4 | 5 | # Preface | 前言 6 | 7 | ![](https://tva2.sinaimg.cn/large/007DFXDhgy1g4wxluw2a6j30ku0h9jsw.jpg) 8 | 9 | 前端开发是一系列技术、实践和机构的综合,它主要由两种力量构成:其一是硬件,或者更具体地说是消费级设备;其二就是对盈利收益的不变追求,这一点与其他所有行业是一致的。 10 | 11 | ![](https://tva2.sinaimg.cn/large/007DFXDhgy1g4rc4vwxg1j30u00eagm4.jpg) 12 | 13 | 近年来,我们看到消费设备的种类经历了一场爆发式增长——智能手表、智能眼镜、智能音箱、智能电视、可折叠智能手机,等等——在物联网(IoT)时代,每天都可能有新的事物戴上名为“智能”的帽子加入智能设备大家庭,人们对此早已习以为常。 14 | 15 | ## 跨端混合开发 16 | 17 | Hybrid 技术分为两个大的分支,一个以 Cordova 为代表的基于系统的 WebView 与本地调用。另一种早期以 Titanium、Tamarin,如今以 React Native 这样为代表的 Cross Compilation,即跨平台编译技术。因为每个平台都有浏览器,也都有 WebView 控件,所以我们可以使用 HTML,CSS 和 JavaScript 来将 web 的内容和体验搬到本地。通过这样做我们可以将逻辑和 UI 渲染部分都统一,以减少开发和维护成本。这种方式开发的 app 一般被称为 [Hybrid app](http://blogs.telerik.com/appbuilder/posts/12-06-14/what-is-a-hybrid-mobile-app-),像 [PhoneGap](http://phonegap.com) 或者 [Cordova](http://cordova.apache.org) 这样的解决方案就是典型的应用。除了使用前端开发的一套技巧来构建页面和交互以外,一般这类框架还会提供一些访问设备的接口,比如相机和 GPS 等。 18 | 19 | ![hybrid-app](https://onevcat.com/assets/images/2015/hybrid-app.jpg) 20 | 21 | 虽然使用全网页的开发策略和环境可以带来代码维护的便利,但是这种方式是有致命弱点的,那就是缓慢的渲染速度和难以驾驭的动画效果。这两者对于用户体验是致命而且难以接受的。随着三年前 Facebook 使用 native 代码重新构建 Facebook 的手机 app 这一[标志性事件](https://www.facebook.com/notes/facebook-engineering/under-the-hood-rebuilding-facebook-for-ios/10151036091753920)的发生,曾经一度占领半壁江山的网页套壳的 app 的发展也日渐式微。特别在现在对于用户体验的追求几近苛刻的现在,呆板的动画效果和生硬的交互体验已经完全无法满足人民群众对高质量 app 的心理预期了。 22 | 23 | “ 一次编码,处处运行 ” 随着技术的发展,不同平台的差异会更加体现在设计,而非实现。 24 | 25 | # About 26 | 27 | ## Copyright & More | 延伸阅读 28 | 29 | [![技术视野](https://s3.ax1x.com/2021/02/21/yTSKdH.png)](https://github.com/wx-chevalier/Awesome-MindMaps) 30 | 31 | 您还可以前往 [NGTE Books](https://ng-tech.icu/books-gallery/) 主页浏览包含知识体系、编程语言、软件工程、模式与架构、Web 与大前端、服务端开发实践与工程架构、分布式基础架构、人工智能与深度学习、产品运营与创业等多类目的书籍列表: 32 | 33 | [![NGTE Books](https://s2.ax1x.com/2020/01/18/19uXtI.png)](https://ng-tech.icu/books-gallery/) 34 | -------------------------------------------------------------------------------- /移动应用/Android/界面开发/Activity.md: -------------------------------------------------------------------------------- 1 | # Support Library Activity 2 | 3 | 为了使在旧版手机上依然可以使用譬如 Holo、Material 这样的设计风格,Google 在 Support Library 中推出了多种兼容性的 Activity。 4 | 5 | ## AppCompatActivity 6 | 7 | 在 Android Support Library 22.1 之后,就将旧有的 ActionBarActivity 替换为了 AppCompatActivity,提供了最低到 API Level 7 的 ActionBar 的兼容。 8 | 9 | ![](https://androidresearch.files.wordpress.com/2015/04/android-support-22.png) 10 | 11 | **1.** In order to benefit from all these things, the first thing you should do is to update the support library to 22.1.0. 12 | 13 | ``` 14 | `dependencies {``   ``// … ``   ``compile 'com.android.support:appcompat-v7:22.1.0'``}` 15 | ``` 16 | 17 | **2.** Then let your activity extend `AppCompatActivity`. 18 | 19 | ```java 20 | public class MainActivity extends AppCompatActivity { 21 | // ... 22 | } 23 | ``` 24 | 25 | **3.** And finally, change the application theme to `AppCompat` or any descendants of it. 26 | 27 | ``` 28 | `<``application` `android:theme``=``"@style/Theme.AppCompat"``>` 29 | ``` 30 | 31 | ## ActionBarActivity!deprecated 32 | 33 | ActionBarActivity 是在 Material Design 推出之前的主流的 V7 兼容包中的基类型,目前已经被 AppCompatActivity 替代。 34 | 35 | # Activity Theme 36 | 37 | ## Dialog 38 | 39 |     要把某个 Activity 窗口化,即直接定义其主题为 Dialog 样式即可: 40 | 41 | ``` 42 | 44 | ``` 45 | 46 | 其效果如下图 47 | 48 | ![Activity 窗口示意图][1] 49 | 50 | ### Transparent 51 | 52 |     有时候需要把窗体透明化,只要为窗口定义如下的 Style 即可: 53 | 54 | ```xml 55 | 65 | ``` 66 | 67 | 然后在 AndroidManifest.xml 中添加如下配置: 68 | 69 | ``` 70 | 73 | { 76 | it('returns null for invalid input', () => { 77 | expect(getImageSource().uri).toBe(null); 78 | }); 79 | ... 80 | }); 81 | ``` 82 | 83 | 因为 Jest 是默认自动 Mock 的,所以需要对待测试的方法设置 dontMock. 84 | 85 | [Reactotron](https://github.com/skellock/reactotron):Control, monitor, and instrument your React DOM and React Native apps 86 | ![](https://github.com/skellock/reactotron/raw/master/images/Reactotron.gif) 87 | -------------------------------------------------------------------------------- /移动应用/React Native/语法基础/组件基础.md: -------------------------------------------------------------------------------- 1 | # 组件基础 2 | 3 | ![](https://cdn-images-1.medium.com/max/1600/0*E-c0mBtxYrcZJuA5.) 4 | 5 | # 组件声明 6 | 7 | # 组件样式 8 | 9 | ## 样式声明 10 | 11 | 接下来我们 12 | 13 | ```js 14 | import { StyleSheet } from "react-native"; 15 | 16 | // 创建能够用于 React Native 组件的样式表 17 | const styles = StyleSheet.create({ 18 | container: { 19 | flex: 1, 20 | justifyContent: "center", 21 | alignItems: "center", 22 | backgroundColor: "ghostwhite" 23 | }, 24 | box: { 25 | width: 100, 26 | height: 100, 27 | justifyContent: "center", 28 | alignItems: "center", 29 | backgroundColor: "lightgray" 30 | }, 31 | boxText: { 32 | color: "darkslategray", 33 | fontWeight: "bold" 34 | } 35 | }); 36 | export default styles; 37 | ``` 38 | 39 | 在真实项目中随着组件复杂度的增加,我们往往会遵循单一职责原则将组件的样式声明抽取到单独的文件中,然后在组件内引入该样式文件,有点类似于 CSS Modules 的写法。 40 | 41 | ## Flexbox 布局 42 | 43 | ## 应用主题设置 44 | 45 | ## CSS-in-JS 46 | 47 | React Native 允许我们以声明对象的方式来声明样式,不过我们就无法使用 CSS 中的便捷写法了。譬如在下列的 CSS 样式声明中我们使用了缩略写法来声明字体与边距样式: 48 | 49 | ``` 50 | font: bold 14px/16px "Helvetica"; 51 | margin: 5px 7px 2px; 52 | ``` 53 | 54 | 其对应转换为 React Native 支持的样式对象: 55 | 56 | ``` 57 | { 58 |   fontFamily: 'Helvetica', 59 |   fontSize: 14, 60 |   fontWeight: 'bold', 61 |   fontStyle: 'normal', 62 |   fontVariant: [], 63 |   lineHeight: 16, 64 |   marginTop: 5, 65 |   marginRight: 7, 66 |   marginBottom: 2, 67 |   marginLeft: 7, 68 | } 69 | ``` 70 | 71 | 这里为了避免声明复杂的样式类,我们可以选用一些 CSS-in-JS 库来帮助我们处理样式或者创建带样式的组件。较为轻量级的用法可以使用  css-to-react-native 或者类似的库将 CSS 样式声明转化为对应的 React Native 样式对象: 72 | 73 | ``` 74 | import transform from 'css-to-react-native'; 75 | // or const transform = require('css-to-react-native').default; 76 | 77 | 78 | transform([ 79 |   ['font', 'bold 14px/16px "Helvetica"'], 80 |   ['margin', '5px 7px 2px'], 81 |   ['border-left-width', '5px'], 82 | ]); // => { fontFamily: 'Helvetica', ... } 83 | ``` 84 | 85 | 我们也可以转换某个单独的样式属性名或者匹配对象: 86 | 87 | ``` 88 | import { getPropertyName, getStylesForProperty } from 'css-to-react-native'; 89 | 90 | 91 | getPropertyName('border-width'); // => 'borderWidth' 92 | getStylesForProperty('borderWidth', '1px 0px 2px 0px'); // => { borderTopWidth: 1, ... } 93 | ``` 94 | 95 | # 交互事件 96 | 97 | 在标准的 Web 应用中,我们能够方便地编写跨浏览器的事件响应代码;不过鉴于原生界面渲染的差异,在 React Native 中我们还需要根据不同的平台差异使用不同的响应机制。本小节我们即着眼于如何使用不同的 React Native 组件来收集用户输入或者响应用户操作,从基础的文本输入,到列表项选择、单选框以及时间日期选择器等等。 98 | -------------------------------------------------------------------------------- /移动应用/React Native/架构原理/启动流程.md: -------------------------------------------------------------------------------- 1 | # React Native 启动流程 2 | 3 | 现在,每当启动 React Native 应用程序时,要加载的第一个项目是本机入口点。Native 线程产生 JS VM 线程,该线程运行捆绑的 JS 代码。JS 代码具有应用程序的所有业务逻辑。JS 基础设施初始化. 主要是 require 等基本模块的加载并替换 JS 默认的实现。自定义 require, Warning window, Alert window, fetch 等都是在这里进行的。基础设施初始化好以后就可以开始加载 js 代码了。 4 | 5 | Native 线程现在通过 RN Bridge 发送消息以启动 JS 应用程序。现在,生成的 Javascript 线程开始通过 RN Bridge 向本机线程发出指令。说明包括要加载的视图,要从硬件检索的信息等。例如,如果 JS 线程想要创建视图和文本,它将把请求批处理为单个消息并将其发送到 用于渲染它们的本机线程。 6 | 7 | ``` 8 | [ [2,3,[2,'Text',{...}]] [2,3,[3,'View',{... 9 | ``` 10 | 11 | 本机线程将执行这些操作并将结果发送回 JS,以确保已执行操作。 12 | 13 | ![image.png](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230418223518.png) 14 | 15 | # 原生代码初始化 16 | 17 | 这里讨论的主要是 RN 相关的原生代码和用户自定义的 RN 模块的原生代码的加载和初始化。原生代码初始化主要分两步: 18 | 19 | - 静态加载。iOS 没有动态加载原生代码的接口,所有的代码都在编译的初期就已经编译为静态代码并且链接好,程序启动的时候所有的原生代码都会加载好。这是原生代码的静态加载,iOS 里面没有动态加载原生代码的概念,这也是为何没有静态代码热更新的原因。 20 | 21 | - RN 模块解析和注入 JS。这是加载的第二步。在 RootView 初始化的时候会遍历所有被标记为 RCTModule 的原生模块,生成一个 json 格式的模块信息,里面包含模块名称和方法名称,然后注入到 JS Engine, 由 MessageQueue 记录下来。原生代码在生成 json 模块信息的时候同时会在原生代码这边维护一个名称字典,用来把模块和方法的名称映射到原生代码的地址上去,用于 JS 调用原生代码的翻译。 22 | 23 | # Javascript 环境初始化 24 | 25 | RN 的初始化是从 RCRootView 开始的,所有的绘制都会在这个 RootView 里面进行(Alert 除外).RootView 做的第一件事情就是初始化一个空的 JS Engine。这个空的 JS Engine 里面包含一些最基础的模块和方法(fetch, require, alert 等), 没有 UI 绘制模块。RN 的工作就是替换这些基础的模块和方法,然后把 RN 的 UI 绘制模块加载并注入到 JS Engine。JS Engine 不直接管理 UI 的绘制。 26 | 27 | - 所有的绘制由原生控制的 UI 事件和 Timer 触发 28 | - 影响界面刷新的事件发生以后一部分直接由原生控件消化掉,直接更新原生控件。剩下的部分会通过 Bridge 派发给 MessageQueue,然后在 JS 层进行业务逻辑的计算,再由 React 来进行 Virtual Dom 的管理和更新。Virtual Dom 再通过 MessageQueue 发送重绘指令给对应的原生组件进行 UI 更新。 29 | 30 | # Native Modules 加载 31 | 32 | 在 OC 里面,所有 NativeModules 要加载进 JS Engine 都必须遵循一定的协议(protocol)。 33 | 34 | 模块(OC 里面的类)需要声明为, 然后在类里面还必须调用宏 RCT_EXPORT_MODULE() 用来定义一个接口告诉 JS 当前模块叫什么名字。这个宏可以接受一个可选的参数,指定模块名,不指定的情况下就取类名。 35 | 36 | 对应的 JS 模块在初始化的时候会调用原生类的[xxx new]方法. 37 | 38 | 模块声明为后只是告诉 Native Modules 这有一个原生模块,是一个空的模块。要导出任何方法给 JS 使用都必须手动用宏 RCT_EXPORT_METHOD 来导出方法给 JS 用. 39 | 40 | 所有的原生模块都会注册到 NativeModules 这一个 JS 模块下面去,你如果想要让自己的模块成为一个顶级模块就必须再写一个 JS 文件封装一遍 NativeModules 里面的方法。 41 | 42 | 你如果想自己的方法导出就默认成为顶级方法,那么你需要一个手动去调用 JSC 的接口,这个在前面章节有讲解。不建议这样做,因为这样你会失去跨 JS 引擎的便利性。 43 | 44 | 你可以导出常量到 JS 里面去, 模块初始化的时候会坚持用户是否有实现 constantsToExport 方法, 接受一个常量词典。 45 | 46 | ```c 47 | - (NSDictionary *)constantsToExport 48 | { 49 | return @{ @"firstDayOfTheWeek": @"Monday" };// JS里面可以直接调用 ModuleName.firstDayOfTheWeek获取这个常量 50 | } 51 | ``` 52 | 53 | 常量只会在初始化的时候调用一次,动态修改该方法的返回值无效 54 | 55 | 所有标记为 RCT_EXPORT_MODULE 的模块都会在程序启动的时候自动注册好这些模块,主要是记录模块名和方法名。只是注册,不一定会初始化。 56 | 57 | ## 懒加载的模块 58 | 59 | React Native 的 NativeModules 是有延迟加载机制的。App 初始化的时候 60 | 61 | React Native JS 接口兼容(Polyfills) 62 | fetch 替换 63 | CommonJS Require 64 | alert 替换 65 | console.warning 替换 66 | console.error 替换 67 | -------------------------------------------------------------------------------- /.meta/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | ## [Android](../Android/README.md) 4 | 5 | - [Introduction](../Android/README.md) 6 | 7 | - [工程实践](../Android/工程实践/README.md) 8 | - [Android Development : Some of the best practices](../Android/工程实践/Android Development : Some of the best practices.md) 9 | - [Best Practices for Android Developer Productivity](../Android/工程实践/Best Practices for Android Developer Productivity.md) 10 | - [界面开发](../Android/界面开发/README.md) 11 | - [ActionBar](../Android/界面开发/ActionBar.md) 12 | - [Activity](../Android/界面开发/Activity.md) 13 | - [AdapterView](../Android/界面开发/AdapterView.md) 14 | - [Gallery](../Android/界面开发/Gallery.md) 15 | - [RecycleView](../Android/界面开发/RecycleView.md) 16 | - [界面开发基础](../Android/界面开发/界面开发基础.md) 17 | - [系统功能](../Android/系统功能/README.md) 18 | - [网络请求](../Android/系统功能/网络请求.md) 19 | - [高性能 OkHttp](../Android/系统功能/高性能 OkHttp.md) 20 | - [资源处理](../Android/资源处理/README.md) 21 | - [字符串](../Android/资源处理/字符串.md) 22 | 23 | ## [MR](../MR/README.md) 24 | 25 | - [Introduction](../MR/README.md) 26 | 27 | ## [Web](../Web/README.md) 28 | 29 | - [Introduction](../Web/README.md) 30 | 31 | ## [iOS](../iOS/README.md) 32 | 33 | - [Introduction](../iOS/README.md) 34 | 35 | - [Objective-C](../iOS/Objective-C/README.md) 36 | - [语法基础](../iOS/Objective-C/语法基础.md) 37 | - [Swift](../iOS/Swift/README.md) 38 | - [Linkedin 样式指南](../iOS/Swift/Linkedin 样式指南.md) 39 | - [导论](../iOS/导论/README.md) 40 | - [AppStote](../iOS/导论/AppStote.md) 41 | - [概述](../iOS/导论/概述.md) 42 | - [界面开发](../iOS/界面开发/README.md) 43 | - [TableGrid](../iOS/界面开发/TableGrid.md) 44 | - [UITableView](../iOS/界面开发/UITableView.md) 45 | - [事件与手势](../iOS/界面开发/事件与手势.md) 46 | - [界面基础](../iOS/界面开发/界面基础.md) 47 | - [系统功能](../iOS/系统功能/README.md) 48 | - [HealthCheck](../iOS/系统功能/HealthCheck.md) 49 | - [数据存储](../iOS/系统功能/数据存储.md) 50 | - [系统进程](../iOS/系统功能/系统进程.md) 51 | - [网络请求](../iOS/系统功能/网络请求.md) 52 | 53 | ## [数据可视化](../数据可视化/README.md) 54 | 55 | - [Introduction](../数据可视化/README.md) 56 | 57 | - [Canvas](../数据可视化/Canvas/README.md) 58 | - [图片处理](../数据可视化/Canvas/图片处理.md) 59 | - [SVG](../数据可视化/SVG/README.md) 60 | - [动画](../数据可视化/SVG/动画.md) 61 | - [图形](../数据可视化/SVG/图形.md) 62 | - [Three.js](../数据可视化/Three.js/README.md) 63 | - [WebGL](../数据可视化/WebGL/README.md) 64 | - [二维绘图](../数据可视化/二维绘图/README.md) 65 | - [图形语法](../数据可视化/图形语法/README.md) 66 | - [图片](../数据可视化/图片/README.md) 67 | - [PNG](../数据可视化/图片/PNG.md) 68 | - [图表绘制](../数据可视化/图表绘制/README.md) 69 | - [韦恩图.nd](../数据可视化/图表绘制/韦恩图.ndREADME.md) 70 | - [多维数据可视化](../数据可视化/多维数据可视化/README.md) 71 | - [可视化过程](../数据可视化/多维数据可视化/可视化过程.md) 72 | - [探索分析](../数据可视化/探索分析/README.md) 73 | - [数据透视表](../数据可视化/探索分析/数据透视表.md) 74 | - [数据与图表类别](../数据可视化/数据与图表类别/README.md) 75 | - [关联类](../数据可视化/数据与图表类别/关联类.md) 76 | - [平面比较类](../数据可视化/数据与图表类别/平面比较类.md) 77 | - [数据类别](../数据可视化/数据与图表类别/数据类别.md) 78 | - [柱状比较类](../数据可视化/数据与图表类别/柱状比较类.md) 79 | 80 | ## [游戏](../游戏/README.md) 81 | 82 | - [Introduction](../游戏/README.md) 83 | -------------------------------------------------------------------------------- /移动应用/React Native/架构原理/README.md: -------------------------------------------------------------------------------- 1 | # React Native 架构原理 2 | 3 | React Native 是一个跨平台开发框架,允许开发人员使用 Javascript 构建原生应用程序。RN 和基于 Cordova 的应用程序之间的主要区别在于:基于 Cordova 的应用程序在 Webview 中运行,而 RN 应用程序使用原生视图进行渲染;RN 应用程序可以直接访问底层移动操作系统提供的所有 Native API 和视图,因此具有与本机应用程序相同的开发体验和性能表现。 4 | 5 | React Native 并没有直接将 JS 代码编译到相应的本机代码中,因为 Java 和 Objective C 是强类型语言,而 Javascript 则不是。本质上,React Native 可以被视为一组 React 组件,其中每个组件代表相应的本机视图和组件;例如,TextInput 将具有相应的 RN 组件,该组件可以直接导入到 JS 代码中,并像任何其他 React 组件一样使用。因此,开发人员将像编写任何其他 React Web 应用程序一样编写代码,但输出将是原生应用程序。 6 | 7 | # 架构概览 8 | 9 | React Native 的 iOS 与 Android 版本的架构大同小异,可以认为大体包含以下三个模块:Native Code/Modules, JavaScript VM, Bridge。 10 | 11 | ![image.png](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230430223016.png) 12 | 13 | ## Native Code/Modules 14 | 15 | 这是应用程序启动后立即生成的主要线程。它加载应用程序并启动 JS 线程来执行 Javascript 代码。本机线程还会侦听 UI 事件,例如“press”,“touch”等。然后,这些事件将通过 RN Bridge 传递给 JS 线程。一旦 Javascript 加载,JS 线程就会将需要呈现的内容的信息发送到屏幕上。阴影节点线程使用此信息来计算布局。影子线程基本上就像一个数学引擎,最终决定如何计算视图位置。然后将这些指令传递回主线程以呈现视图。 16 | 17 | 除了由 React Native 生成的线程之外,我们还可以在我们构建的自定义本机模块上生成线程,以加快应用程序的性能。例如 - 动画由 React Native 在一个单独的本机线程中处理,以从 JS 线程卸载工作。 18 | 19 | ## JavaScript VM 20 | 21 | 运行所有 JavaScript 代码的 JS 虚拟机。在 iOS / Android 模拟器和设备上,React Native 使用 JavaScriptCore,它是为 Safari 提供支持的 JavaScript 引擎。JavaScriptCore 是一个最初为 WebKit 构建的开源 JavaScript 引擎。对于 iOS,React Native 使用 iOS 平台提供的 JavaScriptCore。它首先在 iOS 7 中与 OS X Mavericks 一起推出。 22 | 23 | 在 Android 的情况下,React Native 将 JavaScriptCore 与应用程序捆绑在一起。这会增加应用程序的大小。因此,对于 Android,RN 的 Hello World 应用程序将需要大约 3 到 4 兆字节。 24 | 25 | 在 Chrome 调试模式的情况下,JavaScript 代码在 Chrome 本身(而不是设备上的 JavaScriptCore)中运行,并通过 WebSocket 与本机代码通信。在这里,它将使用 V8 引擎。这样,我们就可以看到有关 Chrome 调试工具的大量信息,例如网络请求,控制台日志等。 26 | 27 | Javascript Queue 是主捆绑 JS 线程运行的线程队列。JS 线程运行所有业务逻辑。 28 | 29 | ![image.png](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230516214652.png) 30 | 31 | ## Bridge 32 | 33 | ![image.png](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230516214710.png) 34 | 35 | Bridge 的作用就是给 RN 内嵌的 JS Engine 提供原生接口的扩展供 JS 调用。所有的本地存储、图片资源访问、图形图像绘制、3D 加速、网络访问、震动效果、NFC、原生控件绘制、地图、定位、通知等都是通过 Bridge 封装成 JS 接口以后注入 JS Engine 供 JS 调用。理论上,任何原生代码能实现的效果都可以通过 Bridge 封装成 JS 可以调用的组件和方法, 以 JS 模块的形式提供给 RN 使用。 36 | 37 | 每一个支持 RN 的原生功能必须同时有一个原生模块和一个 JS 模块,JS 模块是原生模块的封装,方便 Javascript 调用其接口。Bridge 会负责管理原生模块和对应 JS 模块之间的沟通, 通过 Bridge, JS 代码能够驱动所有原生接口,实现各种原生酷炫的效果。RN 中 JS 和 Native 分隔非常清晰,JS 不会直接引用 Native 层的对象实例,Native 也不会直接引用 JS 层的对象实例(所有 Native 和 JS 互掉都是通过 Bridge 层会几个最基础的方法衔接的)。 38 | 39 | Bridge 原生代码负责管理原生模块并生成对应的 JS 模块信息供 JS 代码调用。每个功能 JS 层的封装主要是针对 ReactJS 做适配,让原生模块的功能能够更加容易被用 ReactJS 调用。MessageQueue.js 是 Bridge 在 JS 层的代理,所有 JS2N 和 N2JS 的调用都会经过 MessageQueue.js 来转发。JS 和 Native 之间不存在任何指针传递,所有参数都是字符串传递。所有的 instance 都会被在 JS 和 Native 两边分别编号,然后做一个映射,然后那个数字/字符串编号会做为一个查找依据来定位跨界对象。 40 | 41 | # Fabric 42 | 43 | React Native 团队目前正在研究 React Native 的新架构。新架构的代号为 Fabric,允许 React Native 以同步方式执行高优先级 UI 更新。这意味着 UI 在某些边缘情况下(例如滚动视图)会更具响应性。要了解 Fabric 是什么以及它将如何改善 React Native 体验,请观看 Parashuram N 在 React Conf 2018 上的精彩演讲。 44 | -------------------------------------------------------------------------------- /移动应用/iOS/界面开发/TableGrid.md: -------------------------------------------------------------------------------- 1 | # UICollectionView 2 | 3 | > 参考资料 4 | > 5 | > - [UICollectionView 详解][9] 6 | > - [Creating Custom Collection View Cells in iOS7](http://blog.csdn.net/x359981514/article/category/1266042) 7 | > - [An iPhone iOS 6 Storyboard-based Collection View Tutorial](http://www.techotopia.com/index.php/An_iPhone_iOS_6_Storyboard-based_Collection_View_Tutorial) 8 | > - [UICollectionView Tutorial By Swift](http://www.raywenderlich.com/78550/beginning-ios-collection-views-swift-part-1) 9 | 10 | ## UICollectionViewCell:自定义样式 11 | 12 | ### Auto Size 13 | 14 | ``` 15 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 16 | return CGSizeMake(100, 100); 17 | } 18 | ``` 19 | 20 | # UIStackView 21 | 22 | 1. UICollectionView is like a grid, UIStackView is only for 1 dimension: vertical or horizontal. 23 | 24 | UICollectionView is like UITableView, but it supports more than single-column layouts. 25 | 26 | > Collection views provide the same general function as table views except that a collection view is able to support more than just single-column layouts. Collection views support customizable layouts that can be used to implement multi-column grids, tiled layouts, circular layouts, and many more. You can even change the layout of a collection view dynamically if you want. 27 | 28 | vs 29 | 30 | > The UIStackView class provides a streamlined interface for laying out a collection of views in either a column or a row 31 | 32 | 1. For me, With StackView, you benefit the "AutoLayout" feature, for example: you put 4 views in the Stack, this component will decide how those views will be presented on the screen, depending on their size. 33 | 34 | > - [iOS 9: UIStackView 入门](http://www.cocoachina.com/ios/20150623/12233.html) 35 | > - [uistackview-tutorial-introducing-stack-views](http://www.raywenderlich.com/114552/uistackview-tutorial-introducing-stack-views) 36 | 37 | # UIStackView-Extension 38 | 39 | ## [FDStackView](https://github.com/forkingdog/FDStackView) 40 | 41 | 直接在 Podfile 中加入 FDStackView,即可以无缝使用 StackView 及其 N 多的功能。 42 | 43 | ## 44 | 45 | # CardView 46 | 47 | ## [Koloda](https://github.com/Yalantis/Koloda) 48 | 49 | ![Preview](https://github.com/Yalantis/Koloda/raw/master/Koloda_v2_example_animation.gif) 50 | 51 | [1]: http://www.cnblogs.com/kenshincui/p/3931948.html 52 | [2]: http://images.cnitblog.com/blog/62046/201408/232318443782284.png 53 | [3]: http://images.cnitblog.com/blog/62046/201408/232318457064256.png 54 | [4]: /Users/apple/Repo/3F4D5236-0250-4A3F-A521-451CB8A4004B.png 55 | [5]: http://images.cnitblog.com/blog/62046/201408/232318518789574.png 56 | [6]: /Users/apple/Repo/3F66EEF3-E8E5-467B-9ADE-CD7ACE87F97C.png 57 | [7]: https://github.com/forkingdog/UITableView-FDTemplateLayoutCell 58 | [8]: https://github.com/forkingdog/UITableView-FDTemplateLayoutCell/raw/master/Sceenshots/screenshot2.gif 59 | [9]: http://www.cnblogs.com/ios8/p/iOS-UICollectionView.html 60 | -------------------------------------------------------------------------------- /移动应用/iOS/系统功能/数据存储.md: -------------------------------------------------------------------------------- 1 | # File(文件型) 2 | 3 | ## FileBrowser 4 | 5 | ### [Swift-FileBrowser](https://github.com/marmelroy/FileBrowser) 6 | 7 | ![](https://camo.githubusercontent.com/5ea19d119a5426eeca3edbe750c280617f804aa0/687474703a2f2f692e67697068792e636f6d2f336f3667615936794c516b686a696f6b35572e676966) 8 | 9 | # Cache(缓存型) 10 | 11 | ## NSUserDefault 12 | 13 | 类似于 Android 中的 SharedPreference 以及 Web 的 LocalStorage,iOS 中也提供了便捷的可持久化的存储方案:NSUserDefault。NSUserDefault 更多的表现像个属性列表,可以看做 plist 文件的封装好的接口。NSUserDefaults 中主要可以存放以下六类接口: 14 | 15 | - NSData 16 | - NSString 17 | - NSNumber 18 | - NSDate 19 | - NSArray 20 | - NSDictionary 21 | 22 | ### WriteData(写入数据) 23 | 24 | 创建一个 user defaults 方法有多个,最简单得快速创建方法: 25 | 26 | ```objective-c 27 | NSUserDefaults *accountDefaults = [NSUserDefaults standardUserDefaults]; 28 | //添加数据到 user defaults: 29 | [accountDefaults setObject:nameField.text forKey:UserDefaultNameKey]; 30 | ``` 31 | 32 | 也可以添加基本数据类型 int, float, bool 等,有相应得方法 33 | 34 | ```objective-c 35 | [accountDefaults setBool:YES forKey:UserDefaultBoolKey]; 36 | ``` 37 | 38 | 如果是 Swift,用法如下: 39 | 40 | ```swift 41 | let defaults = NSUserDefaults.standardUserDefaults() 42 | defaults.setObject("Coding Explorer", forKey: "userNameKey") 43 | ``` 44 | 45 | ### ReadData(读取数据) 46 | 47 | 从 UserDefault 中读取数据,方式就像从一个全局的 Dictionary 中读取数据: 48 | 49 | ```objective-c 50 | [accountDefaults objectForKey:NCUserDefaultNameKey] 51 | [accountDefaults boolForKey: UserDefaultBoolKey]; 52 | ``` 53 | 54 | 如果是 Swift,读取方式如下所示: 55 | 56 | ```swift 57 | let defaults = NSUserDefaults.standardUserDefaults() 58 | if let name = defaults.stringForKey("userNameKey") 59 | { 60 | println(name) 61 | } 62 | ``` 63 | 64 | # CoreData 65 | 66 | # SQLite 67 | 68 | ## [FMDB](https://github.com/ccgus/fmdb) 69 | 70 | ## [SQLite.swift](https://github.com/stephencelis/SQLite.swift) 71 | 72 | ![SQLite.playground Screen Shot](https://github.com/stephencelis/SQLite.swift/raw/master/Documentation/Resources/playground@2x.png) 73 | 74 | # Realm 75 | 76 | ### Primary Keys 77 | 78 | ```objective-c 79 | @interface Dog : RLMObject 80 | @property int key; 81 | @property NSString *name; 82 | @property NSInteger age; 83 | @end 84 | 85 | @implementation Dog : RLMObject 86 | + (NSString *)primaryKey { 87 | return @"key"; 88 | } 89 | @end 90 | 91 | RLM_ARRAY_TYPE(Dog) 92 | 93 | @interface Owner : RLMObject 94 | @property int key; 95 | @property NSString *name; 96 | @property RLMArray *dogs; 97 | @end 98 | 99 | @implementation Owner : RLMObject 100 | + (NSString *)primaryKey { 101 | return @"key"; 102 | } 103 | @end 104 | ``` 105 | 106 | 在插入数据时,如果发现主键中数据存在重复的则会抛出异常: 107 | 108 | ``` 109 | Owner *owner = [Owner createOrUpdateInDefaultRealmWithObject:@{ 110 | @"key": @0, 111 | @"name": @"Tim", 112 | @"dogs": @[ 113 | @{@"key": @5, @"name": @"Rex", @"age": @3} 114 | ] 115 | }]; 116 | 117 | NSLog(@"Number of owners: %u", (unsigned)[Owner allObjects].count); 118 | NSLog(@"Number of dogs: %u", (unsigned)owner.dogs.count); 119 | for (Dog *dog in owner.dogs) { 120 | NSLog(@"Dog %@", dog); 121 | } 122 | ``` 123 | -------------------------------------------------------------------------------- /移动应用/React Native/语法基础/布局基础.md: -------------------------------------------------------------------------------- 1 | # 语法基础 2 | 3 | # 声明组件 4 | 5 | ```js 6 | import React, { Component } from "react"; 7 | import { AppRegistry, Text } from "react-native"; 8 | 9 | class HelloWorldApp extends Component { 10 | render() { 11 | return Hello world!; 12 | } 13 | } 14 | 15 | AppRegistry.registerComponent("HelloWorldApp", () => HelloWorldApp); 16 | ``` 17 | 18 | 在 React Native 中,只有组件能够接受文本作为子节点,换言之,如下的写法是错的: 19 | 20 | ```html 21 | Text doesn't go here! 22 | ``` 23 | 24 | 与之相对的,应该将文本包裹在组件中: 25 | 26 | ```html 27 | 28 | This is okay! 29 | 30 | ``` 31 | 32 | 在 HTML 中,如果需要展示一系列具有复杂格式的字符串,可能需要``、``等动态组合,譬如: 33 | 34 | ```html 35 |

The quick brown fox jumped over the lazy dog.

36 | ``` 37 | 38 | 而在于 React Native 中,只有组件可以用来包裹某个文本,而如果需要对其属性进行控制,则需要利用其 Style,而不是添加``、``这些标签。 39 | 40 | ```html 41 | 42 | The quick brown fox 43 | jumped over the lazy dog. 44 | 45 | ``` 46 | 47 | 不过上述这种直接把属性写在组件里的方式会将整个代码变得非常的冗长,可以以另一种方式进行处理,譬如将独特功能的组件封装为特殊的组件: 48 | 49 | ```js 50 | var styles = StyleSheet.create({ 51 | bold: { 52 | fontWeight: "bold", 53 | }, 54 | italic: { 55 | fontStyle: "italic", 56 | }, 57 | }); 58 | 59 | var Strong = React.createClass({ 60 | render: function () { 61 | return {this.props.children}; 62 | }, 63 | }); 64 | 65 | var Em = React.createClass({ 66 | render: function () { 67 | return {this.props.children}; 68 | }, 69 | }); 70 | ``` 71 | 72 | 在使用了这样的自带样式的组件之后,可以以一种非常类似于 HTML 的方式去使用这些组件,譬如: 73 | 74 | ```js 75 | 76 | The quick brown fox jumped over the lazy dog. 77 | 78 | ``` 79 | 80 | ``` 81 | import { Text, View } from 'react-native'; 82 | import React from 'react'; 83 | 84 | const CustomComponent = props => ( 85 | 88 | 91 | 94 | 95 | 96 | 97 | ); 98 | 99 | CustomComponent.propTypes = { 100 | containerStyle: View.propTypes.style, 101 | someCoolTextStyle: Text.propTypes.style, 102 | someCoolContainerStyle: View.propTypes.style, 103 | }; 104 | 105 | export default CustomComponent; 106 | ``` 107 | 108 | # 样式与布局 109 | 110 | 在讲解 React Native 的样式之前,需要先介绍下 FaceBook 本身推荐的模式,它强烈的推荐重用带样式的组件而不是样式本身。并且 React Native 是不支持样式继承的,如它在文档中所述: 111 | 112 | > 你并没有被允许去为整个节点树设置一个默认的譬如字体这样的属性,推荐的方式是通过创建一个带有一致的字体样式的组件并且在整个应用中通过重用组件的方式来完成样式的统一与继承。 113 | > 在 CSS 种,Media Query 是非常常用的技巧之一,而在 React Native 种,并不能够直接运用上 CSS 的一些属性,不过可以使用如下方式: 114 | 115 | ```java 116 | var Dimensions = require('Dimensions'); 117 | var { width, height } = Dimensions.get('window'); 118 | ``` 119 | 120 | ## Flexbox 布局 121 | 122 | # 交互事件响应 123 | 124 | # Links 125 | 126 | - [2016~React Native Training](https://unbug.gitbooks.io/react-native-training/content/) 127 | - [Handling Text Input](https://facebook.github.io/react-native/docs/handling-text-input.html) 128 | -------------------------------------------------------------------------------- /移动应用/React Native/架构原理/Bridge.md: -------------------------------------------------------------------------------- 1 | # Bridge 2 | 3 | # RCTRootView 4 | 5 | RCTRootView 是 React Native 加载的地方,是万物之源。从这里开始,我们有了 JS Engine, JS 代码被加载进来,对应的原生模块也被加载进来,然后 js loop 开始运行。js loop 的驱动来源是 Timer 和 Event Loop(用户事件). js loop 跑起来以后应用就可以持续不停地跑下去了。 6 | 7 | 如果你要通过调试来理解 RN 底层原理,你也应该是从 RCTRootView 着手,顺藤摸瓜。 8 | 9 | 每个项目的 AppDelegate.m 的- (BOOL)application:didFinishLaunchingWithOptions:里面都可以看到 RCTRootView 的初始化代码,RCTRootView 初始化完成以后,整个 React Native 运行环境就已经初始化好了,JS 代码也加载完毕,所有 React 的绘制都会有这个 RCTRootView 来管理。 10 | 11 | RCTRootView 做的事情如下: 12 | 13 | 创建并且持有 RCTBridge 14 | 加载 JS Bundle 并且初始化 JS 运行环境. 15 | 初始化 JS 运行环境的时候在 App 里面显示 loadingView, 注意不是屏幕顶部的那个下拉悬浮进度提示条. RN 第一次加载之后每次启动非常快,很少能意识到这个加载过程了。loadingView 默认情况下为空, 也就是默认是没有效果的。loadingView 可以被自定义,直接覆盖 RCTRootView.loadingView 就可以了.开发模式下 RN app 第一次启动因为需要完整打包整个 js 所以可以很明显看到加载的过程,加载第一次以后就看不到很明显的加载过程了,可以执行下面的命令来触发重新打包整个 js 来观察 loadingView 的效果 `watchman watch-del-all && rm -rf node_modules/ && yarn install && yarn start – –reset-cache`, 然后杀掉 app 重启你就会看到一个很明显的进度提示. 16 | JS 运行环境准备好以后把加载视图用 RCTRootContentView 替换加载视图. 17 | 所有准备工作就绪以后调用 AppRegistry.runApplication 正式启动 RN JS 代码,从 Root Component()开始 UI 绘制。 18 | 一个 App 可以有多个 RCTRootView, 初始化的时候需要手动传输 Bridge 做为参数,全局可以有多个 RCTRootView, 但是只能有一个 Bridge. 19 | 20 | 如果你做过 React Native 和原生代码混编,你会发现混编就是把 AppDelegate 里面那段初始化 RCTRootView 的代码移动到需要混编的地方,然后把 RCTRootView 做为一个普通的 subview 来加载到原生的 view 里面去,非常简单。不过这地方也要注意处理好单 Bridge 实例的问题,同时,混编里面要注意 RCTRootView 如果销毁过早可能会引发 JS 回调奔溃的问题。 21 | 22 | # RCTRootContentView 23 | 24 | RCTRootContentView reactTag 在默认情况下为 1. 在 Xcode view Hierarchy debugger 下可以看到,最顶层为 RCTRootView, 里面嵌套的是 RCTRootContentView, 从 RCTRootContentView 开始,每个 View 都有一个 reactTag. 25 | 26 | RCTRootView 继承自 UIView, RCTRootView 主要负责初始化 JS Environment 和 React 代码,然后管理整个运行环境的生命周期。RCTRootContentView 继承自 RCTView, RCTView 继承自 UIView, RCTView 封装了 React Component Node 更新和渲染的逻辑,RCTRootContentView 会管理所有 react ui components. RCTRootContentView 同时负责处理所有 touch 事件. 27 | 28 | # RCTBridge 29 | 30 | 这是一个加载和初始化专用类,用于前期 JS 的初始化和原生代码的加载。 31 | 32 | 负责加载各个 Bridge 模块供 JS 调用 33 | 找到并注册所有实现了 RCTBridgeModule protocol 的类, 供 JS 后期使用. 34 | 创建和持有 RCTBatchedBridge 35 | RCTBatchedBridge 36 | 如果 RCTBridge 是总裁, 那么 RCTBatchedBridge 就是副总裁。前者负责发号施令,后者负责实施落地。 37 | 38 | 负责 Native 和 JS 之间的相互调用(消息通信) 39 | 持有 JSExecutor 40 | 实例化所有在 RCTBridge 里面注册了的 native node_modules 41 | 创建 JS 运行环境, 注入 native hooks 和 modules, 执行 JS bundle script 42 | 管理 JS run loop, 批量把所有 JS 到 native 的调用翻译成 native invocations 43 | 批量管理原生代码到 JS 的调用,把这些调用翻译成 JS 消息发送给 JS executor 44 | 45 | # RCTJavaScriptLoader 46 | 47 | 这是实现远程代码加载的核心。热更新,开发环境代码加载,静态 jsbundle 加载都离不开这个工具。 48 | 49 | 从指定的地方(bundle, http server)加载 script bundle 50 | 把加载完成的脚本用 string 的形式返回 51 | 处理所有获取代码、打包代码时遇到的错误 52 | RCTContextExecutor 53 | 封装了基础的 JS 和原生代码互掉和管理逻辑,是 JS 引擎切换的基础。通过不同的 RCTCOntextExecutor 来适配不同的 JS Engine,让我们的 React JS 可以在 iOS、Android、chrome 甚至是自定义的 js engine 里面执行。这也是为何我们能在 chrome 里面直接调试 js 代码的原因。 54 | 55 | 管理和执行所有 N2J 调用 56 | 57 | # RCTModuleData 58 | 59 | 加载和管理所有和 JS 有交互的原生代码。把需要和 JS 交互的代码按照一定的规则自动封装成 JS 模块。 60 | 61 | 收集所有桥接模块的信息,供注入到 JS 运行环境 62 | 63 | # RCTModuleMethod 64 | 65 | 记录所有原生代码的导出函数地址(JS 里面是不能直接持有原生对象的),同时生成对应的字符串映射到该函数地址。JS 调用原生函数的时候会通过 message 的形式调用过来。 66 | 67 | 记录所有的原生代码的函数地址,并且生成对应的字符串映射到该地址 68 | 记录所有的 block 的地址并且映射到唯一的一个 id 69 | 翻译所有 J2N call,然后执行对应的 native 方法。 70 | 如果是原生方法的调用则直接通过方法名调用,MessageQueue 会帮忙把 Method 翻译成 MethodID, 然后转发消息给原生代码,传递函数签名和参数给原生 MessageQueue, 最终给 RCTModuleMethod 解析调用最终的方法 71 | 如果 JS 调用的是一个回调 block,MessageQueue 会把回调对象转化成一个一次性的 block id, 然后传递给 RCTModuleMethod, 最终由 RCTModuleMethod 解析调用。基本上和方法调用一样,只不过生命周期会不一样,block 是动态生成的,要及时销毁,要不然会导致内存泄漏。 72 | 注: 73 | 74 | 实际上是不存在原生 MessageQueue 对象模块的,JS 的 MessageQueue 对应到原生层就是 RCTModuleData & RCTModuleMethod 的组合, MessageQueue 的到原生层的调用先经过 RCTModuleData 和 RCTModuleMethod 翻译成原生代码调用,然后执行. 75 | -------------------------------------------------------------------------------- /_sidebar.md: -------------------------------------------------------------------------------- 1 | - [1 INTRODUCTION](/INTRODUCTION.md) 2 | - [2 Web](/Web/README.md) 3 | 4 | - [3 图形与可视化](/图形与可视化/README.md) 5 | 6 | - [4 游戏 [1]](/游戏/README.md) 7 | - [4.1 Web 2D](/游戏/Web%202D/README.md) 8 | 9 | - 5 移动应用 [5] 10 | - [5.1 Android [4]](/移动应用/Android/README.md) 11 | - 5.1.1 工程实践 [4] 12 | - [5.1.1.1 Android Development : Some of the best practices](/移动应用/Android/工程实践/Android%20Development%20:%20Some%20of%20the%20best%20practices.md) 13 | - [5.1.1.2 Apk 解析](/移动应用/Android/工程实践/Apk%20解析.md) 14 | - [5.1.1.3 Best Practices for Android Developer Productivity](/移动应用/Android/工程实践/Best%20Practices%20for%20Android%20Developer%20Productivity.md) 15 | - [5.1.1.4 应用加固](/移动应用/Android/工程实践/应用加固.md) 16 | - 5.1.2 界面开发 [6] 17 | - [5.1.2.1 ActionBar](/移动应用/Android/界面开发/ActionBar.md) 18 | - [5.1.2.2 Activity](/移动应用/Android/界面开发/Activity.md) 19 | - [5.1.2.3 AdapterView](/移动应用/Android/界面开发/AdapterView.md) 20 | - [5.1.2.4 Gallery](/移动应用/Android/界面开发/Gallery.md) 21 | - [5.1.2.5 RecycleView](/移动应用/Android/界面开发/RecycleView.md) 22 | - [5.1.2.6 界面开发基础](/移动应用/Android/界面开发/界面开发基础.md) 23 | - 5.1.3 系统功能 [2] 24 | - [5.1.3.1 网络请求](/移动应用/Android/系统功能/网络请求.md) 25 | - [5.1.3.2 高性能 OkHttp](/移动应用/Android/系统功能/高性能%20OkHttp.md) 26 | - 5.1.4 资源处理 [1] 27 | - [5.1.4.1 字符串](/移动应用/Android/资源处理/字符串.md) 28 | - [5.2 Flutter [1]](/移动应用/Flutter/README.md) 29 | - [5.2.1 引擎与运行时](/移动应用/Flutter/引擎与运行时/README.md) 30 | 31 | - [5.3 React Native [5]](/移动应用/React%20Native/README.md) 32 | - [5.3.1 Expo](/移动应用/React%20Native/Expo/README.md) 33 | 34 | - 5.3.2 工程实践 [1] 35 | - [5.3.2.1 调试与测试](/移动应用/React%20Native/工程实践/调试与测试.md) 36 | - [5.3.3 架构原理 [4]](/移动应用/React%20Native/架构原理/README.md) 37 | - [5.3.3.1 Bridge](/移动应用/React%20Native/架构原理/Bridge.md) 38 | - [5.3.3.2 原理与机制](/移动应用/React%20Native/架构原理/原理与机制.md) 39 | - [5.3.3.3 启动流程](/移动应用/React%20Native/架构原理/启动流程.md) 40 | - [5.3.3.4 实践技巧](/移动应用/React%20Native/架构原理/实践技巧.md) 41 | - 5.3.4 组件优化 [1] 42 | - [5.3.4.1 列表优化](/移动应用/React%20Native/组件优化/列表优化.md) 43 | - 5.3.5 语法基础 [7] 44 | - [5.3.5.1 事件与手势](/移动应用/React%20Native/语法基础/事件与手势.md) 45 | - [5.3.5.2 布局基础](/移动应用/React%20Native/语法基础/布局基础.md) 46 | - [5.3.5.3 环境搭建与工具链](/移动应用/React%20Native/语法基础/环境搭建与工具链.md) 47 | - [5.3.5.4 界面开发](/移动应用/React%20Native/语法基础/界面开发.md) 48 | - [5.3.5.5 组件基础](/移动应用/React%20Native/语法基础/组件基础.md) 49 | - [5.3.5.6 组件基础.old](/移动应用/React%20Native/语法基础/组件基础.old.md) 50 | - [5.3.5.7 路由导航](/移动应用/React%20Native/语法基础/路由导航.md) 51 | - [5.4 WebView [2]](/移动应用/WebView/README.md) 52 | - 5.4.1 Cordova [1] 53 | - [5.4.1.1 iOS](/移动应用/WebView/Cordova/iOS.md) 54 | - [5.4.2 Ionic](/移动应用/WebView/Ionic/README.md) 55 | 56 | - [5.5 iOS [5]](/移动应用/iOS/README.md) 57 | - [5.5.1 AppStore](/移动应用/iOS/AppStore/README.md) 58 | 59 | - 5.5.2 Objective C [1] 60 | - [5.5.2.1 语法基础](/移动应用/iOS/Objective-C/语法基础.md) 61 | - 5.5.3 Swift [1] 62 | - [5.5.3.1 Linkedin 样式指南](/移动应用/iOS/Swift/Linkedin%20样式指南.md) 63 | - 5.5.4 界面开发 [4] 64 | - [5.5.4.1 TableGrid](/移动应用/iOS/界面开发/TableGrid.md) 65 | - [5.5.4.2 UITableView](/移动应用/iOS/界面开发/UITableView.md) 66 | - [5.5.4.3 事件与手势](/移动应用/iOS/界面开发/事件与手势.md) 67 | - [5.5.4.4 界面基础](/移动应用/iOS/界面开发/界面基础.md) 68 | - 5.5.5 系统功能 [4] 69 | - [5.5.5.1 HealthCheck](/移动应用/iOS/系统功能/HealthCheck.md) 70 | - [5.5.5.2 数据存储](/移动应用/iOS/系统功能/数据存储.md) 71 | - [5.5.5.3 系统进程](/移动应用/iOS/系统功能/系统进程.md) 72 | - [5.5.5.4 网络请求](/移动应用/iOS/系统功能/网络请求.md) 73 | - [6 虚拟现实](/虚拟现实/README.md) 74 | -------------------------------------------------------------------------------- /移动应用/iOS/系统功能/网络请求.md: -------------------------------------------------------------------------------- 1 | # NetworkManagement 2 | 3 | ## Reachability(可达性判断) 4 | 5 | ### [RealReachability](https://github.com/dustturtle/RealReachability) 6 | 7 | To integrate RealReachability into your Xcode project using CocoaPods, specify it in your `Podfile`: 8 | 9 | ``` 10 | source 'https://github.com/CocoaPods/Specs.git' 11 | platform :ios, '6.0' 12 | 13 | pod 'RealReachability', '~> 1.1.1' 14 | ``` 15 | 16 | Then, run the following command: 17 | 18 | ``` 19 | $ pod install 20 | ``` 21 | 22 | #### Start to notify 23 | 24 | ``` 25 | [GLobalRealReachability startNotifier]; 26 | [[NSNotificationCenter defaultCenter] addObserver:self 27 | selector:@selector(networkChanged:) 28 | name:kRealReachabilityChangedNotification 29 | object:nil]; 30 | ``` 31 | 32 | #### [](https://github.com/dustturtle/RealReachability#observer-like-below)Observer like below 33 | 34 | ``` 35 | - (void)networkChanged:(NSNotification *)notification 36 | { 37 | RealReachability *reachability = (RealReachability *)notification.object; 38 | ReachabilityStatus status = [reachability currentReachabilityStatus]; 39 | NSLog(@"currentStatus:%@",@(status)); 40 | } 41 | ``` 42 | 43 | #### [](https://github.com/dustturtle/RealReachability#trigger-realtime-reachability-like-below)Trigger realtime Reachability like below 44 | 45 | ``` 46 | [GLobalRealReachability reachabilityWithBlock:^(ReachabilityStatus status) { 47 | switch (status) 48 | { 49 | case RealStatusNotReachable: 50 | { 51 | // case NotReachable handler 52 | break; 53 | } 54 | 55 | case RealStatusViaWiFi: 56 | { 57 | // case WiFi handler 58 | break; 59 | } 60 | 61 | case RealStatusViaWWAN: 62 | { 63 | // case WWAN handler 64 | break; 65 | } 66 | 67 | default: 68 | break; 69 | } 70 | }]; 71 | ``` 72 | 73 | #### [](https://github.com/dustturtle/RealReachability#query-currentstatus)Query currentStatus 74 | 75 | ``` 76 | ReachabilityStatus status = [reachability currentReachabilityStatus]; 77 | ``` 78 | 79 | # HTTP Client 80 | 81 | ## Alamofire 82 | 83 | - [Alamofire Github 主页](https://github.com/Alamofire/Alamofire) 84 | 85 | # HTTP Stubs 86 | 87 | ## [OHHTTPStubs](https://github.com/AliSoftware/OHHTTPStubs/) 88 | 89 | ### Basic example 90 | 91 | #### [](https://github.com/AliSoftware/OHHTTPStubs/#in-objective-c)In Objective-C 92 | 93 | ``` 94 | [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { 95 | return [request.URL.host isEqualToString:@"mywebservice.com"]; 96 | } withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) { 97 | // Stub it with our "wsresponse.json" stub file (which is in same bundle as self) 98 | NSString* fixture = OHPathForFile(@"wsresponse.json", self.class); 99 | return [OHHTTPStubsResponse responseWithFileAtPath:fixture 100 | statusCode:200 headers:@{@"Content-Type":@"application/json"}]; 101 | }]; 102 | ``` 103 | 104 | #### [](https://github.com/AliSoftware/OHHTTPStubs/#in-swift)In Swift 105 | 106 | This example is using the Swift helpers found in `OHHTTPStubsSwift.swift` provided by the `OHHTTPStubs/Swift` subspec 107 | 108 | ``` 109 | stub(isHost("mywebservice.com")) { _ in 110 | // Stub it with our "wsresponse.json" stub file (which is in same bundle as self) 111 | let stubPath = OHPathForFile("wsresponse.json", self.dynamicType) 112 | return fixture(stubPath!, headers: ["Content-Type":"application/json"]) 113 | } 114 | ``` 115 | -------------------------------------------------------------------------------- /移动应用/React Native/语法基础/环境搭建与工具链.md: -------------------------------------------------------------------------------- 1 | # React Native 开发环境搭建与工具链介绍 2 | 3 | # iOS 环境搭建 4 | 5 | 必须安装的依赖有:Node、Watchman 和 React Native 命令行工具以及 Xcode。虽然你可以使用任何编辑器来开发应用(编写 js 代码),但你仍然必须安装 Xcode 来获得编译 iOS 应用所需的工具和环境。 6 | 7 | ## Node, Watchman 8 | 9 | 我们推荐使用 [Homebrew](http://brew.sh/) 来安装 Node 和 Watchman。在命令行中执行下列命令安装: 10 | 11 | ```sh 12 | brew install node 13 | brew install watchman 14 | ``` 15 | 16 | 如果你已经安装了 Node,请检查其版本是否在 v8.3 以上。安装完 Node 后建议设置 npm 镜像以加速后面的过程(或使用科学上网工具)。 17 | 18 | 设置 npm 镜像: 19 | 20 | ``` 21 | npm config set registry https://registry.npm.taobao.org --global 22 | npm config set disturl https://npm.taobao.org/dist --global 23 | ``` 24 | 25 | 或者使用 [nrm](https://github.com/Pana/nrm): 26 | 27 | ```sh 28 | $ nrm ls 29 | 30 | * npm ----- https://registry.npmjs.org/ 31 | cnpm ---- http://r.cnpmjs.org/ 32 | taobao -- https://registry.npm.taobao.org/ 33 | nj ------ https://registry.nodejitsu.com/ 34 | skimdb -- https://skimdb.npmjs.com/registry 35 | $ nrm use cnpm //switch registry to cnpm 36 | 37 | Registry has been set to: http://r.cnpmjs.org/ 38 | ``` 39 | 40 | [Watchman](https://facebook.github.io/watchman) 则是由 Facebook 提供的监视文件系统变更的工具。安装此工具可以提高开发时的性能(packager 可以快速捕捉文件的变化从而实现实时刷新)。 41 | 42 | ## Yarn、React Native 的命令行工具(react-native-cli) 43 | 44 | Yarn 是 Facebook 提供的替代 npm 的工具,可以加速 node 模块的下载。React Native 的命令行工具用于执行创建、初始化、更新项目、运行打包服务(packager)等任务。 45 | 46 | ```sh 47 | npm install -g yarn react-native-cli 48 | ``` 49 | 50 | 安装完 Yarn 后同理也要设置镜像源: 51 | 52 | ```sh 53 | yarn config set registry https://registry.npm.taobao.org --global 54 | yarn config set disturl https://npm.taobao.org/dist --global 55 | ``` 56 | 57 | 安装完 yarn 之后就可以用 yarn 代替 npm 了,例如用 yarn 代替 npm install 命令,用 yarn add 某第三方库名代替 npm install 某第三方库名。 58 | 59 | ## Xcode 60 | 61 | React Native 目前需要 [Xcode](https://developer.apple.com/xcode/downloads/) 9.4 或更高版本。你可以通过 App Store 或是到 [Apple 开发者官网](https://developer.apple.com/xcode/downloads/) 上下载。这一步骤会同时安装 Xcode IDE、Xcode 的命令行工具和 iOS 模拟器。 62 | 63 | - Xcode 的命令行工具 64 | 65 | 启动 Xcode,并在 `Xcode | Preferences | Locations` 菜单中检查一下是否装有某个版本的 `Command Line Tools`。Xcode 的命令行工具中包含一些必须的工具,比如 `git` 等。 66 | 67 | ![](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230417210009.png) 68 | 69 | # 搭建 Android 开发环境 70 | 71 | 必须安装的依赖有:Node、Watchman 和 React Native 命令行工具以及 JDK 和 Android Studio。虽然你可以使用任何编辑器来开发应用(编写 js 代码),但你仍然必须安装 Android Studio 来获得编译 Android 应用所需的工具和环境。 72 | 73 | ## Java Development Kit 74 | 75 | React Native 需要 Java Development Kit [JDK] 1.8(暂不支持 1.9 及更高版本)。你可以在命令行中输入 `javac -version` 来查看你当前安装的 JDK 版本。如果版本不合要求,则可以到官网上下载。 76 | 77 | ## Android 开发环境 78 | 79 | ### 安装 Android Studio 80 | 81 | 首先下载和安装 [Android Studio](https://developer.android.com/studio/index.html),国内用户可能无法打开官方链接,请自行使用搜索引擎搜索可用的下载链接。安装界面中选择"Custom"选项,确保选中了以下几项: 82 | 83 | - Android SDK 84 | - Android SDK Platform 85 | - Performance (Intel ® HAXM) 86 | - Android Virtual Device 87 | 88 | 然后点击"Next"来安装选中的组件,安装完成后,看到欢迎界面时,就可以进行下面的操作了。 89 | 90 | ### 安装 Android SDK 91 | 92 | Android Studio 默认会安装最新版本的 Android SDK。目前编译 React Native 应用需要的是 Android 6.0 (Marshmallow) 版本的 SDK(注意 SDK 版本不等于终端系统版本,RN 目前支持 android 4.1 以上设备)。你可以在 Android Studio 的 SDK Manager 中选择安装各版本的 SDK。 93 | 94 | 你可以在 Android Studio 的欢迎界面中找到 SDK Manager。点击 "Configure",然后就能看到 "SDK Manager"。SDK Manager 还可以在 Android Studio 的 "Preferences" 菜单中找到。具体路径是 `Appearance & Behavior → System Settings → Android SDK`。 95 | 96 | ![](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230417210029.png) 97 | 98 | 在 SDK Manager 中选择 "SDK Platforms"选项卡,然后在右下角勾选 "Show Package Details"。展开 Android 6.0 (Marshmallow) 选项,确保勾选了下面这些组件: 99 | 100 | - Android SDK Platform 28 101 | - Intel x86 Atom_64 System Image(官方模拟器镜像文件,使用非官方模拟器不需要安装此组件) 102 | 103 | # 开发 104 | 105 | # 调试 106 | 107 | # 原生集成 108 | 109 | # 错误 110 | 111 | # Links 112 | 113 | - https://nervjs.github.io/taro/docs/react-native.html#%E4%BD%BF%E7%94%A8%E5%8E%9F%E7%94%9F%E6%A8%A1%E5%9D%97 114 | -------------------------------------------------------------------------------- /移动应用/Android/界面开发/Gallery.md: -------------------------------------------------------------------------------- 1 | # ImageView 2 | 3 | ## HoverView:图片浮层 4 | 5 | ### [**AndroidViewHover**](https://github.com/daimajia/AndroidViewHover) 6 | 7 | ![](https://camo.githubusercontent.com/44affb72f0688c213500917009a1680b41492413/687474703a2f2f7777322e73696e61696d672e636e2f6d773639302f36313064633033346a7731656a356969686a746c35673230387a3066326e70642e676966) 8 | 9 | # ImageView-Loader 10 | 11 | Picasso 是一个专门的面向图片任务的 HTTP 库。譬如,可以用一行代码就把网络图片加载到 ImageView 中: 12 | 13 | ``` 14 | Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView); 15 | ``` 16 | 17 | Picasso 与 Retrofit 都是默认的使用 OkHttpClient 作为底层的 HTTP 客户端,然而,你也可以配置自己的基于 HttpUrlConnection 的客户端。 18 | 19 | [Glide](https://github.com/bumptech/glide/wiki)是一个非常类似于 Picasso 的库,不过它提供了一些额外的功能,譬如 GIF 动画、简略图生成以及视频。 20 | 21 | Facebook 开源的它们自己的图片加载库[Fresco](https://code.facebook.com/posts/366199913563917/introducing-fresco-a-new-image-library-for-android/)使用了它们自定义的 Android 客户端。其中它的一个非常优秀的特性在于 Fresco 的面向 Bitmaps 的自定义才存储策略能够避免 JVM 堆顶的垃圾回收的限制。Fresco 是分配了 Android 中被称为 ashmem 部分的内存,同时,它用了一些方法允许同时从 Java 以及 C++代码访问 ashmem 部分,来进行 NDK 级别的 CPU 处理。为了节省数据存储空间以及 CPU 的消耗,Fresco 有三层不同的缓存:两层在内存中,以及一层在内部存储中。 22 | 23 | # ImageProcess:图片处理 24 | 25 | ## Label(标签) 26 | 27 | ## Sticker(贴纸) 28 | 29 | ### [StickerView](https://github.com/nimengbo/StickerView) 30 | 31 | ![](https://github.com/nimengbo/StickerView/raw/master/stickerGIF.gif) 32 | 33 | ## Crop(裁剪) 34 | 35 | ### [SimpleCropView](https://github.com/IsseiAoki/SimpleCropView) 36 | 37 | ![demo](https://camo.githubusercontent.com/7ef872746a0181356ea0b44d94b7bd939f28c5ae/68747470733a2f2f7261772e6769746875622e636f6d2f77696b692f4973736569416f6b692f53696d706c6543726f70566965772f696d616765732f6769662f64656d6f5f62617369635f75736167652e676966) 38 | 39 | # Focus(图片缩放浏览) 40 | 41 | ### [Pull Back Layout](https://github.com/oxoooo/pull-back-layout) 42 | 43 | ![](https://github.com/oxoooo/pull-back-layout/raw/master/screenshot.gif) 44 | 45 | Pull-Back-Layout 本质上提供了一层对于 ImageView 或者 ViewPager 的封装,允许以下拉方式关闭某个放大的 ImageView 或者 ViewPager。 46 | 47 | - _Installation_ 48 | 49 | ``` 50 | repositories { 51 | // ... 52 | maven { url "https://jitpack.io" } 53 | } 54 | 55 | dependencies { 56 | // ... support library ... 57 | // ... 58 | compile 'com.github.oxoooo:pull-back-layout:1.0.0' 59 | } 60 | ``` 61 | 62 | - _Usage_ 63 | 64 | 1. Make your Activity translucent by adding these two lines to your theme: 65 | 66 | ``` 67 | 77 | ``` 78 | 79 | 2. Wraps `ooo.oxo.library.widget.PullBackLayout` around your `ImageView` or `ViewPager`: 80 | 81 | ``` 82 | 86 | 87 | 91 | 92 | 93 | ``` 94 | 95 | 3. Set a callback: 96 | 97 | ``` 98 | public class ViewerActivity extends AppCompatActivity implements PullBackLayout.Callback { 99 | 100 | @Override 101 | protected void onCreate(Bundle savedInstanceState) { 102 | super.onCreate(savedInstanceState); 103 | /* ... */ 104 | puller.setCallback(this); 105 | } 106 | 107 | @Override 108 | public void onPullStart() { 109 | // fade out Action Bar ... 110 | // show Status Bar ... 111 | } 112 | 113 | @Override 114 | public void onPull(float progress) { 115 | // set the opacity of the window's background 116 | } 117 | 118 | @Override 119 | public void onPullCancel() { 120 | // fade in Action Bar 121 | } 122 | 123 | @Override 124 | public void onPullComplete() { 125 | supportFinishAfterTransition(); 126 | } 127 | 128 | } 129 | ``` 130 | -------------------------------------------------------------------------------- /移动应用/Android/工程实践/Best Practices for Android Developer Productivity.md: -------------------------------------------------------------------------------- 1 | - [原文地址](https://medium.com/@sergii/best-practices-for-android-developer-productivity-cfd6ffba804c#.bp2tjpwt0) 2 | 3 | 软件工程师的工作效率不仅依赖于知识的掌握程度与经验,也会依赖于你选择的工具集合、合适的环境配置以及团队内的合作技巧。本文即是作者在[Droidcon Berlin](http://droidcon.de/en/sessions/effective-android-development)上演讲的总结。 4 | 5 | # 看穿你的 AndroidManifest 6 | 7 | 众所周知,我们开发时候在文本编辑器中看到的`AndroidManifest.xml`并非最终打包到应用中的`AndroidManifest.xml`文件。在打包时,编译器会自动地将你所使用的第三方依赖库中的譬如``这些元素从它们自己的 Manifests 文件中抽取出来然后统一在主入口的`AndroidManifest.xml`文件中声明,关于这部分的详细说明可以参考[这里](https://commonsware.com/blog/2015/06/25/hey-where-did-these-permissions-come-from.html)。Android Studio 2.2 中提供了一个新特性,[Merged Manifest Viewer](http://android-developers.blogspot.de/2016/05/android-studio-22-preview-new-ui.html)可以用于预览在 APK 构建之后最终生成的`AndroidManifest.xml`文件,该文件中会包含项目依赖中声明的构建类型、变量等等信息。 8 | 9 | # 好好使用 Support Annotations 10 | 11 | 另一个非常有用的工具就是 Support Annotations Library,可以直接在 build.gradle 文件中添加`com.android.support:support-annotations:23.4.0`依赖,然后可以在代码中添加元注释来辅助很快地 Bug 追踪与代码规则定义。最常见的使用场景就是用于标记 Nullable 与 Non-Nullable 变量,将整型标记为资源标识符以及设置某个函数的运行线程。不过因为这些注释属于 Metadata Annotations,即使某些定义的规则被破坏了项目还是会正常编译。只不过 Android Studio 与 Lint 会自动高亮地展示这些警告,并且在 CI 工具中也可以使用这些警告。 12 | 13 | # 快速的 Code Review 14 | 15 | Code Review 是项目开发中的常见操作,我们需要切换到某个 Feature 分支,然后重新编译运行项目,常见的工作流如下 16 | 17 | - 暂存本分支的更改 18 | - 检出待 Review 的分支 19 | - 在 IDE 中重载 gradle 配置文件 20 | - 在 IDE 中浏览代码 21 | - 编译、运行与测试应用 22 | - 切换分支并且重复以上步骤 23 | 24 | 上述流程很规范很正常,不过当你的项目拥有 1000 个以上的类与不同的配置时,估计你要好几分钟来等待项目编译完毕。我们的解决方案是为特征分支使用单独的 IDE 实例与仓库文件夹,这样会避免你来回地进行分支切换。如果你想用这套方案,那么建议你到电脑至少要 16GB 以上的 RAM,你会觉得物有所值的。 25 | 26 | # 修改的热加载 27 | 28 | 笔者在 React 开发中使用 HOT Reload 是个很不错的体验,而在 Android 开发中,即使是一个很小的 Android 项目,也需要等待不少的时间来等待修改之后的代码重新构建与部署,更何况对于那些有成百上千个类与 XML 布局文件的项目。另一方面,你也需要在你的应用中跳转选择到合适的页面来查看你做的更改的实际效果。2015 年底,Android 社区逐步使用两个工具来加速代码更改的加载速度,首先是[JRebel](https://zeroturnaround.com/software/jrebel-for-android/),源于 Java 在服务端开发中的长期实践。另一个是 Google 团队随着 Android Studio 2.0 一起推出的工具:[Instant Run](https://developer.android.com/studio/run/index.html#instant-run)。两个工具的目标差不多,不过 JRebel 包含更多的特性,但是它也需要付费。作者编辑了一个简单的表单来描述二者的特性对比 29 | 30 | - https://developer.android.com/studio/run/index.html#instant-run 31 | - Reto Meier: “Instant Run: How Does it Work?!” 32 | - Oleg Selajev: “Looking at JRebel for Android and Instant Run …” 33 | 34 | 两个工具都正处在不断的开发与完善中,从我们的经验来看,目前还有很多的用户场景并没有被涉及到,因此我们也相信未来这两个工具的提升空间令人期待。 35 | 36 | # 性能分析与执行效率估测 37 | 38 | 另一个应用调试与性能分析中常需的用户特性是对于方法的输入输出与执行时间的日志记录,作者在这方面是使用了 Jake Wharton 开源的[Hugo](https://github.com/JakeWharton/hugo),这个工具在你不希望使用复杂的譬如 Systrace 工具的时候很有帮助。譬如我们会希望监测如下的目标方法 39 | 40 | ``` 41 | @DebugLog 42 | public String getName(String first, String last) {/* ... */} 43 | ``` 44 | 45 | 然后在控制台中我们可以看到如下的信息 46 | 47 | ``` 48 | V/Example: --> getName(first="Jake", last="Wharton") 49 | V/Example: <-- getName [16ms] = "Jake Wharton" 50 | ``` 51 | 52 | # 读取设备中的日志输出 53 | 54 | 我们在开发过程中经常会使用 Android Studio 内置的 Android Monitor 来读取日志信息,这种方式确实很简单易用,不过也有很大的缺陷 55 | 56 | - 我们必须使用额外的工具来对日志记录进行格式化 57 | - Android Studio 的调试工具是附着到应用的进程 ID 上,一旦我们重新部署应用或者杀掉了进程,之前打印出来的日志就会被覆盖掉 58 | 59 | 作者在这里推荐的是 Jake Wharton 开源的另一款工具[Pidcat](https://github.com/JakeWharton/pidcat),其优势在于 60 | 61 | - 更友好的配色与格式化 62 | - 根据包名来获取调试信息,即使重新部署应用也不会删除之前的日志记录 63 | 64 | # 网络调试与分析 65 | 66 | 应用开发中我们也常常需要通过查看 HTTPClient 库的日志记录来读取客户端与服务端的交互信息,不过这种方法往往会有如下的一些缺陷 67 | 68 | - 如果你在开发过程中全程保持网络的日志输出,会大大影响应用的运行性能,毕竟需要一些时间来输出日志 69 | - 如果你的应用使用的其他依赖库需要进行网络通信,譬如 Google Analytics 这样的分析工具,我们需要进行额外的配置来过滤掉这些扰乱的信息 70 | - 在生产环境下无法监测到网络通信信息 71 | 72 | 这边作者首先建议的方法是使用类似于[Charles Proxy](https://www.charlesproxy.com/)这样的 HTTP 监测与代理工具,它可以将你的应用当成黑箱对待,并且提供了以下功能 73 | 74 | - HTTP/HTTPS 的流量监控与记录 75 | - 重定义某些特定情况下的服务端返回信息 76 | - 设置部分网络调用的断点 77 | - 为设备安装合适的 SSL 证书来阅读加密的流量 78 | 79 | 另一个推荐的工具就是[Facebook Stetho](http://facebook.github.io/stetho/),它允许开发者在生产环境下获取网络交互数据,不过我们在开发中肯定也是可以使用该工具,不过作者发现好像它还不能读取基于 SSL 的加密信息。 80 | 81 | # 多版本测试 82 | 83 | 在应用开发过程中,作者建议务必要在 Lollipop 以及更高级的 API(API 21+) 以上来进行应用测试,从而发现因为系统适配而导致的潜在问题。我们最常见的 Bug 就是因为触摸反馈与系统颜色问题导致的 Bug,譬如我们经常看到因为使用了老版本的 API 而导致应用崩溃。 84 | 85 | # UI 自动化测试 86 | 87 | 有些用户场景下我们需要在不同的设备中进行重复的 UI 点击或者输入操作,如果你需要在两三个设备上进行重复的测试,估摸着你会爆炸的。我们首先想到的方法是通过输入`adb`命令来模拟用户点击输入,譬如*adb shell input keyevent 4*等效于在测试设备上点击了 UP 按钮。这样的话你可以在 adb 中完成系统按键点击、键盘输入与屏幕点击。而在多设备的环境下,我们建议使用 Roman Nurik 开源的 [adb-ninja](https://github.com/romannurik/env/blob/master/bin/ninja-adb) 脚本来在多个设备间同步提交指令。 88 | 89 | # build.gradle 配置检测 90 | 91 | 即使是有经验的开发者也往往会使用些过时的配置方案,作者建议根据如下顺序来检查下你的 build.gradle 配置 92 | 93 | - 使用 jcenter 来替换 mavenCentral,jcenter 的响应速度比 mavenCentral 好。 94 | - 检测 _Android Plugin for Gradle_ 的版本号,使用最新版本的依赖有助于提高编译速度。 95 | - 不要使用范围来定义依赖的版本号,而使用 “23.4.0” 这样固定的版本号来避免不确定的依赖中的 API 的变化。 96 | 97 | ![](http://153.3.251.190:11900/best-practices-for-android-developer-productivity) 98 | -------------------------------------------------------------------------------- /移动应用/Android/系统功能/高性能 OkHttp.md: -------------------------------------------------------------------------------- 1 | # 高性能的 OkHttp 2 | 3 | [OkHttp](http://square.github.io/okhttp/) 是笔者在为为 [Khan Academy](https://www.khanacademy.org/)开发这个 [Android app](https://play.google.com/store/apps/details?id=org.khanacademy.android)时候的必用库之一。OkHttp 库本身已经提供了非常优秀的功能配置,但是下面是我们在实践中总结出来的一些能够有效增加资源利用率的步骤: 4 | 5 | # 使用文件系统级别的响应缓存 6 | 7 | 默认情况下,OkHttp 并没有将包含`Cache-Control`头部文件的响应进行缓存。因此你的客户端可能会浪费时间与带宽去重复请求相同的资源,而不是简单地读取初次请求时候的缓存副本。为了保证基于文件系统的响应缓存,应该配置一个`com.squareup.okhttp.Cache`实例并且把它传递给`OkHttpClient`的`setCache`方法。在初始化该`Cache`时,需要指定一个`File`对象来指代文件目录以及最大的缓存容量。响应会被缓存写入指定的文件夹,如果缓存大小已经超过了文件夹的指定大小,会根据 LRU 规则来进行筛选。下面是笔者的具体实践: 8 | 9 | ```java 10 | // Base directory recommended by http://stackoverflow.com/a/32752861/400717. 11 | // Guard against null, which is possible according to 12 | // https://groups.google.com/d/msg/android-developers/-694j87eXVU/YYs4b6kextwJ and 13 | // http://stackoverflow.com/q/4441849/400717. 14 | final @Nullable File baseDir = context.getCacheDir(); 15 | if (baseDir != null) { 16 | final File cacheDir = new File(baseDir, "HttpResponseCache"); 17 | okHttpClient.setCache(new Cache(cacheDir, HTTP_RESPONSE_DISK_CACHE_MAX_SIZE)); 18 | } 19 | ``` 20 | 21 | 其中,我们建议的`HTTP_RESPONSE_DISK_CACHE_MAX_SIZE` 值为`10 * 1024 * 1024`, 或者 10 MB。 22 | 23 | # 使用 Stetho 24 | 25 | [Stetho](http://facebook.github.io/stetho/) 是来自于 Facebook 的一个很有用的辅助库,能够允许开发者使用 [Chrome Developer Tools](https://developers.google.com/web/tools/setup/workspace/setup-devtools) 来监测 Android 应用。除了能够允许开发者监测 SQLite 数据库以及视图层级之外,Stetho 还能辅助监测所有的来自 OkHttp 的网络请求与响应。 26 | 27 | ![Image of Stetho](http://omgitsmgp.com/assets/images/posts/stetho-inspector-network.png) 28 | 29 | 这样的自检方法能够保证服务器返回恰当的带有缓存控制的响应,同时还能监测当缓存资源存在时是否确定没有请求被发出。如果要使用 Stetho,只需要简单地来添加一个`StethoInterceptor`实例到网络拦截器中: 30 | 31 | ```java 32 | okHttpClient.networkInterceptors().add(new StethoInterceptor()); 33 | ``` 34 | 35 | 接下来,在应用运行之后,打开 Chrome 并且跳转到`chrome://inspect`界面,既可以看到上述画面。 36 | 37 | # 与 Picasso 以及 Retrofit 混合使用 38 | 39 | 笔者是直接使用了 Picasso 进行网络图片的加载,以及使用 Retrofit 来简化请求构造以及响应解码。默认情况下,这两个库是使用了 OkHttpClient 进行底层的网络交互。以 Picasso 的 2.5.2 版本的`OkHttpDownloader`为例: 40 | 41 | ```java 42 | private static OkHttpClient defaultOkHttpClient() { 43 | OkHttpClient client = new OkHttpClient(); 44 | client.setConnectTimeout(Utils.DEFAULT_CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 45 | client.setReadTimeout(Utils.DEFAULT_READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 46 | client.setWriteTimeout(Utils.DEFAULT_WRITE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 47 | return client; 48 | } 49 | ``` 50 | 51 | Retrofit 有一个很类似的工厂方法。在应用中,图片往往是常用的大型资源之一,Picasso 本身维护了一个基于 LRU 规则的缓存系统,是直接在内存中的缓存。 52 | 53 | ```java 54 | final Picasso picasso = new Picasso.Builder(context) 55 | .downloader(new OkHttpDownloader(okHttpClient)) 56 | .build(); 57 | 58 | // The client should inject this instance whenever it is needed, but replace the singleton 59 | // instance just in case. 60 | Picasso.setSingletonInstance(picasso); 61 | ``` 62 | 63 | # 定制一个 User Agent 拦截器 64 | 65 | 对于由客户端提供的包含在 User-Agent 头文件中的详细的本机信息是往往用来日志记录与分析的重要来源。默认情况下,OkHttp 中自带的 User-Agent 属性会说明 OkHttp 的版本。如果需要定制自己的 User-Agent 信息,可以通过创建一个拦截器并且替换以下这些值: 66 | 67 | ```java 68 | public final class UserAgentInterceptor implements Interceptor { 69 | private static final String USER_AGENT_HEADER_NAME = "User-Agent"; 70 | private final String userAgentHeaderValue; 71 | 72 | public UserAgentInterceptor(String userAgentHeaderValue) { 73 | this.userAgentHeaderValue = 74 | Preconditions.checkNotNull(userAgentHeaderValue); 75 | } 76 | 77 | @Override 78 | public Response intercept(Chain chain) throws IOException { 79 | final Request originalRequest = chain.request(); 80 | final Request requestWithUserAgent = originalRequest 81 | .newBuilder() 82 | .removeHeader(USER_AGENT_HEADER_NAME) 83 | .addHeader(USER_AGENT_HEADER_NAME, userAgentHeaderValue) 84 | .build(); 85 | return chain.proceed(requestWithUserAgent); 86 | } 87 | } 88 | ``` 89 | 90 | 在构造传入拦截器的具体的 User-Agent 的值时,我们使用了如下信息: 91 | 92 | - Android 的版本信息 93 | - `Build.MODEL`, 或者用户可见的终端产品名 94 | - `Build.BRAND` 95 | - `Build.VERSION.SDK_INT` 96 | - `BuildConfig.APPLICATION_ID` 97 | - `BuildConfig.VERSION_NAME` 98 | - `BuildConfig.VERSION_CODE` 99 | 100 | 最后三个值是 Gradle 文件中包含的版本信息。如果你是使用的 WebView,则可以直接在 WebView 中进行构造: 101 | 102 | ```java 103 | WebSettings settings = webView.getSettings(); 104 | settings.setUserAgentString(userAgentHeaderValue); 105 | ``` 106 | 107 | # 明确合理的超时时间 108 | 109 | Picasso specifies: 110 | 111 | - A connect timeout of 15 seconds. 112 | - A read timeout of 20 seconds. 113 | - A write timeout of 20 seconds. 114 | 115 | Whereas Retrofit specifies: 116 | 117 | - A connect timeout of 15 seconds. 118 | - A read timeout of 20 seconds. 119 | - No write timeout. 120 | -------------------------------------------------------------------------------- /移动应用/Android/界面开发/RecycleView.md: -------------------------------------------------------------------------------- 1 | # 纵向单列表 2 | 3 | ## 下拉刷新 4 | 5 | ## 上拉刷新 6 | 7 | # Extension 8 | 9 | ## Separator 10 | 11 | ### [**Dividers**](https://github.com/Karumi/Dividers) 12 | 13 | [Demo screenshot](https://github.com/Karumi/Dividers/raw/master/art/example.gif) 14 | 15 | ## Pagination 16 | 17 | ### [Paginate](https://github.com/MarkoMilos/Paginate) 18 | 19 | [Demo Screen](https://github.com/MarkoMilos/Paginate/raw/master/art/demo.gif) 20 | 21 | # Introduction 22 | 23 |    RecyclerView 是谷歌 V7 包下新增的控件,用来替代 ListView 的使用,在 RecyclerView 标准化了 ViewHolder 类似于 ListView 中 convertView 用来做视图缓存.先来说说 RecyclerView 的有点就是,他可以通过设置 LayoutManager 来快速实现 listview、gridview、瀑布流的效果,而且还可以设置横向和纵向显示,添加动画效果也非常简单(自带了 ItemAnimation,可以设置加载和移除时的动画,方便做出各种动态浏览的效果),也是官方推荐使用的.以下是官方的说明: 24 | 25 | > RecyclerView is a more advanced and flexible version of ListView. This widget is a container for large sets of views that can be recycled and scrolled very efficiently. Use the RecyclerView widget when you have lists with elements that change dynamically. 简单说就是当你需要动态展示一组数据的时候就会需要用到它。 26 | 27 | # List 28 | 29 | 首先要用这个控件,你需要在 gradle 文件中添加包的引用(配合官方 CardView 使用) 30 | 31 | ```gradle 32 | compile 'com.android.support:cardview-v7:21.0.3' 33 | compile 'com.android.support:recyclerview-v7:21.0.3' 34 | ``` 35 | 36 | 然后是在 XML 文件用使用它 37 | 38 | ```xml 39 | 46 | ``` 47 | 48 | 接着在 Activity 中设置它 49 | 50 | ```java 51 | public class MainActivity extends ActionBarActivity { 52 | @InjectView(R.id.recycler_view) 53 | RecyclerView mRecyclerView; 54 | 55 | @Override 56 | protected void onCreate(Bundle savedInstanceState) { 57 | super.onCreate(savedInstanceState); 58 | setContentView(R.layout.activity_main); 59 | ButterKnife.inject(this); 60 | 61 | mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); //这里用线性显示 类似于listview 62 | 63 | // mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));//这里用线性宫格显示 类似于grid view 64 | // mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL));//这里用线性宫格显示 类似于瀑布流 65 | mRecyclerView.setAdapter(new NormalRecyclerViewAdapter(this)); 66 | } 67 | 68 | @Override 69 | public boolean onCreateOptionsMenu(Menu menu) { 70 | getMenuInflater().inflate(R.menu.menu_main, menu); 71 | return true; 72 | } 73 | 74 | @Override 75 | public boolean onOptionsItemSelected(MenuItem item) { 76 | int id = item.getItemId(); 77 | 78 | if (id == R.id.action_settings) { 79 | return true; 80 | } 81 | 82 | return super.onOptionsItemSelected(item); 83 | } 84 | } 85 | ``` 86 | 87 | 然后是适配器代码 88 | 89 | ``` 90 | public class NormalRecyclerViewAdapter extends RecyclerView.Adapter { 91 | private final LayoutInflater mLayoutInflater; 92 | private final Context mContext; 93 | private String[] mTitles; 94 | 95 | public NormalRecyclerViewAdapter(Context context) { 96 | mTitles = context.getResources().getStringArray(R.array.titles); 97 | mContext = context; 98 | mLayoutInflater = LayoutInflater.from(context); 99 | } 100 | 101 | @Override 102 | public NormalTextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 103 | return new NormalTextViewHolder(mLayoutInflater.inflate(R.layout.item_text, parent, false)); 104 | } 105 | 106 | @Override 107 | public void onBindViewHolder(NormalTextViewHolder holder, int position) { 108 | holder.mTextView.setText(mTitles[position]); 109 | } 110 | 111 | @Override 112 | public int getItemCount() { 113 | return mTitles == null ? 0 : mTitles.length; 114 | } 115 | 116 | public static class NormalTextViewHolder extends RecyclerView.ViewHolder { 117 | @InjectView(R.id.text_view) 118 | TextView mTextView; 119 | 120 | NormalTextViewHolder(View view) { 121 | super(view); 122 | ButterKnife.inject(this, view); 123 | view.setOnClickListener(new View.OnClickListener() { 124 | @Override 125 | public void onClick(View v) { 126 | Log.d("NormalTextViewHolder", "onClick--> position = " + getPosition()); 127 | } 128 | }); 129 | } 130 | } 131 | } 132 | ``` 133 | 134 | ![enter description here][1] 135 | 136 | # Grid 137 | 138 | ## Aspect Ratio 139 | 140 | ### [greedo-layout-for-android](https://github.com/500px/greedo-layout-for-android):支持百分比分割 141 | 142 | ![](https://github.com/500px/greedo-layout-for-android/raw/master/screenshot.png) 143 | -------------------------------------------------------------------------------- /移动应用/React Native/架构原理/原理与机制.md: -------------------------------------------------------------------------------- 1 | ## Application Exploration(应用探究) 2 | 3 | ### Architecture(应用架构) 4 | 5 | 当使用 react-native 命令创建新的项目时,调用的即https://github.com/facebook/react-native/blob/master/react-native-cli/index.js这个脚本。当使用```react-native init HelloWorld```创建一个新的应用目录时,它会创建一个新的 HelloWorld 的文件夹,包含如下列表: 6 | 7 | > HelloWorld.xcodeproj/ 8 | > 9 | > Podfile 10 | > 11 | > iOS/ 12 | > 13 | > Android/ 14 | > 15 | > index.ios.js 16 | > 17 | > index.android.js 18 | > 19 | > node_modules/ 20 | > 21 | > package.json 22 | 23 | React Native 最大的卖点在于(1)可以使用 JavaScript 编写 iOS 或者 Android 原生程序。(2)应用可以运行在原生环境下并且提供流畅的 UI 与用户体验。众所周知,iOS 或者 Android 并不能直接运行 JavaScript 代码,而是依靠类似于 UIWebView 这样的原生组件去运行 JavaScript 代码,也就是传统的混合式应用。整个应用运行开始还是自原生开始,不过类似于 Objective-C/Java 这样的原生代码只是负责启动一个 WebView 容器,即没有浏览器界面的浏览器引擎。 24 | 25 | 而对于 React Native 而言,并不需要一个 WebView 容器去执行 Web 方面的代码,而是将所有的 JavaScript 代码运行在一个内嵌的 JavaScriptCore 容器实例中,并最终渲染为高级别的平台相关的组件。这里以 iOS 为例,打开 HelloWorld/AppDelegate.m 文件,可以看到如下的代码: 26 | 27 | ```objective-c 28 | ..................... 29 | RCTRootView *rootView = [[RCTRootView alloc] 30 | initWithBundleURL:jsCodeLocation 31 | moduleName:@"HelloWorld" 32 | launchOptions:launchOptions]; 33 | ..................... 34 | ``` 35 | 36 | AppDelegate.m 文件本身是 iOS 程序的入口,相信每一个有 iOS 开发经验的同学都不会陌生,这也是本地的 Objective-C 代码与 React Native 的 JavaScript 代码胶合的地方。而这种胶合的关键就是 RCTRootView 这个组件,可以从 React 声明的组件中加载到 Native 的组件。RCTRootView 组件是一个由 React Native 提供的原生的 Objective-C 类,可以读取 React 的 JavaScript 代码并且执行,除此之外,也允许我们从 JavaScript 代码中调用 iOS UI 的组件。 37 | 38 | 到这里我们可以看出,React Native 并没有将 JavaScript 代码编译转化为原生的 Objective-C 或者 Swift 代码,但是这些在 React 中创建的组件渲染的方式也非常类似于传统的 Objective-C 或者 Swift 创建的基于 UIKit 的组件,并不是类似于 WebView 中网页渲染的结果。 39 | 40 | 这种架构也就很好地解释了为什么可以动态加载我们的应用,当我们仅仅改变了 JS 代码而没有原生的代码改变的时候,不需要去重新编译。RCTRootView 组件会监听`Command+R`组合键然后重新执行 JavaScript 代码。 41 | 42 | ### Virtual Dom 的扩展 43 | 44 | Virtual Dom 是 React 的核心机制之一,对于 Virtual Dom 的详细说明可以参考笔者 React 系列文章。在 React 组件被用于原生渲染之前,Clipboard 已经将 React 用于渲染到 HTML 的 Canvas 中,可以查看[render React to the HTML element](https://github.com/Flipboard/react-canvas)这篇文章。对于 React Web 而言,就是将 React 组件渲染为 DOM 节点,而对于 React Natively 而言,就是利用原生的接口把 React 组件渲染为原生的接口,其大概示意图可以如下: 45 | 46 | ![React Native behaves much like React, but can render to many different targets.](https://www.safaribooksonline.com/library/view/learning-react-native/9781491929049/assets/render-targets.png) 47 | 48 | 虽然 React 最初是以 Web 的形式呈现,但是 React 声明的组件可以通过*bridge*,即不同的桥接器转化器会将同样声明的组件转化为不同的具体的实现。React 在组件的 render 函数中返回具体的平台中应该如何去渲染这些组件。对于 React Native 而言,``这个组件会被转化为 iOS 中特定的`UIView`组件。 49 | 50 | ### 载入 JavaScript 代码 51 | 52 | React Native 提供了非常方便的动态调试机制,具体的表现而言即是允许以一种类似于中间件服务器的方式动态的加载 JS 代码,即 53 | 54 | ```objective-c 55 | jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle"]; 56 | ``` 57 | 58 | 另一种发布环境下,可以将 JavaScript 代码打包编译,即`npm build`: 59 | 60 | ```objective-c 61 | jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 62 | ``` 63 | 64 | 如果在 Xcode 中直接运行程序会自动调用`npm start`命令来启动一个动态编译的服务器,如果没有自动启动可以手动的使用`npm start`命令,就如定义在 package.json 文件中的,它会启动 node_modules/react-native/packager/packager.sh 这个脚本。 65 | 66 | ### React Native 中的现代 JavaScript 代码 67 | 68 | 从上文中可以看出,React Native 中使用的是所谓的 JSX 以及大量的 ES6 的语法,在打包器打包之前需要将 JavaScript 代码进行一些转换。这是因为 iOS 与 Android 中的 JavaScript 解释器目前主要还是支持到了 ES5 版本,并不能完全识别 React Native 中提供的语法或者关键字。当然,并不是说我们不能使用 ES5 的语法去编写 React Native 程序,只是最新的一些语法细则规范可以辅助我们快速构建高可维护的应用程序。 69 | 70 | 譬如我们以 JSX 的语法编写了如下渲染函数: 71 | 72 | ```js 73 | render: function() { 74 | return ( 75 | 76 | 80 | 81 | Hello, {this.state.name}! 82 | 83 | To get started, edit index.ios.js 84 | 85 | 86 | Press Cmd+R to reload,{'\n'} 87 | Cmd+Control+Z for dev menu 88 | 89 | 90 | ); 91 | } 92 | ``` 93 | 94 | 在 JS 代码载入之前,React 打包器需要首先将 JSX 语法转化为 ES5 的表达式: 95 | 96 | ```js 97 | render: function() { 98 | return ( 99 | React.createElement(View, {style: styles.container}, 100 | React.createElement(TextInput, { 101 | style: styles.nameInput, 102 | onChange: this.onNameChanged, 103 | placeholder: "Who should be greeted?"}), 104 | React.createElement(Text, {style: styles.welcome}, 105 | "Hello, ", this.state.name, "!"), 106 | React.createElement(Text, {style: styles.instructions}, 107 | "To get started, edit index.ios.js" 108 | ), 109 | React.createElement(Text, {style: styles.instructions}, 110 | "Press Cmd+R to reload,", '\n', 111 | "Cmd+Control+Z for dev menu" 112 | ) 113 | ) 114 | ); 115 | } 116 | ``` 117 | 118 | 另一些比较常用的语法转换,一个是模块导入时候的结构器,即我们常常见到模块导入: 119 | 120 | ```js 121 | var React = require("react-native"); 122 | var { AppRegistry, StyleSheet, Text, TextInput, View } = React; 123 | ``` 124 | 125 | 上文中的用法即是所谓的解构赋值,一个简单的例子如下: 126 | 127 | ```js 128 | var fruits = { banana: "A banana", orange: "An orange", apple: "An apple" }; 129 | var { banana, orange, apple } = fruits; 130 | ``` 131 | 132 | 那么我们在某个组件中进行导出的时候,就可以用如下语法: 133 | 134 | ```js 135 | module.exports.displayName = "Name"; 136 | module.exports.Component = Component; 137 | ``` 138 | 139 | 而导入时,即是: 140 | 141 | ```js 142 | var { Component } = require("component.js"); 143 | ``` 144 | 145 | 另一个常用的 ES6 的语法即是所谓的 Arrow Function,这有点类似于 Lambda 表达式: 146 | 147 | ```js 148 | AppRegistry.registerComponent("HelloWorld", () => HelloWorld); 149 | ``` 150 | 151 | 会被转化为: 152 | 153 | ```js 154 | AppRegistry.registerComponent("HelloWorld", function() { 155 | return HelloWorld; 156 | }); 157 | ``` 158 | -------------------------------------------------------------------------------- /移动应用/Android/系统功能/网络请求.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | 笔者没有把 OkHttp 归纳到 Android 体系内,还是放置到了 Java 体系内。Retrofit 本身就是对于 OkHttp 的封装。 4 | 5 | ![](https://packetzoom.com/blog/images/httplibs.png) 6 | 7 | ## Comparison( 比较 ) 8 | 9 | - [comparison-of-android-networking-libraries-okhttp-retrofit-volley](http://stackoverflow.com/questions/16902716/comparison-of-android-networking-libraries-okhttp-retrofit-volley#) 10 | 11 | 目前基本上每个应用都会使用 HTTP/HTTPS 协议来作为主要的传输协议来传输数据。即使你没有直接使用 HTTP 协议,也会有成堆的 SDK 会包含这些协议,譬如分析、Crash 反馈等等。当然,目前也有很多优秀的 HTTP 的协议库,可以很方便的帮助开发者构建应用,本篇博文中会尽可能地涵盖这些要点。Android 的开发者在选择一个合适的 HTTP 库时需要考虑很多的要点,譬如在使用 Apache Client 或者 HttpURLConnection 时可能会考虑: 12 | 13 | - 能够取消现有的网络请求 14 | - 能够并发请求 15 | - 连接池能够复用存在的 Socket 连接 16 | - 本地对于响应的缓存 17 | - 简单的异步接口来避免主线程阻塞 18 | - 对于 REST API 的封装 19 | - 重连策略 20 | - 能够有效地载入与传输图片 21 | - 支持对于 JSON 的序列化 22 | - 支持 SPDY、HTTP/2 最早的时候 Android 只有两个主要的 HTTP 客户端: [HttpURLConnection](http://developer.android.com/reference/java/net/HttpURLConnection.html), [Apache HTTP Client](https://developer.android.com/sdk/api_diff/22/changes/android.net.http.AndroidHttpClient.html)。根据 Google 官方博客的内容,HttpURLConnection 在早期的 Android 版本中可能存在一些 Bug: 23 | 24 | > 在 Froyo 版本之前,HttpURLConnection 包含了一些很恶心的错误。特别是对于关闭可读的 InputStream 时候可能会污染整个连接池。 25 | 26 | 同样,Google 官方并不想转到 Apache HTTP Client 中: 27 | 28 | > Apache HTTP Client 中复杂的 API 设计让人们根本不想用它,Android 团队并不能够有效地工作。 29 | 30 | 而对于大部分普通开发者而言,它们觉得应该根据不同的版本使用不同的客户端。对于 Gingerbread(2.3) 以及之后的版本,HttpURLConnection 会是最佳的选择,它的 API 更简单并且体积更小。透明压缩与数据缓存可以减少网络压力,提升速度并且能够节约电量。当我们审视 Google Volley 的源代码的时候,可以看得出来它也是根据不同的 Android 版本选择了不同的底层的网络请求库: 31 | 32 | ```java 33 | if (stack == null) { 34 | if (Build.VERSION.SDK_INT >= 9) { 35 | stack = new HurlStack(); 36 | } else { 37 | // Prior to Gingerbread, HttpUrlConnection was unreliable. 38 | // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html 39 | stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); 40 | } 41 | } 42 | ``` 43 | 44 | 不过这样会很让开发者头疼,2013 年,Square 为了解决这种分裂的问题发布了 OkHttp。OkHttp 是直接架构与 Java Socket 本身而没有依赖于其他第三方库,因此开发者可以直接用在 JVM 中,而不仅仅是 Android。为了简化代码迁移速度,OkHttp 也实现了类似于 HttpUrlConnection 与 Apache Client 的接口。 45 | 46 | ![](https://packetzoom.com/blog/images/okhttp.png) 47 | 48 | OkHttp 获得了巨大的社区的支持,以至于 Google 最终是将它作为了 Android 4.4 默认的 Engine,并且会在 5.1 之后弃用 Apache Client。目前 OkHttp V2.5.0 支持如下特性: 49 | 50 | - HTTP/2 以及 SPDY 的支持多路复用 51 | - 连接池会降低并发连接数 52 | - 透明 GZIP 加密减少下载体积 53 | - 响应缓存避免大量重复请求 54 | - 同时支持同步的阻塞式调用与异步回调式调用 55 | 56 | 笔者关于 OkHttp 最喜欢的一点是它能够将异步请求较好的展示: 57 | 58 | ```java 59 | private final OkHttpClient client = new OkHttpClient(); 60 | 61 | public void run() throws Exception { 62 | Request request = new Request.Builder() 63 | .url("http://publicobject.com/helloworld.txt") 64 | .build(); 65 | 66 | client.newCall(request).enqueue(new Callback() { 67 | @Override 68 | public void onFailure(Request request, Throwable throwable) { 69 | throwable.printStackTrace(); 70 | } 71 | 72 | @Override 73 | public void onResponse(Response response) throws IOException { 74 | if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); 75 | System.out.println(response.body().string()); 76 | } 77 | }); 78 | } 79 | ``` 80 | 81 | 这个用起来非常方便,因为往往大的数据请求都不能放置在 UI 主线程中进行。事实上,从 Android 3.0(Honeycomb 11) 开始,所有的网络操作都必须强制在单独的线程中进行。在当时如果要把 HttpUrlConnection 和 AsyncTask 结合起来使用,还是比较复杂的。而 2013 年的 Google IO 大会上,Google 提出了 Volley,一个提供了如下便利的 HTTP 库: 82 | 83 | - Automatic scheduling of network requests. 84 | - Multiple concurrent network connections. 85 | - Transparent disk and memory response caching with standard HTTP cache coherence. 86 | - Support for request prioritization. 87 | - Cancellation request API. You can cancel a single request, or you can set blocks or scopes of requests to cancel. 88 | - Ease of customization, for example, for retry and backoff. 89 | - Strong ordering that makes it easy to correctly populate your UI with data fetched asynchronously from the network. 90 | - Debugging and tracing tools. 91 | 92 | ![](https://packetzoom.com/blog/images/volley_arch.png) 93 | 94 | Volley 主要架构在 HttpUrlConnection 之上,如果希望能够抓取图片或者 JSON 数据,Volley 有自定义的抽象类型 ImageRequest 与 JsonObjectRequest,可以自动转化为 HTTP 请求。同时,Volley 也有一个硬编码的网络连接池大小: 95 | 96 | ```java 97 | private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; 98 | ``` 99 | 100 | 不过 OkHttp 可以自定义连接池的大小: 101 | 102 | ```java 103 | private int maxRequests = 64; 104 | private int maxRequestsPerHost = 5; 105 | 106 | executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, 107 | new LinkedBlockingQueue(), Util.threadFactory("OkHttp Dispatcher", false)); 108 | ``` 109 | 110 | 在某些情况下,OkHttp 可以通过使用多线程来有更好的性能体现。不过如果现有的程序中已经用 Volley 做了顶层封装,那么也可以使用[HttpStack implementation](https://gist.github.com/bryanstern/4e8f1cb5a8e14c202750)这个来使用 OkHttp 的请求与响应接口来替换 HttpUrlConnection。 111 | 112 | 到这里已经可以发现,OkHttp 本质上是自定义了一套底层的网络请求架构。目前 HTTP 客户端已经逐步转化为了支持大量图片,特别是那种无限滚动与图片传输的应用。同时,REST API 已经成为了业界标准,基本上每位开发者都需要处理大量标准化的任务,类似于 JSON 序列化与将 REST 请求映射到 Java 的接口上。Square 也在不久之后针对这两个问题提出了自己的解决方案: 113 | 114 | - [Retrofit](http://square.github.io/retrofit/) - **一个类型安全的 HTTP 客户端支持 REST 接口** 115 | - [Picasso](http://square.github.io/picasso/) - **针对 Android 的图片下载与缓存库** 116 | 117 | Retrofit 提供了一个面向 Java 代码与 REST 接口之间的桥接,可以迅速将 HTTP API 转化到 Java 接口中并且自动生成带有完整文档的实现: 118 | 119 | ```java 120 | public interface GitHubService { 121 | @GET("/users/{user}/repos") 122 | Call> listRepos(@Path("user") String user); 123 | } 124 | 125 | 126 | Retrofit retrofit = new Retrofit.Builder() 127 | .baseUrl("https://api.github.com") 128 | .build(); 129 | 130 | GitHubService service = retrofit.create(GitHubService.class); 131 | ``` 132 | 133 | 除此之外,Retrofit 也支持面向 JSON、XML 以及 Protocol Buffers 的数据转化。在[另一篇博客](http://instructure.github.io/blog/2013/12/09/volley-vs-retrofit/)中将 AsyncTask 与 Volley 以及 Retrofit 做了一个比较,其性能对比如下: 134 | 135 | ![](http://i.imgur.com/tIdZkl3.png) 136 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Frontend-Series 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 34 | 38 | 40 | 45 | 46 |
47 | 64 | 97 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 143 | 144 | 145 | 146 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /移动应用/iOS/界面开发/事件与手势.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: iOS-Gesture&Event 3 | tags: Cocoa,ViewController,Gesture 4 | --- 5 | 6 | # Introduction 7 | 8 | 触摸(Cocoa Touch)就是用户的手指放在屏幕上,系统和硬件联合工作,会知道何时触摸屏幕以及触摸屏幕的未知。触摸事件是在 UIView 上进行的,而不是一个 UIResponder 对象。触摸本身是一个 UITouch 对象,每一个手指即会对应一个 UITouch 对象,多个 UITouch 对象包含在一个 UIEvent 中。系统会将 UIEvent 发送到应用程序中,最后应用程序将 UIEvent 传递给当前一个 UIView。 9 | 10 | > 参考资料 11 | > 12 | > - [iOS 事件机制][1] 13 | 14 | ## iOS 事件分类 15 | 16 | 对于 IOS 设备用户来说,他们操作设备的方式主要有三种:触摸屏幕、晃动设备、通过遥控设施控制设备。对应的事件类型有以下三种: 17 | 18 | 1、触屏事件(Touch Event):单点、多点触控以及各种手势操作 19 | 20 | 2、运动事件(Motion Event):重力、加速度传感器等 21 | 22 | 3、远端控制事件(Remote-Control Event):远程遥控 iOS 设备多媒体播放等 23 | 24 | ![iOS事件图例](http://ryantang.me/images/2013/12/ios_event_dispatch/1.png) 25 | 26 | # 事件封装与响应 27 | 28 | ## UIControl 控件事件分类 29 | 30 | > 参考资料 31 | > 32 | > - [iOS UIControl 几个事件的说明][6] 33 | 34 | 在控件事件中,简单解释下下面几个事件。 35 | 36 | 说明:由于是在“iOS 模拟器”中测试的,所以不能用手指,只能用鼠标。 37 | 38 | 1)UIControlEventTouchDown 39 | 40 | 指鼠标左键按下(注:只是“按下”)的动作 41 | 42 | 2)UIControlEventTouchDownRepeat 43 | 44 | 指鼠标左键连续多次重复按下(注:只是“按下”)的动作,比如,鼠标连续双击、三击、……、多次连击。 45 | 46 | 说明:多次重复按下时,事件序列是这样的: 47 | 48 | UIControlEventTouchDown -> 49 | 50 | (UIControlEventTouchUpInside) -> 51 | 52 | UIControlEventTouchDown -> 53 | 54 | UIControlEventTouchDownRepeat -> 55 | 56 | (UIControlEventTouchUpInside) -> 57 | 58 | UIControlEventTouchDown -> 59 | 60 | UIControlEventTouchDownRepeat -> 61 | 62 | (UIControlEventTouchUpInside) -> 63 | 64 | ... 65 | 66 | 除了第一次按下外,后面每次摁下都是一个 UIControlEventTouchDown 事件,然后紧跟一个 UIControlEventTouchDownRepeat 事件。 67 | 68 | 3)UIControlEventTouchDragInside 69 | 70 | 指按下鼠标,然后在控件边界范围内拖动。 71 | 72 | 4)UIControlEventTouchDragOutside 73 | 74 | 与 UIControlEventTouchDragInside 不同的是,拖动时,鼠标位于控件边界范围之外。但首先得有个 UIControlEventTouchDown 事件,然后接一个 UIControlEventTouchDragInside 事件,再接一个 UIControlEventTouchDragExit 事件,这时,鼠标已经位于控件外了,继续拖动就是 UIControlEventTouchDragOutside 事件了。 75 | 76 | 具体操作是:在控件里面按下鼠标,然后拖动到控件之外。 77 | 78 | 5)UIControlEventTouchDragEnter 79 | 80 | 指拖动动作中,从控件边界外到内时产生的事件。 81 | 82 | 6)UIControlEventTouchDragExit 83 | 84 | 指拖动动作中,从控件边界内到外时产生的事件。 85 | 86 | 7)UIControlEventTouchUpInside 87 | 88 | 指鼠标在控件范围内抬起,前提先得按下,即 UIControlEventTouchDown 或 UIControlEventTouchDownRepeat 事件。 89 | 90 | 8)UIControlEventTouchUpOutside 91 | 92 | 指鼠标在控件边界范围外抬起,前提先得按下,然后拖动到控件外,即 UIControlEventTouchDown -> UIControlEventTouchDragInside(n 个) -> UIControlEventTouchDragExit -> UIControlEventTouchDragOutside(n 个) 时间序列,再然后就是抬起鼠标,产生 UIControlEventTouchUpOutside 事件。 93 | 94 | ### 自动关联 95 | 96 | 可以通过 Xib 里面的关联口 IBAction 实现。 97 | 98 | ### 手动关联 99 | 100 | ``` 101 | [button addTarget:self action:@selector(navigatePics:) forControlEvents:UIControlEventTouchUpInside]; 102 | ``` 103 | 104 | ## 手势关联 105 | 106 | 每一个特定的手势必须关联到 view 对象中才会有作用,一个 view 对象可以关联多个不同的特定手势,但是每一个特定的手势只能与一个 view 相关联。当用户触摸了 view,这个 GestureRecognizer 就会接受到消息,它可以响应特定的触摸事件。 107 | 108 | ![enter description here](http://images.cnitblog.com/blog/133128/201304/30154250-3cc837e5df9b44f59d504c57eee6b8aa.png) 109 | 110 | ![enter description here](http://images.cnitblog.com/blog/133128/201304/30154335-9b65e3e1d4024132beba1a3c8360c4b5.png) 111 | 112 | 通俗来说,关联手势与响应分为以下三步: 113 | 114 | - 创建 GestureRecognizer 实例 115 | - addGestureRecognizer 116 | - 实现处理手势的方法 117 | 118 | ### UIPanGestureRecognizer 119 | 120 | ``` 121 | _panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlerPanGesture:)]; 122 | _panGestureRecognizer.delegate = self; 123 | _panGestureRecognizer.maximumNumberOfTouches = 2; 124 | _panGestureRecognizer.minimumNumberOfTouches = 2; 125 | [self.view addGestureRecognizer:_panGestureRecognizer]; 126 | 127 | - (void)handlerPanGesture:(UIPanGestureRecognizer *)recognizer 128 | { 129 | if ((recognizer.state == UIGestureRecognizerStateBegan) || 130 | (recognizer.state == UIGestureRecognizerStateChanged)) 131 | { 132 | CGPoint offset = [recognizer translationInView:self.view]; 133 | CGRect frame = self.rightViewController.view.frame; 134 | frame.origin.x += offset.x; 135 | if (frame.origin.x >= 0 && frame.origin.x <= kScreenWidth) 136 | { 137 | self.rightViewController.view.frame = frame; 138 | } 139 | 140 | [recognizer setTranslation:CGPointZero inView:self.view]; 141 | } 142 | else if (recognizer.state == UIGestureRecognizerStateEnded) 143 | { 144 | BOOL isVisible = self.rightViewController.view.frame.origin.x < kScreenWidth / 2; 145 | [self showRightView:isVisible]; 146 | } 147 | } 148 | ``` 149 | 150 | # 事件分发(Event Delivery) 151 | 152 | 第一响应者(First responder)指的是当前接受触摸的响应者对象(通常是一个 UIView 对象),即表示当前该对象正在与用户交互,它是响应者链的开端。整个响应者链和事件分发的使命都是找出第一响应者。 153 | 154 | UIWindow 对象以消息的形式将事件发送给第一响应者,使其有机会首先处理事件。如果第一响应者没有进行处理,系统就将事件(通过消息)传递给响应者链中的下一个响应者,看看它是否可以进行处理。 155 | 156 | iOS 系统检测到手指触摸(Touch)操作时会将其打包成一个 UIEvent 对象,并放入当前活动 Application 的事件队列,单例的 UIApplication 会从事件队列中取出触摸事件并传递给单例的 UIWindow 来处理,UIWindow 对象首先会使用 hitTest:withEvent:方法寻找此次 Touch 操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为 hit-test view。 157 | 158 | # 复杂手势控制(Complex Gesture Event) 159 | 160 | 在 iOS 中一个事件用一个 UIEvent 对象表示,UITouch 用来表示一次对屏幕的操作动作,由多个 UITouch 对象构成了一个 UIEvent 对象。另外,UIResponder 是所有响应者的父类,UIView、UIViewController、UIWindow、UIApplication 都直接或间接的集成了 UIResponder。 161 | 162 | ## Gesture Recognizers(手势识别器) 163 | 164 | Gesture Recognizers 是一类手势识别器对象,它可以附属在你指定的 View 上,并且为其设定指定的手势操作,例如是点击、滑动或者是拖拽。当触控事件 发生时,设置了 Gesture Recognizers 的 View 会先通过识别器去拦截触控事件,如果该触控事件是事先为 View 设定的触控监听事件,那么 Gesture Recognizers 将会发送动作消息给目标处理对象,目标处理对象则对这次触控事件进行处理,先看看如下流程图。 165 | 166 | [Action 传递模型][3] 167 | 168 | 在 iOS 中,View 就是我们在屏幕上看到的各种 UI 控件,当一个触控事件发生时,Gesture Recognizers 会先获取到指定的事件,然后发送动作消息(action message)给目标对象(target),目标对象就是 ViewController,在 ViewController 中通过事件方法完成对该事件的处理。Gesture Recognizers 能设置诸如单击、滑动、拖拽等事件,通过 Action-Target 这种设计模式,好处是能动态为 View 添加各种事件监听,而不用去实现一个 View 的子类去完成这些功能。 169 | 170 | 以上过程就是我们在开发中在方法中常见的设置 action 和设置 target,例如为 UIButton 设置监听事件等。 171 | 172 | ### 常用手势识别类 173 | 174 | 在 UIKit 框架中,系统为我们事先定义好了一些常用的手势识别器,包括点击、双指缩放、拖拽、滑动、旋转以及长按。通过这些手势识别器我们可以构造丰富的操作方式。 175 | 176 | [手势识别类列表][4] 177 | 178 | 在上表中可以看到,UIKit 框架中已经提供了诸如 UITapGestureRecognizer 在内的六种手势识别器,如果你需要实现自定义的手势识别器,也可以通过继承 UIGestureRecognizer 类并重写其中的方法来完成,这里我们就不详细讨论了。 179 | 180 | 每一个 Gesture Recognizer 关联一个 View,但是一个 View 可以关联多个 Gesture Recognizer,因为一个 View 可能还能响应多种触控操作方式。当一个触控事件发生时,Gesture Recognizer 接收一个动作消息要先于 View 本身,结果就是 Gesture Recognizer 作为 View 处理触控事件的代表,或者叫代理。当 Gesture Recognizer 接收到指定的事件时,它就会发送一条动作消息(action message)给 ViewController 并处理。 181 | 182 | ### 连续和不连续动作 183 | 184 | [连续与不连续动作][5] 185 | 186 | 触控动作同时分为连续动作(continuous)和不连续动作(discrete),连续动作例如滑动和拖拽,它会持续一小段时间,而不连续动作例如单击,它瞬间就会完成,在这两类事件的处理上又稍有不同。对于不连续动作,Gesture Recognizer 只会给 ViewContoller 发送一个单一的动作消息(action message),而对于连续动作,Gesture Recognizer 会发送多条动作消息给 ViewController,直到所有的事件都结束。 187 | 188 | 为一个 View 添加 GestureRecognizer 有两种方式,一种是通过 InterfaceBuilder 实现,另一种就是通过代码实现,我们看看通过代码来如何实现。 189 | 190 | ## Drag(拖拽) 191 | 192 | ## Zoom(缩放) 193 | 194 | [1]: http://ryantang.me/blog/2013/12/07/ios-event-dispatch-1/ 195 | [2]: http://ryantang.me/images/2013/12/ios_event_dispatch/1.png 196 | [3]: http://ryantang.me/images/2013/12/ios_event_dispatch/2.png 197 | [4]: http://ryantang.me/images/2013/12/ios_event_dispatch/3.png 198 | [5]: http://ryantang.me/images/2013/12/ios_event_dispatch/4.png 199 | [6]: http://blog.csdn.net/g5dsk/article/details/6613943 200 | [7]: http://images.cnitblog.com/blog/133128/201304/30154250-3cc837e5df9b44f59d504c57eee6b8aa.png 201 | [8]: http://images.cnitblog.com/blog/133128/201304/30154335-9b65e3e1d4024132beba1a3c8360c4b5.png 202 | -------------------------------------------------------------------------------- /移动应用/React Native/语法基础/组件基础.old.md: -------------------------------------------------------------------------------- 1 | # Component(组件) 2 | 3 | 一个最基本的组件构成如下: 4 | 5 | ```js 6 | "use strict"; 7 | 8 | var React = require("react-native"); 9 | var { AppRegistry, StyleSheet, Text, View } = React; 10 | 11 | var HelloWorld = React.createClass({ 12 | render: function () { 13 | return ( 14 | 15 | Welcome to React Native! 16 | 17 | To get started, edit index.ios.js 18 | 19 | 20 | Press Cmd+R to reload,{"\n"} 21 | Cmd+D or shake for dev menu 22 | 23 | 24 | ); 25 | }, 26 | }); 27 | 28 | var styles = StyleSheet.create({ 29 | container: { 30 | flex: 1, 31 | justifyContent: "center", 32 | alignItems: "center", 33 | backgroundColor: "#F5FCFF", 34 | }, 35 | welcome: { 36 | fontSize: 20, 37 | textAlign: "center", 38 | margin: 10, 39 | }, 40 | instructions: { 41 | textAlign: "center", 42 | color: "#333333", 43 | marginBottom: 5, 44 | }, 45 | }); 46 | 47 | AppRegistry.registerComponent("HelloWorld", () => HelloWorld); 48 | ``` 49 | 50 | ## Basic(组件基础) 51 | 52 | ### 原生组件与 HTML 元素的相同点 53 | 54 | 在 Web 领域的开发中,我们往往需要使用大量基本的 HTML 元素,包括但不限于 div、span、img 这样的,而在 React Native 中往往是使用 View、Image、Text 以及 ListView 等等替换,一个对照表如下所示: 55 | 56 | | HTML | React Native | 57 | | --------- | --------------------- | 58 | | div | View | 59 | | img | Image | 60 | | span, p | Text | 61 | | ul/ol, li | ListView, child items | 62 | 63 | # Built-in Components 64 | 65 | ## Indicator 66 | 67 | ### Text 68 | 69 | 在 React Native 中,只有组件能够接受文本作为子节点,换言之,如下的写法是错的: 70 | 71 | ```html 72 | Text doesn't go here! 73 | ``` 74 | 75 | 与之相对的,应该将文本包裹在组件中: 76 | 77 | ```html 78 | 79 | This is okay! 80 | 81 | ``` 82 | 83 | 在 HTML 中,如果需要展示一系列具有复杂格式的字符串,可能需要``、``等动态组合,譬如: 84 | 85 | ```html 86 |

The quick brown fox jumped over the lazy dog.

87 | ``` 88 | 89 | 而在于 React Native 中,只有组件可以用来包裹某个文本,而如果需要对其属性进行控制,则需要利用其 Style,而不是添加``、``这些标签。 90 | 91 | ```html 92 | 93 | The quick brown fox 94 | jumped over the lazy dog. 95 | 96 | ``` 97 | 98 | 不过上述这种直接把属性写在组件里的方式会将整个代码变得非常的冗长,可以以另一种方式进行处理,譬如将独特功能的组件封装为特殊的组件: 99 | 100 | ```js 101 | var styles = StyleSheet.create({ 102 | bold: { 103 | fontWeight: "bold", 104 | }, 105 | italic: { 106 | fontStyle: "italic", 107 | }, 108 | }); 109 | 110 | var Strong = React.createClass({ 111 | render: function () { 112 | return {this.props.children}; 113 | }, 114 | }); 115 | 116 | var Em = React.createClass({ 117 | render: function () { 118 | return {this.props.children}; 119 | }, 120 | }); 121 | ``` 122 | 123 | 在使用了这样的自带样式的组件之后,可以以一种非常类似于 HTML 的方式去使用这些组件,譬如: 124 | 125 | ```js 126 | 127 | The quick brown fox jumped over the lazy dog. 128 | 129 | ``` 130 | 131 | 同样地,RN 中也没有类似于 h1、h2 这样地元素,不过就像类似于封装 EM 一样,可以自己基于 Text 组件进行封装。 132 | 133 | ## Gallery 134 | 135 | ### Image 136 | 137 | If text is the most basic element in an application, images are a close contender, for both mobile and for web. When writing HTML and CSS for the web, we include images in a variety of ways: sometimes we use the tag, while at other times we apply images via CSS, such as when we use the background-image property. In React Native, we have a similar component, but it behaves a little differently. 138 | The basic usage of the component is straightforward; just set the source prop: 139 | 140 | ``` 141 | 142 | ``` 143 | 144 | How does that require call work? Where does this resource live? Here’s one part of React Native that you’ll have to adjust based on which platform you’re targeting. On iOS, this means that you’ll need to import it into the assets folder within your XCode project. By providing the appropriate @2x and @3x resolution files, you will enable XCode to serve the correct asset file for the correct platform. This is a nice change from web development: the relatively limited possible combinations of screen size and resolution on iOS means that it’s easier to create targeted assets. 145 | For React Native on other platforms, we can expect that the image! require syntax will point to a similar assets directory. 146 | It’s also worth mentioning that it is also possible to include web-based image sources instead of bundling your assets with your application. Facebook does this as one of the examples in the UIExplorer application: 147 | 148 | ``` 149 | 151 | ``` 152 | 153 | When utilizing network resources, you will need to specify dimensions manually. 154 | Downloading images via the network rather than including them as assets has some advantages. During development, for instance, it may be easier to use this approach while prototyping, rather than carefully importing all of your assets ahead of time. It also reduces the size of your bundled mobile application, so that users needn’t download all of your assets. However, it means that instead you’ll be relying on the user’s data plan whenever they access your application in the future. For most cases, you’ll want to avoid using the URI-based method. 155 | If you’re wondering about working with the user’s own images, we’ll discuss the Camera Roll during our exploration of platform-specific APIs. 156 | Because React Native emphasizes a component-based approach, images must be included as an component instead of being referenced via styles. For instance, last chapter, we wanted to use an image as a background for our weather application. Whereas in plain HTML and CSS you would likely use the background-image property to apply a background image, in React Native you instead use the as a container component, like so: 157 | 158 | ``` 159 | 160 | {/* Your content here... */} 161 | 162 | ``` 163 | 164 | Styling the images themselves is fairly straightforward. In addition to applying styles, certain props control how the image will be rendered. You’ll often make use of the resizeMode prop, for instance, which can be set to resize, cover, or contain. The UIExplorer app demonstrates this well: 165 | 166 | The component is easy to work with, and very flexible. You will likely make extensive use of it in your own applications. 167 | 168 | ## Resource 169 | 170 | # Container 171 | 172 | ## Navigation 173 | 174 | - [Routing and Navigation in React Native](https://medium.com/the-exponent-log/routing-and-navigation-in-react-native-6b27bee39603#.s1ozj1r3b) 175 | 176 | ## TabBar 177 | 178 | # Layout 179 | 180 | ## Responsive 181 | 182 | ### [autoresponsive-react](https://xudafeng.github.io/autoresponsive-react/) 183 | 184 | ### Images 185 | 186 | [responsive-images-in-react-native](http://www.reactnative.com/responsive-images-in-react-native/) 187 | 188 | # Storage 189 | 190 | ## AsyncStorage 191 | 192 | ## SQLite 193 | 194 | ReactNative 对于 SQLite 的支持主要还是依靠本地 Module-Bridge。可以参考[react-native-sqlit](https://github.com/almost/react-native-sqlite#usage)这个类库 195 | -------------------------------------------------------------------------------- /移动应用/iOS/界面开发/UITableView.md: -------------------------------------------------------------------------------- 1 | # UITableView 2 | 3 | > 参考资料 4 | > 5 | > - [iOS 开发系列--UITableView 全面解析][1] 6 | 7 | 使用 UITableView 时,一般会要实现两个 Protocol: 8 | 9 | UITableViewDataSource 与 UITableViewDelegate。注意,如果使用 UITableViewController 方法,它使自动继承了上述两个 Protocol,但是他会内置一个 tableview。如果是自己在 Xib 文件中设置了 UITableView,建议还是使用原生的 UIViewController。 10 | 11 | 其中 UITableViewDataSource 的需要实现的方法为: 12 | 13 | | 方 法 | 返回类型 | 说 明 | 14 | | ----------------------------------------------- | ----------------- | ---------------------------------------------- | 15 | | tableView:cellForRowAtIndexPath: | UITableViewCell\* | 为表视图单元格提供数据,该方法是必须实现的方法 | 16 | | tableView:numberOfRowsInSection: | NSInteger | 返回某个节中的行数 | 17 | | tableView:titleForHeaderInSection: | NSString | 返回节头的标题 | 18 | | tableView:titleForFooterInSection: | NSString | 返回节脚的标题 | 19 | | numberOfSectionsInTableView: | NSInteger | 返回节的个数 | 20 | | sectionIndexTitlesForTableView: | NSArray\* | 提供表视图节索引标题 | 21 | | tableView:commitEditingStyle:forRowAtIndexPath: | void | 为删除或修改提供数据 | 22 | 23 | UITableViewDelegate 需要实现的方法为: 24 | 25 | | 方 法 | 返回类型 | 说 明 | 26 | | ------------------------------------------------- | -------- | -------------------------------------------------------------------- | 27 | | tableView:viewForHeaderInSection: | UIView\* | 为节头准备自定义视图,iOS 6 之后可以使用 UITableViewHeaderFooterView | 28 | | tableView:viewForFooterInSection: | UIView\* | 为节脚准备自定义视图,iOS 6 之后可以使用 UITableViewHeaderFooterView | 29 | | tableView:didEndDisplayingHeaderView:forSection: | void | 该方法在节头从屏幕中消失时触发(iOS 6 之后的方法) | 30 | | tableView:didEndDisplayingFooterView:forSection: | void | 当节脚从屏幕中消失时触发(iOS 6 之后的方法) | 31 | | tableView:didEndDisplayingCell:forRowAtIndexPath: | void | 当单元格从屏幕中消失时触发(iOS 6 之后的方法) | 32 | | tableView:didSelectRowAtIndexPath: | void | 响应选择表视图单元格时调用的方法 | 33 | 34 | ## Simple UITableView 35 | 36 | UITableView 有两种风格:UITableViewStylePlain 和 UITableViewStyleGrouped。这两者操作起来其实并没有本质区别,只是后者按分组样式显示前者按照普通样式显示而已。大家先看一下两者的应用: 37 | 38 | ![Group][2] 39 | 40 | ![Plain][3] 41 | 42 | 这两种不同的呈现模式,可以利用初始化时候指定: 43 | 44 | ``` 45 | self.tableView =[[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; 46 | ``` 47 | 48 | 如果是利用 Xib 来构建一个 UITableView,也可以在 49 | 50 | 最简单的完成 UITableView 的时序图如下: 51 | 52 | ![][4] 53 | 54 | ### DataSource:数据源 55 | 56 | 在完成对于 UITableView 的初始化之后,需要指定 UITableView 的数据源。即指定\_tableView 的句柄指向一个实现了 UITableViewDataSource 的类。 57 | 58 | ``` 59 | //设置数据源,注意必须实现对应的UITableViewDataSource协议 60 | _tableView.dataSource=self; 61 | ``` 62 | 63 | #### tableView:cellForRowAtIndexPath: 64 | 65 | 数据源的接口中,最重要的就是要完成对于 Cell 的构造。在简单实现中,我们可以调用系统提供的默认样式: 66 | 67 | ``` 68 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath 69 | { 70 | //初始化Cell 71 | static NSString *CellIdentifier = @"CellIdentifier"; 72 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 73 | if (cell == nil) 74 | { 75 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 76 | reuseIdentifier:CellIdentifier]; 77 | } 78 | //为Cell填充数据 79 | NSUInteger row = [indexPath row]; 80 | NSDictionary *rowDict = [self.listTeams objectAtIndex:row]; 81 | cell.textLabel.text = [rowDict objectForKey:@"name"]; 82 | NSString *imagePath = [rowDict objectForKey:@"image"]; 83 | imagePath = [imagePath stringByAppendingString:@".png"]; 84 | cell.imageView.image = [UIImage imageNamed:imagePath]; 85 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 86 | return cell; 87 | } 88 | ``` 89 | 90 | 除了 Default 的样式之外,其他 iOS 还默认提供的 Cell 的样式有: 91 | 92 | - UITableViewCellStyleSubtitle 93 | 94 | Subtitle 样式:左边一个显示图片的 imageView,上边一个主标题 textLabel,一个副标题 detailTextLabel。主标题字体大且加黑,副标题字体小在主标题下边。 95 | 96 | - UITableViewCellStyleValue1 97 | 98 | Value1 样式:左边一个显示图片的 imageView,左边一个主标题 textLabel,右边一个副标题 detailTextLabel,主标题字体比较黑。 99 | 100 | - UITableViewCellStyleValue2 101 | 102 | Value2 样式:左边一个主标题 textLabel 字体偏小,挨着右边一个副标题 detailTextLabel,字体大且加黑。 103 | 104 | ## UITableViewCell:自定义样式 105 | 106 | ### UITableViewCellAccessory 107 | 108 | ``` 109 | typedef NS_ENUM(NSInteger, UITableViewCellAccessoryType) { 110 | UITableViewCellAccessoryNone, // 不显示任何图标 111 | UITableViewCellAccessoryDisclosureIndicator, // 跳转指示图标 112 | UITableViewCellAccessoryDetailDisclosureButton, // 内容详情图标和跳转指示图标 113 | UITableViewCellAccessoryCheckmark, // 勾选图标 114 | UITableViewCellAccessoryDetailButton NS_ENUM_AVAILABLE_IOS(7_0) // 内容详情图标 115 | }; 116 | ``` 117 | 118 | ![Cell 操作][5] 119 | 120 | 可以通过: 121 | 122 | ``` 123 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 124 | ``` 125 | 126 | 方式来进行指定。 127 | 128 | ### CustomCell 129 | 130 | 如果需要使用自定义的 Cell,可以先建立一个自定义的 Xib 文件,并将其与某个 View 建立关联。如下图所示: 131 | 132 | ![生成自定义Xib文件][6] 133 | 134 | 然后,同样的在关于生成 Cell 的 Delegate 方法中,进行 Cell 的配置: 135 | 136 | ``` 137 | - (UITableViewCell *)tableView:(UITableView *)tableView 138 | cellForRowAtIndexPath:(NSIndexPath *)indexPath 139 | { 140 | static NSString *CellIdentifier = @"Cell"; 141 | CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 142 | NSUInteger row = [indexPath row]; 143 | NSDictionary *rowDict = [self.listTeams objectAtIndex:row]; 144 | cell.name.text = [rowDict objectForKey:@"name"]; 145 | NSString *imagePath = [rowDict objectForKey:@"image"]; 146 | imagePath = [imagePath stringByAppendingString:@".png"]; 147 | cell.image.image = [UIImage imageNamed:imagePath]; 148 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 149 | return cell; 150 | } 151 | ``` 152 | 153 | 注意,在上述代码中,不需要再手动对于 Cell 进行初始化了。 154 | 155 | > 常见错误 156 | > 157 | > reason: 'unable to dequeue a cell with identifier Cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard’ 158 | 159 | ```objective-c 160 | //注册一个Class作为ReusableCell 161 | [self.tableView registerClass:[HSFriendListView class] forCellReuseIdentifier:@"HSFriendListViewCell"]; 162 | 163 | //注册一个Nib文件作为ReusableCell 164 | [self.tableView registerNib:[UINib 165 | nibWithNibName:@"HSCommentCell" 166 | bundle:[NSBundle mainBundle]] 167 | forCellReuseIdentifier:reuseIdentifier]; 168 | ``` 169 | 170 | ### Auto Height 171 | 172 | - [FDTemplateLayoutCell][7] 173 | 174 | [enter description here][8] 175 | 176 | # Static UITableView 177 | 178 | [ios-static-table-view-storyboard](http://www.appcoda.com/ios-static-table-view-storyboard/) 179 | 180 | # UITableView-Extension 181 | 182 | ## QuickCreate 183 | 184 | 这部分总结了一些快速创建表单的辅助。 185 | 186 | ### [Eureka](https://github.com/xmartlabs/Eureka#introduction) 187 | 188 | ![](https://github.com/xmartlabs/Eureka/raw/master/Example/Media/EurekaNavigation.gif) 189 | 190 | ![](https://github.com/xmartlabs/Eureka/raw/master/Example/Media/EurekaRows.gif) 191 | 192 | ## Folding 193 | 194 | ### [folding-cell](https://github.com/Ramotion/folding-cell) 195 | 196 | ![](https://github.com/Ramotion/folding-cell/raw/master/Screenshots/folding-cell.gif) 197 | 198 | # AutoHeight 199 | 200 | ## [TableViewCellWithAutoLayoutiOS8](https://github.com/smileyborg/TableViewCellWithAutoLayoutiOS8) 201 | -------------------------------------------------------------------------------- /移动应用/WebView/Cordova/iOS.md: -------------------------------------------------------------------------------- 1 | # Cordova 2 | 3 | - [iOS Plugin Development Guide](https://cordova.apache.org/docs/en/latest/guide/platforms/ios/plugin.html) 4 | 5 | 在 iOS 的 WebView 开发中,经常会把 Cordova 作为增强版的 WebView 使用。关于本部分的实例可以参考笔者的[iOSBoilerplate](https://github.com/wx-chevalier/iOS-Boilerplate/tree/master/UI-Components/Widgets/WebView/Cordova),可以在 REAME.md 中查看使用说明,也可以`git clone`之后直接运行,按照指导进入相关页面。 6 | 7 | ## Installation 8 | 9 | 引入 Cordova 主要包含三个步骤(怎么感觉有点像把大象塞到冰箱):(1)在 Podfile 中加入依赖项可以使用`pod search Cordova`命令来搜索可用的 Cordova 版本,笔者是使用的 4.0.1 版本: 10 | 11 | ``` 12 | pod 'Cordova', '~> 4.0.1' # 支持Cordova WebView容器 13 | ``` 14 | 15 | 添加完毕然后使用`pod install`命令下载即可。(2)添加 config.xml config.xml 即是主要的配置文件,在 iOS 中其需要放置到`/AppName/config.xml`这种样式。笔者的 config.xml 文件的示范为: 16 | 17 | ``` 18 | 19 | 20 | iOSBoilerplate 21 | 22 | Cordova Demo in iOS Boilerplate 23 | 24 | 25 | Chevalier 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | ``` 53 | 54 | (3)添加 www 文件夹一般来说会把静态资源文件放置到 www 目录下,这边有一个小点需要注意下(不知道是不是笔者搞错了),就是将 www 文件夹引入到 XCode 中的时候,注意不要选择 Copy 而是 File Reference,即最终的文件夹应该是如下图所示的蓝色而不是黄色。 55 | 56 | ### Network Configuration 57 | 58 | > - [cordova-5-ios-9-security-policy-changes](http://moduscreate.com/cordova-5-ios-9-security-policy-changes/) 59 | 60 | 有时候在 iOS 中进行配置的时候会发现部分网络请求被 Ban,可以根据以下几个步骤进行排查。(1)判断 config.xml 中是否设置了网络请求的白名单,老实说现在 cordova-plugin-whitelist 这个插件都没有了 iOS 端,不确定这个是不是需要的。 61 | 62 | ``` 63 | 64 | ``` 65 | 66 | (2)在 iOS 9 之后默认是不允许非 HTTPs 的请求发出,所以要修改下配置允许发起 HTTP 请求。![](http://i.stack.imgur.com/nGw3j.png) 67 | 68 | ``` 69 | NSAppTransportSecurity NSAllowsArbitraryLoads 70 | ``` 71 | 72 | (3)检查下 Content Security Policy Content Security Policy 一般用于对于网页内容的控制,不过这东西如果禁止了你访问网络,那么在浏览器内也是看得出来的。 73 | 74 | ``` 75 | 76 | ``` 77 | 78 | ## Plugins 79 | 80 | ### Config 81 | 82 | 任何一个插件首先需要在 config.xml 中进行注册: 83 | 84 | ``` 85 | 86 | ``` 87 | 88 | 有沒有指定插件的初始值設定項。相反,應使用插件 pluginInitialize 為其啟動邏輯方法。插件需要長時間運行的請求,如媒體重播、聽眾,保持內部狀態應執行的背景活動 onReset 方法來清理這些活動。在方法運行時 UIWebView 定位到新的一頁或刷新,重新載入 JavaScript。 89 | 90 | ### JS Modules 91 | 92 | 关于 JS 部分的详细配置可以参考官方的 JS Modules 部分,这里不做赘述,仅展示下基本的用法: 93 | 94 | ```js 95 | window.echo = function (str, callback) { 96 | cordova.exec( 97 | callback, 98 | function (err) { 99 | callback("Nothing to echo."); 100 | }, 101 | "CordovaPluginsBridge", 102 | "echo", 103 | [str] 104 | ); 105 | }; 106 | ``` 107 | 108 | 调用: 109 | 110 | ```js 111 | window.echo("echome", function (echoValue) { 112 | alert(echoValue == "echome"); // should alert true. 113 | }); 114 | ``` 115 | 116 | 要注意,一般对于 Cordova 的调用要放到 jQuery 的`$(document).ready()`中。 117 | 118 | ### iOS 本地方法 119 | 120 | JavaScript 調用觸發插件請求到本機的一邊,和相應的 iOS 目標 C 插件映射正確地在 config.xml 檔中,但最後 iOS 目標 C 插件類看起來像什麼? 無論派往與 JavaScript 的插件 exec 函數傳遞到相應的插件類的 action 方法。插件的方法有此簽名: 121 | 122 | ``` 123 | - (void)myMethod:(CDVInvokedUrlCommand*)command { CDVPluginResult* pluginResult = nil; NSString* myarg = [command.arguments objectAtIndex:0]; if (myarg != nil) { pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; } else { pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Arg was null"]; } [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } 124 | ``` 125 | 126 | #### iOS CDVPluginResult 訊息類型 127 | 128 | 您可以使用 `CDVPluginResult` 來返回結果的多種類型回 JavaScript 回呼函數,使用類的方法,它們遵循這種模式: 129 | 130 | ``` 131 | + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAs... 132 | ``` 133 | 134 | 您可以創建 `String`,`Int`,`Double`,`Bool`,`Array`,`Dictionary`,`ArrayBuffer`,和 `Multipart` 類型。你可以也離開了任何參數來發送狀態,或返回錯誤,或甚至選擇不發送任何外掛程式的結果,在這種情況下既不回撥火。 135 | 136 | 請注意以下複雜的傳回值為: 137 | 138 | - `messageAsArrayBuffer`預計 `NSData*` 並將轉換為 `ArrayBuffer` 在 JavaScript 回檔。同樣,任何 `ArrayBuffer` JavaScript 發送到一個外掛程式都將轉換為`NSData*`. 139 | - `messageAsMultipart`預計,`NSArray*` 包含任何其他支援類型,並將發送整個陣列作為 `arguments` 給您的 JavaScript 回檔。這種方式,所有參數在序列化或反序列化作為必要的所以它是能夠安全返回 `NSData*` 作為多部分,但不是 `Array` /`Dictionary`. 140 | #### 异步执行 141 | 如果对于部分执行时间较长的代码,可以放在后台进程中执行。 142 | 143 | ``` 144 | - (void)myPluginMethod:(CDVInvokedUrlCommand*)command 145 | { 146 | // Check command.arguments here. 147 | [self.commandDelegate runInBackground:^{ 148 | NSString* payload = nil; 149 | // Some blocking logic... 150 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:payload]; 151 | // The sendPluginResult method is thread-safe. 152 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 153 | }]; 154 | } 155 | ``` 156 | -------------------------------------------------------------------------------- /移动应用/React Native/架构原理/实践技巧.md: -------------------------------------------------------------------------------- 1 | - [Adding a native iOS "Share" button to a React Native app](http://jairtrejo.mx/blog/2015/09/share-button-react-native) 2 | 3 | 笔者正在开发一个自己的 React Native 应用,其中碰到了一个小的功能点,即添加一个本地的第三方分享的按钮。这是一个非常细节的功能点,因此并没有被容纳到默认的框架中。不过,框架提供了一些列本地的 Objective-C 与上层互相调用的接口,很容易即可实现如下的功能: 4 | 5 | ![](http://jairtrejo.mx/media/images/posts/share-button.gif) 6 | 7 | # How it works in iOS(iOS 本地的实现) 8 | 9 | iOS 本地端是使用了[`UIActivityViewController`](http://nshipster.com/uiactivityviewcontroller/)这个控件,它提供了简单的模态对话框的功能,可以显现在屏幕的底端,进行一些预定义的操作。对于 UIActivityViewController 的用法还是比较简单的,用如下代码即可以构造一个简单的分享弹出框: 10 | 11 | ```objective-c 12 | - (IBAction)shareButton:(UIBarButtonItem *)sender 13 | { 14 | NSString *textToShare = @"Look at this awesome website for aspiring iOS Developers!"; 15 | NSURL *myWebsite = [NSURL URLWithString:@"http://www.codingexplorer.com/"]; 16 | 17 | NSArray *objectsToShare = @[textToShare, myWebsite]; 18 | 19 | UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:objectsToShare applicationActivities:nil]; 20 | 21 | NSArray *excludeActivities = @[UIActivityTypeAirDrop, 22 | UIActivityTypePrint, 23 | UIActivityTypeAssignToContact, 24 | UIActivityTypeSaveToCameraRoll, 25 | UIActivityTypeAddToReadingList, 26 | UIActivityTypePostToFlickr, 27 | UIActivityTypePostToVimeo]; 28 | 29 | activityVC.excludedActivityTypes = excludeActivities; 30 | 31 | [self presentViewController:activityVC animated:YES completion:nil]; 32 | } 33 | ``` 34 | 35 | 对于如何在本地端构造并且弹出一个分享模态窗口这边不做赘述,但是需要讨论的是,应该以 UIView 的方式弹出还是以切换 UIViewController 的方式弹出该窗口。我的第一反应是去查看 [React 的官方文档](https://facebook.github.io/react-native/docs/native-components-ios.html#content),这里面他们解释了怎么将一个本地的控件封装为 React 的形式。我的方法也是将分享窗体这个所在的 UIView 显示出来并且以我们需要的方式进行呈现。不过上文中也提及了,这里需要调用的一个 UIViewController 而不是一个 UIView。 36 | 37 | # How to call the Share dialog from a native module 38 | 39 | 因此还是打算用切换 UIViewController 而不是将某个分享的页面嵌入到 React 元素中的方式,创建一个本地的模块还是非常简单的,就如下图所示: 40 | 41 | ![](http://jairtrejo.mx/media/images/posts/new-native-module.png) 42 | 43 | 初始化代码的模板如下所示: 44 | 45 | ```objective-c 46 | // 47 | // RCTShareManager.h 48 | // 49 | 50 | #import 51 | 52 | @interface RCTShareManager : NSObject 53 | @end 54 | 55 | 56 | 57 | // 58 | // 59 | // RCTShareManager.m 60 | // 61 | 62 | #import "RCTShareManager.h" 63 | 64 | @implementation RCTShareManager 65 | 66 | RCT_EXPORT_MODULE(); 67 | 68 | RCT_EXPORT_METHOD(shareURL:(NSString *)URLString) 69 | { 70 | // Some native code 71 | } 72 | 73 | @end 74 | ``` 75 | 76 | 最终 shareURL 这个方法的接口是如下方式暴露到了 JavaScript 中: 77 | 78 | ```js 79 | var React = require("react-native"); 80 | var ShareManager = React.NativeModules.ShareManager; 81 | 82 | // Somewhere in your component... 83 | 84 | function onPress() { 85 | ShareManager.shareURL(someURL); 86 | } 87 | ``` 88 | 89 | 在本地代码中,可以按照如下方法将 UIActivityViewController 结合进来: 90 | 91 | ```objective-c 92 | // 93 | // RCTShareManager.m 94 | // 95 | 96 | #import "RCTShareManager.h" 97 | @import UIKit; 98 | 99 | @implementation RCTShareManager 100 | 101 | RCT_EXPORT_MODULE(); 102 | RCT_EXPORT_METHOD(shareURL:(NSString *)URLString) 103 | { 104 | NSArray *objectsToShare = @[[NSURL URLWithString:URLString]]; 105 | UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:objectsToShare applicationActivities:nil]; 106 | 107 | UIViewController *rootController = UIApplication.sharedApplication.delegate.window.rootViewController; 108 | 109 | [rootController presentViewController:activityVC animated:YES completion:nil]; 110 | } 111 | 112 | @end 113 | ``` 114 | 115 | 不过如果按照上面说的这样处理之后,XCode 会报如下错误: 116 | 117 | ``` 118 | This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release. 119 | ``` 120 | 121 | 并且经过较长时间之后,屏幕会如下所示: 122 | 123 | ![](http://jairtrejo.mx/media/images/posts/share-dialog-error.png) 124 | 125 | 这是因为 React Native 是在后台线程执行了代码,而 iOS 默认是只允许主线程进行前端界面的渲染,因此,我们需要将页面渲染这一部分放置到主线程中进行: 126 | 127 | ```objective-c 128 | RCT_EXPORT_METHOD(shareURL:(NSString *)URLString) 129 | { 130 | NSArray *objectsToShare = @[[NSURL URLWithString:URLString]]; 131 | UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:objectsToShare applicationActivities:nil]; 132 | 133 | UIViewController *rootController = UIApplication.sharedApplication.delegate.window.rootViewController; 134 | 135 | dispatch_async(dispatch_get_main_queue(), ^{ 136 | [rootController presentViewController:activityVC animated:YES completion:nil]; 137 | }); 138 | } 139 | ``` 140 | 141 | > 本文翻译自[React Native: Things I wish I knew before starting out.](https://blog.jari.io/react-native/),从属于笔者的[ReactNative 入门与最佳实践](https://github.com/wx-chevalier/web-frontend-practice-handbook#reactnative)系列文章。 142 | 143 | [React Native](https://facebook.github.io/react-native/) 是个非常优秀的项目,不过鉴于其仍处于不断地演进开发中,我们可能阅读到的很多的教程里的信息还是面向旧版本的。本文就是记录了些根据笔者的日常开发总结而来的你应该格外注意而又没有在文档中强调的细节要点。我会尽可能地在版本迭代之后更新本文的内容,不过还是要强调下本文编写于 React Native v0.33(2016 年 9 月)。 144 | 145 | # rnpm link 146 | 147 | rnpm 已经被集成到了 `react-native` 命令行工具中,可以直接通过`react-native link`命令运行。不要单独的安装 rnpm,它已经被[弃用](https://github.com/rnpm/rnpm#dear-friends)了。很多[第三方的插件](https://github.com/maxs15/react-native-spinkit/tree/v0.1.3#link-the-library-automatically-using-rnpm)乃至于 [官方文档](https://facebook.github.io/react-native/releases/0.33/docs/linking-libraries-ios.html#automatic-linking) 都没有提及这点。 148 | 149 | # 自定义字体?你并不需要编辑 Android/iOS 项目 150 | 151 | 这一点好像也没有在文档之中并没有详细提及,不过根据[这里](https://medium.com/@dabit3/adding-custom-fonts-to-react-native-b266b41bff7f)描述的,我们并不需要编辑 Android/iOS 项目来添加自定义字体。我们只需要在项目的根目录下创建新的`assets`文件夹,拖入你所需要用的字体文件,然后在你的`package.json`中添加如下配置: 152 | 153 | ``` 154 | "rnpm": { "assets": ["assets"] }, 155 | ``` 156 | 157 | 然后,执行`react-native link`命令,该命令不仅会将第三方模块的原生代码链接入 Android/iOS 项目,还会将字体等文件移动至相应的目录中。然后你就可以通过设置`fontFamily`来使用安装好的字体。不过有时候使用`react-native link`并不能如人所愿,此时你可以参考下[react-native-video](https://github.com/react-native-community/react-native-video)这里的一些教程。 158 | 另外,需要注意的是目前 RN 中只支持 font-weights 与 styles 为 bold 与 italic,RN 会自动搜索 yourFamilyName_bold.ttf 与 yourFamilyName_italic.ttf。 159 | 160 | # RN 中集成了不少非标准的 Babel Plugins 161 | 162 | React 与 RN 本身已经集成了不少的 Babel Plugins,你的代码可以直接使用这些 Plugins,不过很多文档中并没有提及这些 Plugins,我觉得了解有哪些 Plugins 还是很有意义的。 163 | 164 | ## Flow 165 | 166 | [Flow](https://flowtype.org/)是 Facebook 出品的静态类型的语言,很类似于微软的[TypeScript](http://typescriptlang.org/)。所谓静态类型语言即使当你在初始化某个变量的类型之后你并不能再改变它的类型,类似于熟悉的 C#、Java 等等这些语言,这一特性有助于我们编写安全可控的代码。一个简单的 Flow 的例子如下所示: 167 | 168 | ``` 169 | function foo(argument1: string, argument2: number): string { 170 | argument1 = 0; // this will fail when running flow 171 | argument2 = 0; // but this won't!return 1; // this however, will - you guessed it - fail. 172 | } 173 | ``` 174 | 175 | Flow 官网上建议在代码的首部添加`// @flow`,不过根据测试因为 RN 中已经内置了合适的 Plugins,其可以完成自动的转化。 176 | 177 | ## ES7 Class Properties 178 | 179 | RN 内部也已支持[ES7 Class Properties](https://babeljs.io/docs/plugins/transform-class-properties/),也就意味着你不需要写如下复杂的代码: 180 | 181 | ``` 182 | class MyComponent extends Component { 183 | constructor(props) { 184 | super(props); 185 | this.thisIsAField = 1; 186 | } 187 | } 188 | MyComponent.propTypes = { 189 | firstname: React.PropTypes.string, 190 | lastname: React.PropTypes.string 191 | }; 192 | ``` 193 | 194 | 而可以写成这样: 195 | 196 | ``` 197 | class MyClass { 198 | thisIsAField = 1; 199 | static propTypes = { 200 | firstname: React.PropTypes.string, 201 | lastname: React.PropTypes.string 202 | }; 203 | } 204 | ``` 205 | 206 | ## Object Spread Operator 207 | 208 | [Object Spread Operator](https://babeljs.io/docs/plugins/transform-object-rest-spread/)可以使得对象的合并更加方便,类似于 Array Spread Operator,我们可以使用`...`来解构某个对象,譬如: 209 | 210 | ``` 211 | // Rest propertieslet { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; 212 | console.log(x); // 1 213 | console.log(y); // 2 214 | console.log(z); // { a: 3, b: 4 }// Spread propertieslet n = { x, y, ...z }; 215 | console.log(n); // { x: 1, y: 2, a: 3, b: 4 } 216 | ``` 217 | -------------------------------------------------------------------------------- /移动应用/iOS/AppStore/README.md: -------------------------------------------------------------------------------- 1 | # AppStore 2 | 3 | # 证书 4 | 5 | > [ iOS Provisioning Profile(Certificate) 与 Code Signing 详解 ](http://blog.csdn.net/phunxm/article/details/42685597/) 6 | 7 | AppStore 的证书与绑定机制对于笔者感觉还是蛮复杂的,不过流程多走即便可能就会清晰很多。首先需要明白的是,在苹果的证书体系中,Certificates 与 Identifiers 是相互独立的,笔者之前经常有个疑问就是最早提交某个 APP 审核的开发者的 P12 丢失了,那后续重新申请的证书还可以用于提交后续版本吗?答案肯定是可以的。一般来说,某个 Certificates+Identifiers 会得到一个 MobileProvision 文件,而该文件就是在进行本地开发、打包上传时所必须的。![](http://www.dcloud.io/docs/a/cert/idc_ioscert.png) iOS 有两种证书和描述文件: 8 | 9 | | 证书类型 | 使用场景 | 10 | | ---------------------------------- | ----------------- | 11 | | 开发 (Development) 证书和描述文件 | 用于开发测试 | 12 | | 发布 (Distribution) 证书和描述文件 | 用于提交 Appstore | 13 | 14 | ## App ID & Bundle Identifier 15 | 16 | 在苹果官方的开发者计划(Apple Developer Member Center )层面,App ID 即 Product ID,用于标识一个或者一组 App。 17 | 18 | 在 Mac/iOS 开发语境中,**bundle**(捆绑) 是指一个内部结构按照标准规则组织的特殊目录。在 Mac OS 应用程序目录下的某个 \*.app 上可右键显示包内容(Contents ),其本质上就是可执行二进制文件(MacOS/ )及其资源(Resources/ )的[打包组合](http://blog.sina.com.cn/s/blog_7b9d64af0101jmj2.html)。因此,在 Xcode 中配置的 Bundle Identifier 必须和 App ID 是一致的(Explicit )或匹配的(Wildcard )。 19 | 20 | App ID 字符串通常以**反域名**(reverse-domain-name )格式的 Company Identifier(Company ID )作为前缀(Prefix/Seed ),一般不超过 255 个 ASCII 字符。 21 | 22 | App ID 全名会被追加 Application Identifier Prefix(一般为 TeamID.),分为两类: 23 | 24 | - Explicit App ID:唯一的 App ID,用于唯一标识一个应用程序。例如 “com.apple.garageband” 这个 App ID,用于标识 Bundle Identifier 为 “com.apple.garageband” 的 App。 25 | - Wildcard App ID:含有通配符的 App ID,用于标识一组应用程序。例如 “_”(实际上是 Application Identifier Prefix)表示所有应用程序;而 “com.apple._” 可以表示 Bundle Identifier 以 “com.apple.” 开头(苹果公司)的所有应用程序。 26 | 27 | 用户可在 Developer Member Center 网站上注册(Register )或删除(Delete )已注册的 App IDs。App ID **被配置到**【XcodeTarget|Info|Bundle Identifier 】下;对于 Wildcard App ID,只要 bundle identifier 包含其作为 Prefix/Seed 即可。![](http://www.dcloud.io/docs/a/cert/id_name.png) ![](http://www.dcloud.io/docs/a/cert/id_id.png) 28 | 29 | ## Certificates 30 | 31 | iOS 证书是用来证明 iOS App 内容(bundle with executable and resources )的合法性和完整性的**数字证书**。对于想安装到真机或发布到 AppStore 的应用程序(App ),只有经过**签名验证**(Signature Validated )才能确保来源可信,并且保证 App 内容是完整、未经篡改的。 32 | 33 | iOS 证书分为两类:Development 和 Production(Distribution )。 34 | 35 | - Development 证书用来开发和调试应用程序:A **development certificate** identifies you, as a team member, in a development provisioning profile that allows apps signed by you to **launch **on devices. 36 | - Production 主要用来分发应用程序(根据证书种类有不同作用): A **\*distribution certificate\*** identifies your team or organization in a distribution provisioning profile and allows you to **\*submit \***your app to the store. Only a team agent or an admin can create a distribution certificate. 37 | 38 | 普通个人开发账号最多可注册 iOS Development/Distribution 证书各 2 个,用户可在网站上删除(Revoke )已注册的 Certificate。Apple 证书颁发机构 WWDRCA(Apple Worldwide Developer Relations Certification Authority) 将使用其 private key 对 CSR 中的 public key 和一些身份信息进行加密签名生成数字证书(ios_development.cer )并记录在案(Apple Member Center )。![](http://img.blog.csdn.net/20150412074512682) ![](http://img.blog.csdn.net/20150422073707077) 39 | 40 | 而 P12 文件,正是用于在多台设备间共享证书。在 Keychain Access|Certificates 中选中欲导出的 certificate 或其下 private key,右键 Export 或者通过菜单 File|Export Items 导出 Certificates.p12——PKCS12 file holds the private key and certificate。其 他 Mac 机器上双击 Certificates.p12(如有密码需输入密码)即可安装该共享证书。有了共享证书之后,在开发者网站上将欲调试的 iOS 设备注册到该开发者账号名下,并下载对应证书授权了 iOS 调试设备的 Provisioning Profile 文件,方可在 iOS 真机设备上开发调试。 41 | 42 | ## Provision Profiles 43 | 44 | Provisioning Profile 文件包含了上述的所有内容:证书、App ID 和 设备 ID。![](http://img.blog.csdn.net/20150126225313444) 一个 Provisioning Profile 对应一个 Explicit App ID 或 Wildcard App ID(一组相同 Prefix/Seed 的 App IDs)。在网站上手动创建一个 Provisioning Profile 时,需要依次指定 App ID(单选)、证书(Certificates,可多选)和设备(Devices,可多选)。用户可在网站上删除(Delete )已注册的 Provisioning Profiles。Provisioning Profile 决定 Xcode 用哪个证书(公钥)/ 私钥组合(Key Pair/Signing Identity )来签署应用程序(Signing Product ),并将在应用程序打包时嵌入到 .ipa 包里。安装应用程序时,Provisioning Profile 文件被拷贝到 iOS 设备中,运行该 iOS App 的设备通过它来认证安装的程序。如果要打包到真机上运行一个 APP,一般要经历以下三步: 45 | 46 | - 首先,需要指明它的 App ID,并且验证 Bundle ID 是否与其一致; 47 | - 其次,需要证书对应的私钥来进行签名,用于标识这个 APP 是合法、安全、完整的; 48 | - 然后,如果是真机调试,需要确认这台设备是否授权运行该 APP。Provisioning Profile 把这些信息全部打包在一起,方便我们在调试和发布程序打包时使用。这样,只要在不同的情况下选择不同的 Provisioning Profile 文件就可以了。Provisioning Profile 也分为 Development 和 Distribution 两类,有效期同 Certificate 一样。Distribution 版本的 ProvisioningProfile 主要用于提交 App Store 审核,其中不指定开发测试的 Devices(0,unlimited)。App ID 为 Wildcard App ID(\* )。App Store 审核通过上架后,允许所有 iOS 设备(Deployment Target )上安装运行该 App。Xcode 将全部供应配置文件(包括用户手动下载安装的和 Xcode 自动创建的 Team Provisioning Profile)放在目录 ~/Library/MobileDevice/Provisioning Profiles 下。 49 | 50 | ## Xcode 7 免证书调试 51 | 52 | 所谓 “ 免证书 ” 真机调试,并不是真的不需要证书,Xcode 真机调试原有的证书配置体系仍在 ——All iOS, tvOS, and watchOS appsmust be code signed and provisioned to launch on a device. 所以,上文啰嗦几千字还是有点用的。自 Xcode7 开始,原来基于付费开发者账号及自助生成证书及配置文件的繁琐过程被苹果简化,Xcode 将针对任何普通账号自动为联调真机生成所需相关的证书及配置文件。当你打算向 App Store 提交发布应用,才需要付费。第一步:进入 Xcode Preferences|Accounts,添加自己的 Apple ID 账号。第二步:Build Settings|Code Signing 下的 Provisioning Profile 选择 Automatic,Code Signing Identity 选择 Automatic 下的 iOS Developer。第三步:General 配置 Bundle identifier,Team 下拉选择苹果 Member Center 自动为你的账号生成的 Personal Team ID。自己的账号在调试公司或其他第三方 APP 代码时,若填写 Bundle identifier 为他人账号注册的 APP ID(例如苹果相机应用 com.apple.camera),会报错: No provisioning profiles with a valid signing identity (i.e. certificate and private key pair) matching the bundle identifier “com.apple.camera” were found. 即使编译通过了,可能运行时 APP 自身与服务器校验也可能会报签名错误,肿么办???Her skill:此时,可以在他人原有 App ID 基础上添加后缀(例如 com.apple.camera.extension),配置成应用的衍生插件(相当于置于同一 App Group 下)就可以快乐的玩耍了。如果启动 APP 时,Xcode 报错 “process launch failed: Security”或 iPhone 报错【不受信任的开发者】,此时需要到 iPhone 通用配置中的描述文件(最新系统中可能叫设备管理)中,在描述文件(开发商应用)中选择对应的描述文件(你的 Apple ID)点击 信任或验证 即可。 53 | 54 | # 内容 / 功能 Issue 55 | 56 | ## UGC 社区审核 57 | 58 | 笔者之前做过体育社交类产品,提交的时候被拒了,原因是没有添加内容举报的功能。 59 | 60 | # IPv6 Issue 61 | 62 | > [针对苹果 iOS 最新审核要求为应用兼容 IPv6](http://www.jianshu.com/p/54b989098537) 63 | 64 | 自 2016 年 6 月 1 日起,苹果要求所有提交 App Store 的 iOS 应用必须支持 IPv6-only 环境,背景也是众所周知的,IPv4 地址已基本分配完毕,同时 IPv6 比 IPv4 也更加高效,向 IPv6 过渡是大势所趋。 65 | 66 | ## IPv6 Only 67 | 68 | 首先需要明确一点,在 App Store 审核 APP 的 IPv6-only 的环境下也是可以正常访问 IPv4 的服务的,只是首先由 DNS64 将解析出来的 IPv4 地址转成兼容的 IPv6 地址,然后访问 IPv4 服务时通过 NAT64 网关对 IPv4 和 IPv6 进行 NAT,并不需要客户有实际的 IPv6 服务。如下图所示: ![](http://upload-images.jianshu.io/upload_images/273968-856b69a836ade53a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 客户端在向 DNS64 请求一个域名的 IPv6 地址时,DNS64 会向域名的授权 DNS 请求 IPv6 地址,如果存在 IPv6 地址,则直接给客户端返回 IPv6 地址,如果不存在 IPv6 地址,则向授权请求 IPv4 地址,并将返回的 IPv4 地址转换为兼容的 IPv6 地址。以 Google DNS64 为例说明转换规则,分别请求 dnspod.cn 的 A 记录(IPv4 地址)和 AAAA 记录(IPv6 地址): ![](http://upload-images.jianshu.io/upload_images/273968-b1d11427a1922f10.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 从解析结果可以看出 IPv4 地址对应的 IPv6 地址,后 32 位的 3b25:7465 实际上就是 IPv4 地址的 16 进制表示 59=0x3b,37=0x25,116=0x74,101=0x65,明白该规则后也可以自己进行 IPv4 向兼容的 IPv6 地址的转换,如 119.29.29.29 的兼容 IPv6 地址为 64:ff9b::771d:1d1d,其中::表示为全 0。DNS64 解析流程如下图所示: ![](http://upload-images.jianshu.io/upload_images/273968-9afe85bbacad3275.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 69 | 70 | ### 搭建 IPv6 测试环境 71 | 72 | 用 Mac 做一个热点,然后用 iPhone 连接这个 Wi-Fi,在 “System Preferences” 界面选中 “Sharing” 的同时,要按住 “Option” 键。![](http://upload-images.jianshu.io/upload_images/841355-bd2a25d779153e4c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 之后在 “Sharing” 界面中,我们会看到和之前不一样的地方,就是红框所标的地方,多了一个叫 “Create NAT64 Network” 的选框,选中它。![](http://upload-images.jianshu.io/upload_images/841355-8e5aa1eac3c24a8d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 73 | 74 | ## 开发修正 75 | 76 | ### 避免使用底层的网络 API 77 | 78 | 下图展示的蓝色部分的这些 API 都是不存在兼容性问题的,而我们平时自己用的包括那些第三方的网络库大部分都是用的这些 API。![](http://upload-images.jianshu.io/upload_images/1196725-d7c683dce4c7b70f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 大部分情况下,我们用高级的 API 完全能够实现我们的需求,而且高级 API 封装的很便于使用,很多底层的像适配 IPv6 的工作都已经帮我们做好了。而用底层 API 会有大量的工作要我们自己来做,更容易产生 bug。但你如果确实需要用底层的 POSIX socket API, 请参照这个 RFC4038: Application Aspects of IPv6 Transition 的指导。 79 | 80 | ### 避免直接使用 IP 地址 81 | 82 | 比如下面这个 API,nodename 这个参数不要传 IP 地址,而应该用域名 ![](http://upload-images.jianshu.io/upload_images/1196725-578f17a4a4ef0a69.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 这个方法在著名的第三方 Reachability 中是用到的,我们常用的第三方网络库 AFNetworking 就用了这个。所以用到的同学得好好查一下了。 83 | 84 | ### 检查不兼容的 IPv6 代码 85 | 86 | ``` 87 | inet_addr() 88 | 89 | inet_aton() 90 | 91 | inet_lnaof() 92 | 93 | inet_makeaddr() 94 | 95 | inet_netof() 96 | 97 | inet_network() 98 | 99 | inet_ntoa() 100 | 101 | inet_ntoa_r() 102 | 103 | bindresvport() 104 | 105 | getipv4sourcefilter() 106 | 107 | setipv4sourcefilter() 108 | ``` 109 | 110 | 以及如下的一些数据类型 : ![](http://upload-images.jianshu.io/upload_images/841355-dd37905d642ca9bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 111 | 112 | ![](http://153.3.251.190:11900/AppStore) 113 | -------------------------------------------------------------------------------- /移动应用/iOS/系统功能/HealthCheck.md: -------------------------------------------------------------------------------- 1 | # Prerequisite(预准备) 2 | 3 | ## Enable HealthKit 4 | 5 | 如果希望在应用程序中使用 HealthKit,首先需要在生成证书的时候勾选 HealthKit 选项。 6 | 7 | ![](http://jademind.com/wp-content/uploads/2014/11/healthkit_enable_capability.png) 8 | 9 | ## Check availability(检查 HealthKit 可用性) 10 | 11 | 考虑到目前 HealthKit 仅仅可以在 iPhone 设备上使用,不能在 iPad 或者 iPod 中使用,所以在接入 HealthKit 代码之前最好检验下可用性: 12 | 13 | ```objective-c 14 | if(NSClassFromString(@"HKHealthStore") && [HKHealthStore isHealthDataAvailable]) 15 | { 16 | // Add your HealthKit code here 17 | } 18 | ``` 19 | 20 | ## Request authorization(请求授权) 21 | 22 | 由于 HealthKit 存储了大量的用户敏感信息,App 如果需要访问 HealthKit 中的数据,首先需要请求用户权限。权限分为读取与读写权限(苹果将读写权限称为 share)。请求权限还是比较简单的,可以直接使用[`requestAuthorizationToShareTypes: readTypes: completion:`](https://developer.apple.com/library/ios/DOCUMENTATION/HealthKit/Reference/HKHealthStore_Class/index.html#//apple_ref/occ/instm/HKHealthStore/requestAuthorizationToShareTypes:readTypes:completion:) 方法。 23 | 24 | ```objective-c 25 | HKHealthStore *healthStore = [[HKHealthStore alloc] init]; 26 | 27 | // Share body mass, height and body mass index 28 | NSSet *shareObjectTypes = [NSSet setWithObjects: 29 | [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass], 30 | [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeight], 31 | [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMassIndex], 32 | nil]; 33 | 34 | // Read date of birth, biological sex and step count 35 | NSSet *readObjectTypes = [NSSet setWithObjects: 36 | [HKObjectType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierDateOfBirth], 37 | [HKObjectType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierBiologicalSex], 38 | [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount], 39 | nil]; 40 | 41 | // Request access 42 | [healthStore requestAuthorizationToShareTypes:shareObjectTypes 43 | readTypes:readObjectTypes 44 | completion:^(BOOL success, NSError *error) { 45 | 46 | if(success == YES) 47 | { 48 | // ... 49 | } 50 | else 51 | { 52 | // Determine if it was an error or if the 53 | // user just canceld the authorization request 54 | } 55 | 56 | }]; 57 | ``` 58 | 59 | 如上代码会调用下图这样的权限请求界面: 60 | 61 | ![](http://jademind.com/wp-content/uploads/2014/11/healthkit_request_auth_dialog-575x1024.png) 62 | 63 | 用户在该界面上可以选择接受或者拒绝某些对于读写健康数据的请求。在确定或者关闭请求界面之后,回调会被自动调用。 64 | 65 | # 读写数据 66 | 67 | 从 Health Store 中读写数据的方法比较直接,HKHealthStore 类是提供了很多便捷的方法读取基本的属性。不过如果需要以更多复杂的方式进行查询,可以使用相关的子类:HKQuery。 68 | 69 | ## 生理数据 70 | 71 | ### 性别与年龄 72 | 73 | ```objective-c 74 | NSError *error; 75 | HKBiologicalSexObject *bioSex = [healthStore biologicalSexWithError:&error]; 76 | 77 | switch (bioSex.biologicalSex) { 78 | case HKBiologicalSexNotSet: 79 | // undefined 80 | break; 81 | case HKBiologicalSexFemale: 82 | // ... 83 | break; 84 | case HKBiologicalSexMale: 85 | // ... 86 | break; 87 | } 88 | ``` 89 | 90 | ### 体重 91 | 92 | ```objective-c 93 | // Some weight in gram 94 | double weightInGram = 83400.f; 95 | 96 | // Create an instance of HKQuantityType and 97 | // HKQuantity to specify the data type and value 98 | // you want to update 99 | NSDate *now = [NSDate date]; 100 | HKQuantityType *hkQuantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass]; 101 | HKQuantity *hkQuantity = [HKQuantity quantityWithUnit:[HKUnit gramUnit] doubleValue:weightInGram]; 102 | 103 | // Create the concrete sample 104 | HKQuantitySample *weightSample = [HKQuantitySample quantitySampleWithType:hkQuantityType 105 | quantity:hkQuantity 106 | startDate:now 107 | endDate:now]; 108 | 109 | // Update the weight in the health store 110 | [healthStore saveObject:weightSample withCompletion:^(BOOL success, NSError *error) { 111 | // .. 112 | }]; 113 | ``` 114 | 115 | ## 运动数据 116 | 117 | 运动数据查询时往往进行的是统计类型的查询,即查询某几个小时或者某几天的运动数据情况,此时最常用的工具类即是 HKStatisticsQuery。要使用该类,首先是调用`initWithQuantityType:quantitySamplePredicate:options:completionHandler:`方法进行初始化,然后使用[executeQuery:](https://developer.apple.com/library/prerelease/ios/documentation/HealthKit/Reference/HKHealthStore_Class/index.html#//apple_ref/occ/instm/HKHealthStore/executeQuery:)方法进行查询。 118 | 119 | ### 步数与卡路里 120 | 121 | - Objective-C 122 | 123 | ```objective-c 124 | - (void)fetchTotalJoulesConsumedWithCompletionHandler:(void (^)(double, NSError *))completionHandler { 125 | NSCalendar *calendar = [NSCalendar currentCalendar]; 126 | 127 | NSDate *now = [NSDate date]; 128 | 129 | NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:now]; 130 | 131 | NSDate *startDate = [calendar dateFromComponents:components]; 132 | 133 | NSDate *endDate = [calendar dateByAddingUnit:NSCalendarUnitDay value:1 toDate:startDate options:0]; 134 | 135 | HKQuantityType *sampleType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierDietaryEnergyConsumed]; 136 | NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionStrictStartDate]; 137 | 138 | HKStatisticsQuery *query = [[HKStatisticsQuery alloc] initWithQuantityType:sampleType quantitySamplePredicate:predicate options:HKStatisticsOptionCumulativeSum completionHandler:^(HKStatisticsQuery *query, HKStatistics *result, NSError *error) { 139 | if (!result) { 140 | if (completionHandler) { 141 | completionHandler(0.0f, error); 142 | } 143 | return; 144 | } 145 | 146 | double totalCalories = [result.sumQuantity doubleValueForUnit:[HKUnit jouleUnit]]; 147 | if (completionHandler) { 148 | completionHandler(totalCalories, error); 149 | } 150 | }]; 151 | 152 | [self.healthStore executeQuery:query]; 153 | } 154 | ``` 155 | 156 | - Swift 157 | 158 | ```swift 159 | func fetchTotalJoulesConsumedWithCompletionHandler( 160 | completionHandler:(Double?, NSError?)->()) { 161 | 162 | let calendar = NSCalendar.currentCalendar() 163 | let now = NSDate() 164 | let components = calendar.components(.YearCalendarUnit | 165 | .MonthCalendarUnit | .DayCalendarUnit, fromDate: now) 166 | 167 | let startDate = calendar.dateFromComponents(components) 168 | 169 | let endDate = calendar.dateByAddingUnit(.DayCalendarUnit, 170 | value: 1, toDate: startDate, options: NSCalendarOptions(nil)) 171 | 172 | let sampleType = HKQuantityType.quantityTypeForIdentifier( 173 | HKQuantityTypeIdentifierDietaryEnergyConsumed) 174 | 175 | let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, 176 | endDate: endDate, options: .StrictStartDate) 177 | 178 | let query = HKStatisticsQuery(quantityType: sampleType, 179 | quantitySamplePredicate: predicate, 180 | options: .CumulativeSum) { query, result, error in 181 | 182 | if result != nil { 183 | completionHandler(nil, error) 184 | return 185 | } 186 | 187 | var totalCalories = 0.0 188 | 189 | if let quantity = result.sumQuantity() { 190 | let unit = HKUnit.jouleUnit() 191 | totalCalories = quantity.doubleValueForUnit(unit) 192 | } 193 | 194 | completionHandler(totalCalories, error) 195 | } 196 | 197 | healthStore.executeQuery(query) 198 | } 199 | ``` 200 | 201 | 注意,在根据时间来获取 HealthKit 的记录值时,往往存在着一个时区的问题。即 HealthKit 默认的是用的 UTC 时间来进行计数的,虽然它显示的是本地时间。而我们一般对时区进行本地化时,只是将 NSDate 的数值变化了,并不一定会修正它所在的时区,笔者用的方法所在时区还是 UTC。所以,可能需要先求出起始时间的 TimeInterval,然后统一用 UTC 时间计算: 202 | 203 | ```objective-c 204 | startDate = [[NSDate alloc] initWithTimeIntervalSinceNow:- [endDate timeIntervalSinceDate:startDate]]; 205 | 206 | endDate = [[NSDate alloc] init]; 207 | ``` 208 | 209 | # [CareKit](https://github.com/carekit-apple/CareKit) 210 | 211 | ![](https://github.com/carekit-apple/CareKit/wiki/AddedBinaries.png) 212 | -------------------------------------------------------------------------------- /移动应用/React Native/README.md: -------------------------------------------------------------------------------- 1 | # React Native 2 | 3 | 目前我们常用的移动端应用开发方式主要为原生方式、混合开发这两种,其中原生开发运行效率高,流畅,用户体验好,可以做各种复杂的动画效果。不过我们需要去掌握不同开发平台上特定的开发语言与内建的组件框架,譬如在 Android 开发中开发者需要掌握 Java,而 iOS 开发中开发者需要掌握 Objective-C 或者 Swift;并且由于平台之间的独立性,代码无法在其他平台上运行,无法做到跨平台。而传统混合开发方式则以 Cordova 与 Ionic 为代表,定义好原生功能与 Web 界面之间的协议,拦截特定的 URL Schema 进行原生功能的调用,应用则调用 Web 提供的 JavaScript 方法,将数据回传给 Web 界面。这种方式可以满足一套代码到处运行的目标,不过受限于 UIWebView 等容器本身的限制,其性能体验与原生应用不可同日而语。实际上无论哪一种开发方式都致力于解决如下几个问题:找到一种能达到或者接近原生体验的开发方式、找到一种一套代码能在各个平台上运行,达到代码复用的目的、能够以热更新或者类似的方式进行快速问题修复。 4 | 5 | 随着 React 在 Web 领域取得的巨大成功,Facebook 继续推出 React Native 以创建接近原生性能的跨平台移动应用,其倡导的 Learn Once,Write Anywhere 的概念同时兼顾了性能与快速迭代的需求。React 的核心设计理念其提供了抽象的、平台无关的组件定义范式,然后通过 react-dom 等库将其渲染到不同的承载体上;这些承载可以是服务端渲染中的字符串,或者客户端渲染中的 DOM 节点。在 React Native 中,我们只需要了解 React 组件定义规范与语法,然后利用 React Native 这个新的渲染库将界面渲染到原生界面组件中。在未来的客户端开发中,负责与用户交互以及存储这一部分建议采用原生的代码,而对于逻辑控制这边,建议是采用 JavaScript 方式实现。 6 | 7 | React Native 本质上是用 JSX 的语法风格编写原生的应用,它本质上还是跨平台编译性质的,并没有提供完整的类似于 WebView 那样的上下文,并且大量的 HTML 元素也是不可以直接应用的。React Native 只是借用了 HTML 的语法风格,并且提供了 JavaScript 与原生的桥接。React Native 使用了所谓的 Native Widget APIs 来调用底层的操作系统相关代码,并且处于性能的考虑它会异步批量地调用原生平台接口,其整体架构如下所示: 8 | 9 | ![](https://www.safaribooksonline.com/library/view/react-and-react/9781786465658/graphics/image_12_001.jpg) 10 | 11 | # 快速开始 12 | 13 | - Architecture(应用架构) 14 | 15 | 当使用 react-native 命令创建新的项目时,调用的即https://github.com/facebook/react-native/blob/master/react-native-cli/index.js这个脚本。当使用```react-native init HelloWorld```创建一个新的应用目录时,它会创建一个新的 HelloWorld 的文件夹,包含如下列表: 16 | 17 | > HelloWorld.xcodeproj/ 18 | > 19 | > Podfile 20 | > 21 | > iOS/ 22 | > 23 | > Android/ 24 | > 25 | > index.ios.js 26 | > 27 | > index.android.js 28 | > 29 | > node_modules/ 30 | > 31 | > package.json 32 | 33 | React Native 最大的卖点在于(1)可以使用 JavaScript 编写 iOS 或者 Android 原生程序。(2)应用可以运行在原生环境下并且提供流畅的 UI 与用户体验。众所周知,iOS 或者 Android 并不能直接运行 JavaScript 代码,而是依靠类似于 UIWebView 这样的原生组件去运行 JavaScript 代码,也就是传统的混合式应用。整个应用运行开始还是自原生开始,不过类似于 Objective-C/Java 这样的原生代码只是负责启动一个 WebView 容器,即没有浏览器界面的浏览器引擎。 34 | 35 | 而对于 React Native 而言,并不需要一个 WebView 容器去执行 Web 方面的代码,而是将所有的 JavaScript 代码运行在一个内嵌的 JavaScriptCore 容器实例中,并最终渲染为高级别的平台相关的组件。这里以 iOS 为例,打开 HelloWorld/AppDelegate.m 文件,可以看到如下的代码: 36 | 37 | ```objective-c 38 | ..................... 39 | RCTRootView *rootView = [[RCTRootView alloc] 40 | initWithBundleURL:jsCodeLocation 41 | moduleName:@"HelloWorld" 42 | launchOptions:launchOptions]; 43 | ..................... 44 | ``` 45 | 46 | AppDelegate.m 文件本身是 iOS 程序的入口,相信每一个有 iOS 开发经验的同学都不会陌生,这也是本地的 Objective-C 代码与 React Native 的 JavaScript 代码胶合的地方。而这种胶合的关键就是 RCTRootView 这个组件,可以从 React 声明的组件中加载到 Native 的组件。RCTRootView 组件是一个由 React Native 提供的原生的 Objective-C 类,可以读取 React 的 JavaScript 代码并且执行,除此之外,也允许我们从 JavaScript 代码中调用 iOS UI 的组件。 47 | 48 | 到这里我们可以看出,React Native 并没有将 JavaScript 代码编译转化为原生的 Objective-C 或者 Swift 代码,但是这些在 React 中创建的组件渲染的方式也非常类似于传统的 Objective-C 或者 Swift 创建的基于 UIKit 的组件,并不是类似于 WebView 中网页渲染的结果。 49 | 50 | 这种架构也就很好地解释了为什么可以动态加载我们的应用,当我们仅仅改变了 JS 代码而没有原生的代码改变的时候,不需要去重新编译。RCTRootView 组件会监听`Command+R`组合键然后重新执行 JavaScript 代码。 51 | 52 | - Virtual Dom 的扩展 53 | 54 | Virtual Dom 是 React 的核心机制之一,对于 Virtual Dom 的详细说明可以参考笔者 React 系列文章。在 React 组件被用于原生渲染之前,Clipboard 已经将 React 用于渲染到 HTML 的 Canvas 中,可以查看[render React to the HTML element](https://github.com/Flipboard/react-canvas)这篇文章。对于 React Web 而言,就是将 React 组件渲染为 DOM 节点,而对于 React Natively 而言,就是利用原生的接口把 React 组件渲染为原生的接口,其大概示意图可以如下: 55 | 56 | ![React Native behaves much like React, but can render to many different targets.](https://www.safaribooksonline.com/library/view/learning-react-native/9781491929049/assets/render-targets.png) 57 | 58 | 虽然 React 最初是以 Web 的形式呈现,但是 React 声明的组件可以通过*bridge*,即不同的桥接器转化器会将同样声明的组件转化为不同的具体的实现。React 在组件的 render 函数中返回具体的平台中应该如何去渲染这些组件。对于 React Native 而言,``这个组件会被转化为 iOS 中特定的`UIView`组件。 59 | 60 | - 载入 JavaScript 代码 61 | 62 | React Native 提供了非常方便的动态调试机制,具体的表现而言即是允许以一种类似于中间件服务器的方式动态的加载 JS 代码,即 63 | 64 | ```objective-c 65 | jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle"]; 66 | ``` 67 | 68 | 另一种发布环境下,可以将 JavaScript 代码打包编译,即`npm build`: 69 | 70 | ```objective-c 71 | jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 72 | ``` 73 | 74 | 如果在 Xcode 中直接运行程序会自动调用`npm start`命令来启动一个动态编译的服务器,如果没有自动启动可以手动的使用`npm start`命令,就如定义在 package.json 文件中的,它会启动 node_modules/react-native/packager/packager.sh 这个脚本。 75 | 76 | ### React Native 中的现代 JavaScript 代码 77 | 78 | 从上文中可以看出,React Native 中使用的是所谓的 JSX 以及大量的 ES6 的语法,在打包器打包之前需要将 JavaScript 代码进行一些转换。这是因为 iOS 与 Android 中的 JavaScript 解释器目前主要还是支持到了 ES5 版本,并不能完全识别 React Native 中提供的语法或者关键字。当然,并不是说我们不能使用 ES5 的语法去编写 React Native 程序,只是最新的一些语法细则规范可以辅助我们快速构建高可维护的应用程序。 79 | 80 | 譬如我们以 JSX 的语法编写了如下渲染函数: 81 | 82 | ```js 83 | render: function() { 84 | return ( 85 | 86 | 90 | 91 | Hello, {this.state.name}! 92 | 93 | To get started, edit index.ios.js 94 | 95 | 96 | Press Cmd+R to reload,{'\n'} 97 | Cmd+Control+Z for dev menu 98 | 99 | 100 | ); 101 | } 102 | ``` 103 | 104 | 在 JS 代码载入之前,React 打包器需要首先将 JSX 语法转化为 ES5 的表达式: 105 | 106 | ```js 107 | render: function() { 108 | return ( 109 | React.createElement(View, {style: styles.container}, 110 | React.createElement(TextInput, { 111 | style: styles.nameInput, 112 | onChange: this.onNameChanged, 113 | placeholder: "Who should be greeted?"}), 114 | React.createElement(Text, {style: styles.welcome}, 115 | "Hello, ", this.state.name, "!"), 116 | React.createElement(Text, {style: styles.instructions}, 117 | "To get started, edit index.ios.js" 118 | ), 119 | React.createElement(Text, {style: styles.instructions}, 120 | "Press Cmd+R to reload,", '\n', 121 | "Cmd+Control+Z for dev menu" 122 | ) 123 | ) 124 | ); 125 | } 126 | ``` 127 | 128 | 另一些比较常用的语法转换,一个是模块导入时候的结构器,即我们常常见到模块导入: 129 | 130 | ```js 131 | var React = require("react-native"); 132 | var { AppRegistry, StyleSheet, Text, TextInput, View } = React; 133 | ``` 134 | 135 | 上文中的用法即是所谓的解构赋值,一个简单的例子如下: 136 | 137 | ```js 138 | var fruits = { banana: "A banana", orange: "An orange", apple: "An apple" }; 139 | var { banana, orange, apple } = fruits; 140 | ``` 141 | 142 | 那么我们在某个组件中进行导出的时候,就可以用如下语法: 143 | 144 | ```js 145 | module.exports.displayName = "Name"; 146 | module.exports.Component = Component; 147 | ``` 148 | 149 | 而导入时,即是: 150 | 151 | ```js 152 | var { Component } = require("component.js"); 153 | ``` 154 | 155 | 另一个常用的 ES6 的语法即是所谓的 Arrow Function,这有点类似于 Lambda 表达式: 156 | 157 | ```js 158 | AppRegistry.registerComponent("HelloWorld", () => HelloWorld); 159 | ``` 160 | 161 | 会被转化为: 162 | 163 | ```js 164 | AppRegistry.registerComponent("HelloWorld", function () { 165 | return HelloWorld; 166 | }); 167 | ``` 168 | 169 | RN 需要一个 JS 的运行环境,在 IOS 上直接使用内置的 javascriptcore,在 Android 则使用 webkit.org 官方开源的 jsc.so。此外还集成了其他开源组件,如 fresco 图片组件,okhttp 网络组件等。 170 | 171 | RN 会把应用的 JS 代码(包括依赖的 framework)编译成一个 js 文件(一般命名为 index.android.bundle),, RN 的整体框架目标就是为了解释运行这个 js 脚本文件,如果是 js 扩展的 API,则直接通过 bridge 调用 native 方法; 如果是 UI 界面,则映射到 virtual DOM 这个虚拟的 JS 数据结构中,通过 bridge 传递到 native,然后根据数据属性设置各个对应的真实 native 的 View。bridge 是一种 JS 和 Java 代码通信的机制,用 bridge 函数传入对方 module 和 method 即可得到异步回调的结果。 172 | 173 | 对于 JS 开发者来说,画 UI 只需要画到 virtual DOM 中,不需要特别关心具体的平台, 还是原来的单线程开发,还是原来 HTML 组装 UI(JSX),还是原来的样式模型(部分兼容 )。RN 的界面处理除了实现 View 增删改查的接口之外,还自定义一套样式表达 CSSLayout,这套 CSSLayout 也是跨平台实现。RN 拥有画 UI 的跨平台能力,主要是加入 Virtual DOM 编程模型,该方法一方面可以照顾到 JS 开发者在 html DOM 的部分传承,让 JS 开发者可以用类似 DOM 编程模型就可以开发原生 APP,另一方面则可以让 Virtual DOM 适配实现到各个平台,实现跨平台的能力,并且为未来增加更多的想象空间,比如 react-cavas, react-openGL。而实际上 react-native 也是从 react-js 演变而来。 174 | 175 | 对于 Android 开发者来说,RN 是一个普通的安卓程序加上一堆事件响应,事件来源主要是 JS 的命令。主要有二个线程,UI main thread, JS thread。UI thread 创建一个 APP 的事件循环后,就挂在 looper 等待事件, 事件驱动各自的对象执行命令。JS thread 运行的脚本相当于底层数据采集器,不断上传数据,转化成 UI 事件,通过 bridge 转发到 UI thread, 从而改变真实的 View。后面再深一层发现,UI main thread 跟 JS thread 更像是 CS 模型,JS thread 更像服务端,UI main thread 是客户端,UI main thread 不断询问 JS thread 并且请求数据,如果数据有变,则更新 UI 界面。 176 | 177 | ![](https://unbug.gitbooks.io/react-native-training/content/21.jpg) 178 | 179 | ![](https://unbug.gitbooks.io/react-native-training/content/Pasted%20Graphic.jpg) 180 | 181 | # 利用 Create React Native App 快速创建 React Native 应用 182 | 183 | [Create React Native App](https://github.com/react-community/create-react-native-app) 是由 Facebook 与 [Expo](https://expo.io/) 联合开发的用于快速创建 React Native 应用的工具,其深受我们在前文介绍的 [Create React App](https://github.com/facebookincubator/create-react-app) 的影响。很多没有移动端开发经验的 Web 开发者在初次尝试 React Native 应用开发时可能会困扰于大量的原生依赖与开发环境,特别对于 Android 开发者而言。而 Create React Native App 则能够让用户在未安装 Xcode 或者 Android Studio 时,即使是在 Linux 或者 Windows 环境下也能开始 React Native 的开发与调试。这一点主要基于我们可以选择将应用运行在 Expo 的客户端应用内,该应用能够加载远端的纯粹的 JavaScript 代码而不用进行任何的原生代码编译操作。我们可以使用 NPM 快速安装命令行工具: 184 | 185 | ``` 186 | $ npm i -g create-react-native-app 187 | $ create-react-native-app my-project 188 | $ cd my-project 189 | $ npm start 190 | ``` 191 | 192 | 命令行中会输出如下界面,我们可以在 Expo 移动端应用中扫描二维码,即可以开始远程调试。我们也可以选择使用 Expo 的桌面端辅助开发工具 [XDE](https://github.com/exponent/xde),其内置了命令行工具与发布工具,同时支持使用内部模拟器: 193 | ![](https://docs.expo.io/bb256105106a86d5d9484892e82f94f3-quality=50&pngCompressionLevel=9&width=2128.png) 194 | 195 | 除此之外,Expo 还提供了 [Sketch](https://sketch.expo.io/Sk90tMVol) 这个在线编辑器,提供了组件拖拽、内建的 ESLint 等功能,允许开发者直接在网页中进行快速开发与共享,然后通过二维码在应用内预览。 196 | 197 | Expo 支持标准的 React Native 组件,目前已经内置了相机、视频、通讯录等等常用的系统 API,并且预置了 Airbnb react-native-maps、Facebook authentication 等优秀的工具库,未来也在逐步将常用的微信、百度地图等依赖作为预置纳入到 SDK 中。我们也可以使用 `npm run eject` 来将其恢复为类似于 `react-native init` 创建的包含原生代码的初始化项目,这样我们就能够自由地添加原生模块。我们也可以使用 Expo 提供的 `exp` 命令行将项目编译为独立可发布的应用。我们需要使用 `npm install -g exp` 安装命令行工具,然后配置 exp.json 文件: 198 | 199 | ``` 200 | { 201 | name: "Playground", 202 | icon: "https://s3.amazonaws.com/exp-us-standard/rnplay/app-icon.png", 203 | version: "2.0.0", 204 | slug: "rnplay", 205 | sdkVersion: "8.0.0", 206 | ios: { 207 | bundleIdentifier: "org.rnplay.exp", 208 | }, 209 | android: { 210 | package: "org.rnplay.exp", 211 | } 212 | } 213 | ``` 214 | 215 | 配置完毕之后在应用目录内使用 `exp start` 命令来启动 Expo 打包工具,然后选择使用 `exp build:android` 或者 `exp build:ios` 分别构建 Android 或者 iOS 独立应用。 216 | 217 | 除此之外,我们还可以使用 [PepperoniAppKit](https://github.com/futurice/pepperoni-app-kit),或者[Deco](https://www.decosoftware.com) 218 | 219 | # 开发第一个应用程序 220 | 221 | 在安装 React Native 开发环境时官方就推荐了 Flow 作为开发辅助工具,Flow 是一个用于静态类型检查的 JavaScript 的开发库。Flow 依赖于类型推导来检测代码中可能的类型错误,并且允许逐步向现存的项目中添加类型声明。如果需要使用 Flow,只需要用如下的命令: 222 | 223 | ``` 224 | flow check 225 | ``` 226 | 227 | 一般情况下默认的应用中都会包含一个*.flowconfig*文件,用于配置 Flow 的行为。如果不希望 flow 检查全部的文件,可以在*.flowconfig*文件中添加配置进行忽略: 228 | 229 | ``` 230 | [ignore] 231 | .*/node_modules/.* 232 | ``` 233 | 234 | 最终检查的时候就可以直接运行: 235 | 236 | ```shell 237 | $ flow check 238 | $ Found 0 errors. 239 | ``` 240 | 241 | React Native 支持使用 Jest 进行 React 组件的测试,Jest 是一个基于 Jasmine 的单元测试框架,它提供了自动的依赖 Mock,并且与 React 的测试工具协作顺利。 242 | 243 | ``` 244 | npm install jest-cli --save-dev 245 | ``` 246 | 247 | 可以将 test 脚本加入到 package.son 文件中: 248 | 249 | ```js 250 | { 251 | ... 252 | "scripts": { 253 | "test": "jest" 254 | } 255 | ... 256 | } 257 | ``` 258 | 259 | 直接使用*npm test*命令直接运行 jest 命令,下面可以创建 tests 文件夹,Jest 会递归搜索 tests 目录中的文件,这些测试文件中的代码如下: 260 | 261 | ```js 262 | "use strict"; 263 | 264 | describe("a silly test", function () { 265 | it("expects true to be true", function () { 266 | expect(true).toBe(true); 267 | }); 268 | }); 269 | ``` 270 | 271 | 而对于一些复杂的应用可以查看 React Native 的官方文档,以其中一个 getImageSource 为例: 272 | 273 | ```js 274 | ** 275 | * Taken from https://github.com/facebook/react-native/blob/master/Examples/Movies/__tests__/getImageSource-test.js 276 | */ 277 | 278 | 'use strict'; 279 | 280 | jest.dontMock('../getImageSource'); 281 | var getImageSource = require('../getImageSource'); 282 | 283 | describe('getImageSource', () => { 284 | it('returns null for invalid input', () => { 285 | expect(getImageSource().uri).toBe(null); 286 | }); 287 | ... 288 | }); 289 | ``` 290 | 291 | 因为 Jest 是默认自动 Mock 的,所以需要对待测试的方法设置 dontMock. 292 | -------------------------------------------------------------------------------- /移动应用/iOS/系统功能/系统进程.md: -------------------------------------------------------------------------------- 1 | # AOP(切面编程) 2 | 3 | ## [Aspects](https://github.com/steipete/Aspects) 4 | 5 | # MultipleThread 6 | 7 | ## Thread 8 | 9 | ### Pthreads 10 | 11 | POSIX 线程(POSIX threads),简称 Pthreads,是线程的 POSIX 标准。该标准定义了创建和操纵线程的一整套 API。在类 Unix 操作系统(Unix、Linux、Mac OS X 等)中,都使用 Pthreads 作为操作系统的线程。 12 | 13 | ```objective-c 14 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 15 | pthread_t thread; 16 | //创建一个线程并自动执行 17 | pthread_create(&thread, NULL, start, NULL); 18 | } 19 | void *start(void *data) { 20 | NSLog(@"%@", [NSThread currentThread]); 21 | return NULL; 22 | } 23 | ``` 24 | 25 | ### NSThread 26 | 27 | 这套方案是经过苹果封装后的,并且完全面向对象的。所以你可以直接操控线程对象,非常直观和方便。但是,它的生命周期还是需要我们手动管理,所以这套方案也是偶尔用用,比如 [NSThread currentThread],它可以获取当前线程类,你就可以知道当前线程的各种属性,用于调试十分方便。下面来看看它的一些用法。 28 | 29 | - 先创建线程类,再启动 30 | 31 | **OBJECTIVE-C** 32 | 33 | ```objective-c 34 | ` ``// 创建`` ``NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];`` ``// 启动`` ``[thread start];` 35 | ``` 36 | 37 | **SWIFT** 38 | 39 | ```swift 40 | ` ``//创建`` ``let thread = NSThread(target: self, selector: ``"run:"``, object: nil)`` ``//启动`` ``thread.start()` 41 | ``` 42 | 43 | - 创建并自动启动 44 | 45 | **OBJECTIVE-C** 46 | 47 | ```objective-c 48 | ` ``[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];` 49 | ``` 50 | 51 | **SWIFT** 52 | 53 | ```swift 54 | ` ``NSThread.detachNewThreadSelector(``"run:"``, toTarget: self, withObject: nil)` 55 | ``` 56 | 57 | #### 常见方法 58 | 59 | ```objective-c 60 | //取消线程 61 | - (void)cancel; 62 | //启动线程 63 | - (void)start; 64 | //判断某个线程的状态的属性 65 | @property (readonly, getter=isExecuting) BOOL executing; 66 | @property (readonly, getter=isFinished) BOOL finished; 67 | @property (readonly, getter=isCancelled) BOOL cancelled; 68 | //设置和获取线程名字 69 | -(void)setName:(NSString *)n; 70 | -(NSString *)name; 71 | //获取当前线程信息 72 | + (NSThread *)currentThread; 73 | //获取主线程信息 74 | + (NSThread *)mainThread; 75 | //使当前线程暂停一段时间,或者暂停到某个时刻 76 | + (void)sleepForTimeInterval:(NSTimeInterval)time; 77 | + (void)sleepUntilDate:(NSDate *)date; 78 | ``` 79 | 80 | #### 线程同步 81 | 82 | #### 线程跳转 83 | 84 | 我们都知道在其他线程操作完成后必须到主线程更新 UI。所以,我们来看看有哪些方法可以回到主线程。 85 | 86 | ## Asynchronous 87 | 88 | ### PromiseKit 89 | 90 | # Concurrence 91 | 92 | > - [iOS 开发中设计并发任务技术与注意事项 ](http://mp.weixin.qq.com/s?__biz=MjM5MjAwODM4MA==&mid=402877688&idx=1&sn=1e499ddb3ce9505e2515e279aea58aa0&scene=23&srcid=0312Qns1x9BTAzFPXGimoqx2#rd) 93 | 94 | ## GCD 95 | 96 | Grand Central Dispatch,听名字就霸气。它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的 CPU 内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理。 97 | 98 | ![blob.png](http://cc.cocimg.com/api/uploads/20150729/1438138129828276.png) 99 | 100 | ### 队列获取与创建 101 | 102 | - **主队列:特殊的串行队列** 103 | 104 | 这是一个特殊的 串行队列。什么是主队列,大家都知道吧,它用于刷新 UI,任何需要刷新 UI 的工作都要在主队列执行,所以一般耗时的任务都要放到别的线程执行。 105 | 106 | ```swift 107 | //OBJECTIVE-C 108 | dispatch_queue_t queue = dispatch_get_main_queue(); 109 | //SWIFT 110 | let queue = dispatch_get_main_queue() 111 | ``` 112 | 113 | - **全局并行队列** 114 | 115 | 这应该是唯一一个并行队列,只要是并行任务一般都加入到这个队列。 116 | 117 | ```swift 118 | //OBJECTIVE-C 119 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 120 | //SWIFT 121 | let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 122 | ``` 123 | 124 | - **自建队列** 125 | 126 | 自己可以创建 串行队列, 也可以创建 并行队列。看下面的代码(代码已更新),它有两个参数,第一个上面已经说了,第二个才是最重要的。第二个参数用来表示创建的队列是串行的还是并行的,传入 DISPATCH_QUEUE_SERIAL 或 NULL 表示创建串行队列。传入 DISPATCH_QUEUE_CONCURRENT 表示创建并行队列。 127 | 128 | ```swift 129 | //OBJECTIVE-C 130 | dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL); 131 | //SWIFT 132 | let queue = dispatch_queue_create("tk.bourne.testQueue", nil); 133 | ``` 134 | 135 | ### 任务 136 | 137 | - **同步任务**:不会新开线程 138 | 139 | ```swift 140 | //OJC 141 | dispatch_sync(, ^{ 142 | //code here 143 | NSLog(@"%@", [NSThread currentThread]); 144 | }); 145 | //Swift 146 | dispatch_sync(, { () -> Void in 147 | //code here 148 | println(NSThread.currentThread()) 149 | }) 150 | ``` 151 | 152 | - 异步任务:会新开线程 153 | 154 | ```swift 155 | //OJC 156 | dispatch_sync(, ^{ 157 | //code here 158 | NSLog(@"%@", [NSThread currentThread]); 159 | }); 160 | //Swift 161 | dispatch_sync(, { () -> Void in 162 | //code here 163 | println(NSThread.currentThread()) 164 | }) 165 | ``` 166 | 167 | ### [Async](https://github.com/duemunk/Async):GCD 的语法糖 168 | 169 | Cocoa 官方提供的这一套 Callback 方式的 GCD 的写法还是有些恶心,有人对其进行了一些封装,使得 GCD 的使用更加的方便,可以参考[Async](https://github.com/duemunk/Async/tree/feature/Swift_2.1): 170 | 171 | ```swift 172 | Async.background { 173 | println("This is run on the background queue") 174 | }.main { 175 | println("This is run on the main queue, after the previous block") 176 | } 177 | ``` 178 | 179 | 在 CocoaPods 中使用: 180 | 181 | ``` 182 | use_frameworks! 183 | pod "AsyncSwift" 184 | ``` 185 | 186 | 即可以引入 187 | (1)Supports the modern queue classes: 188 | 189 | ``` 190 | Async.main {} 191 | Async.userInteractive {} 192 | Async.userInitiated {} 193 | Async.utility {} 194 | Async.background {} 195 | ``` 196 | 197 | (2)Chain as many blocks as you want: 198 | 199 | ``` 200 | Async.userInitiated { 201 | // 1 202 | }.main { 203 | // 2 204 | }.background { 205 | // 3 206 | }.main { 207 | // 4 208 | } 209 | ``` 210 | 211 | (3)Store reference for later chaining: 212 | 213 | ``` 214 | let backgroundBlock = Async.background { 215 | print("This is run on the background queue") 216 | } 217 | 218 | // Run other code here... 219 | 220 | // Chain to reference 221 | backgroundBlock.main { 222 | print("This is run on the \(qos_class_self().description) (expected \(qos_class_main().description)), after the previous block") 223 | } 224 | ``` 225 | 226 | (4)Custom queues: 227 | 228 | ``` 229 | let customQueue = dispatch_queue_create("CustomQueueLabel", DISPATCH_QUEUE_CONCURRENT) 230 | let otherCustomQueue = dispatch_queue_create("OtherCustomQueueLabel", DISPATCH_QUEUE_CONCURRENT) 231 | Async.customQueue(customQueue) { 232 | print("Custom queue") 233 | }.customQueue(otherCustomQueue) { 234 | print("Other custom queue") 235 | } 236 | ``` 237 | 238 | (5)Dispatch block after delay: 239 | 240 | ``` 241 | let seconds = 0.5 242 | Async.main(after: seconds) { 243 | print("Is called after 0.5 seconds") 244 | }.background(after: 0.4) { 245 | print("At least 0.4 seconds after previous block, and 0.9 after Async code is called") 246 | } 247 | ``` 248 | 249 | (6)Cancel blocks that aren't already dispatched: 250 | 251 | ``` 252 | // Cancel blocks not yet dispatched 253 | let block1 = Async.background { 254 | // Heavy work 255 | for i in 0...1000 { 256 | print("A \(i)") 257 | } 258 | } 259 | let block2 = block1.background { 260 | print("B – shouldn't be reached, since cancelled") 261 | } 262 | Async.main { 263 | // Cancel async to allow block1 to begin 264 | block1.cancel() // First block is _not_ cancelled 265 | block2.cancel() // Second block _is_ cancelled 266 | } 267 | ``` 268 | 269 | (7)Wait for block to finish – an ease way to continue on current queue after background task: 270 | 271 | ``` 272 | let block = Async.background { 273 | // Do stuff 274 | } 275 | 276 | // Do other stuff 277 | 278 | block.wait() 279 | ``` 280 | 281 | #### AsyncGroup 282 | 283 | ``` 284 | Multiple dispatch blocks with GCD: 285 | 286 | let group = AsyncGroup() 287 | group.background { 288 | // Run on background queue 289 | } 290 | group.utility { 291 | // Run on utility queue, in parallel to the previous block 292 | } 293 | group.wait() 294 | 295 | All modern queue classes: 296 | 297 | group.main {} 298 | group.userInteractive {} 299 | group.userInitiated {} 300 | group.utility {} 301 | group.background {} 302 | 303 | Custom queues: 304 | 305 | let customQueue = dispatch_queue_create("Label", DISPATCH_QUEUE_CONCURRENT) 306 | group.customQueue(customQueue) {} 307 | 308 | Wait for group to finish: 309 | 310 | let group = AsyncGroup() 311 | group.background { 312 | // Do stuff 313 | } 314 | group.background { 315 | // Do other stuff in parallel 316 | } 317 | // Wait for both to finish 318 | group.wait() 319 | // Do rest of stuff 320 | 321 | Custom asynchronous operations: 322 | 323 | let group = AsyncGroup() 324 | group.enter() 325 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { 326 | // Do stuff 327 | group.leave() 328 | } 329 | group.enter() 330 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { 331 | // Do other stuff in parallel 332 | group.leave() 333 | } 334 | // Wait for both to finish 335 | group.wait() 336 | // Do rest of stuff 337 | ``` 338 | 339 | ### 队列组 340 | 341 | 队列组可以将很多队列添加到一个组里,这样做的好处是,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。下面是使用方法,这是一个很实用的功能。 342 | 343 | ```objective-c 344 | //1.创建队列组 345 | dispatch_group_t group = dispatch_group_create(); 346 | //2.创建队列 347 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 348 | //3.多次使用队列组的方法执行任务, 只有异步方法 349 | //3.1.执行3次循环 350 | dispatch_group_async(group, queue, ^{ 351 | for (NSInteger i = 0; i < 3; i++) { 352 | NSLog(@"group-01 - %@", [NSThread currentThread]); 353 | } 354 | }); 355 | //3.2.主队列执行8次循环 356 | dispatch_group_async(group, dispatch_get_main_queue(), ^{ 357 | for (NSInteger i = 0; i < 8; i++) { 358 | NSLog(@"group-02 - %@", [NSThread currentThread]); 359 | } 360 | }); 361 | //3.3.执行5次循环 362 | dispatch_group_async(group, queue, ^{ 363 | for (NSInteger i = 0; i < 5; i++) { 364 | NSLog(@"group-03 - %@", [NSThread currentThread]); 365 | } 366 | }); 367 | //4.都完成后会自动通知 368 | dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 369 | NSLog(@"完成 - %@", [NSThread currentThread]); 370 | }); 371 | ``` 372 | 373 | ## NSOperation 374 | 375 | GCD 是基于 c 的底层 api,NSOperation 属于 object-c 类。ios 首先引入的是 NSOperation,IOS4 之后引入了 GCD 和 NSOperationQueue 并且其内部是用 gcd 实现的。相对于 GCD: 376 | 377 | 1,NSOperation 拥有更多的函数可用,具体查看 api。 378 | 379 | 2,在 NSOperationQueue 中,可以建立各个 NSOperation 之间的依赖关系。 380 | 381 | 3,有 kvo,可以监测 operation 是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)。 382 | 383 | 4,NSOperationQueue 可以方便的管理并发、NSOperation 之间的优先级。 384 | 385 | NSOperation 只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperation 和 NSBlockOperation。创建一个 Operation 后,需要调用 start 方法来启动任务,它会 默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其 cancel 方法即可。不过因为 NSInvocationOperation 不是类型安全的,其在 Swift 中已经被弃用。 386 | 387 | #### 创建任务 388 | 389 | ```objective-c 390 | //1.创建NSBlockOperation对象 391 | NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ 392 | NSLog(@"%@", [NSThread currentThread]); 393 | }]; 394 | //2.开始任务 395 | [operation start]; 396 | ``` 397 | 398 | 之前说过这样的任务,默认会在当前线程执行。但是 NSBlockOperation 还有一个方法:addExecutionBlock:,通过这个方法可以给 Operation 添加多个执行 Block。这样 Operation 中的任务 会并发执行,它会 在主线程和其它的多个线程 执行这些任务,注意下面的打印结果: 399 | 400 | ```objective-c 401 | //1.创建NSBlockOperation对象 402 | NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ 403 | NSLog(@"%@", [NSThread currentThread]); 404 | }]; 405 | //添加多个Block 406 | for (NSInteger i = 0; i < 5; i++) { 407 | [operation addExecutionBlock:^{ 408 | NSLog(@"第%ld次:%@", i, [NSThread currentThread]); 409 | }]; 410 | } 411 | //2.开始任务 412 | [operation start]; 413 | ``` 414 | 415 | 打印结果: 416 | 417 | ```objective-c 418 | 2015-07-28 17:50:16.585 test[17527:4095467] 第2次 -{number = 1, name = main} 419 | 2015-07-28 17:50:16.585 test[17527:4095666] 第1次 -{number = 4, name = (``null``)} 420 | 2015-07-28 17:50:16.585 test[17527:4095665]{number = 3, name = (``null``)} 421 | 2015-07-28 17:50:16.585 test[17527:4095662] 第0次 -{number = 2, name = (``null``)} 422 | 2015-07-28 17:50:16.586 test[17527:4095666] 第3次 -{number = 4, name = (``null``)} 423 | 2015-07-28 17:50:16.586 test[17527:4095467] 第4次 -{number = 1, name = main} 424 | ``` 425 | 426 | #### 创建队列 427 | 428 | 看过上面的内容就知道,我们可以调用一个 NSOperation 对象的 start() 方法来启动这个任务,但是这样做他们默认是 同步执行 的。就算是 addExecutionBlock 方法,也会在 当前线程和其他线程 中执行,也就是说还是会占用当前线程。这是就要用到队列 NSOperationQueue 了。而且,按类型来说的话一共有两种类型:主队列、其他队列。只要添加到队列,会自动调用任务的 start() 方法。 429 | 430 | - 主线程 431 | 432 | ```objective-c 433 | //OBJECTIVE-C 434 | NSOperationQueue *queue = [NSOperationQueue mainQueue]; 435 | //SWIFT 436 | let queue = NSOperationQueue.mainQueue() 437 | ``` 438 | 439 | - 其他队列 440 | 441 | 因为主队列比较特殊,所以会单独有一个类方法来获得主队列。那么通过初始化产生的队列就是其他队列了,因为只有这两种队列,除了主队列,其他队列就不需要名字了。注意:其他队列的任务会在其他线程并行执行。 442 | 443 | ```objective-c 444 | //1.创建一个其他队列 445 | NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 446 | //2.创建NSBlockOperation对象 447 | NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ 448 | NSLog(@"%@", [NSThread currentThread]); 449 | }]; 450 | //3.添加多个Block 451 | for (NSInteger i = 0; i < 5; i++) { 452 | [operation addExecutionBlock:^{ 453 | NSLog(@"第%ld次:%@", i, [NSThread currentThread]); 454 | }]; 455 | } 456 | //4.队列添加任务 457 | [queue addOperation:operation]; 458 | ``` 459 | 460 | #### 任务依赖 461 | 462 | NSOperation 有一个非常实用的功能,那就是添加依赖。比如有 3 个任务:A: 从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就可以用到依赖了: 463 | 464 | ```objective-c 465 | //1.任务一:下载图片 466 | NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{ 467 | NSLog(@"下载图片 - %@", [NSThread currentThread]); 468 | [NSThread sleepForTimeInterval:1.0]; 469 | }]; 470 | //2.任务二:打水印 471 | NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{ 472 | NSLog(@"打水印 - %@", [NSThread currentThread]); 473 | [NSThread sleepForTimeInterval:1.0]; 474 | }]; 475 | //3.任务三:上传图片 476 | NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{ 477 | NSLog(@"上传图片 - %@", [NSThread currentThread]); 478 | [NSThread sleepForTimeInterval:1.0]; 479 | }]; 480 | //4.设置依赖 481 | [operation2 addDependency:operation1]; //任务二依赖任务一 482 | [operation3 addDependency:operation2]; //任务三依赖任务二 483 | //5.创建队列并加入任务 484 | NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 485 | [queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO]; 486 | ``` 487 | -------------------------------------------------------------------------------- /移动应用/Android/界面开发/界面开发基础.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | ## Material Design 4 | 5 | Material Design 是由 Google 提出的关于如何构建一个 Android 应用的完整的指导方案,该方案不仅仅可以被用于 Android 应用的设计,同样可以被用于 Web 端的设计。目前在 Web 端上已经出现了大量践行 Material Design 的开源的组件库。在开发个 APP 的过程种,Android 提供了多个辅助库来帮助开发者实践这些设计指南。其中最重要的几个库就是: 6 | 7 | - com.android.support:appcompat-v7:23.1.0 8 | 9 | * com.android.support:design :23.1.0 10 | 11 | 总而言之,如果使用 Android Studio 来开发应用的话,这两个库会被自动作为依赖引入项目中。在开发应用时,一个重要的方面就是应用的颜色模式的选择,而 Material Design 的原则中也阐述了如何来选择颜色。 12 | 13 | ### ShowCase 14 | 15 | #### [plaid](https://github.com/nickbutcher/plaid) 16 | 17 | ![](https://github.com/nickbutcher/plaid/raw/master/screenshots/plaid_demo.giff) 18 | 19 | # Screen & Size 20 | 21 | > 参考资料 22 | > 23 | > - [详解 Android 开发中常用的 DPI / DP / SP][1] 24 | 25 | ## DIP(Device Independent Pixels,dp) 26 | 27 | | 概念 | 解释 | 28 | | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 29 | | 屏幕尺寸 Screen Size | 即显示屏幕的实际大小,按照屏幕的对角线进行测量。为简单起见,Android 把所有的屏幕大小分为四种尺寸:小,普通,大,超大(分别对应:small, normal, large, and extra large)。应用程序可以为这四种尺寸分别提供不同的自定义屏幕布局-平台将根据屏幕实际尺寸选择对应布局进行渲染,这种选择对于程序侧是透明的 | 30 | | 屏幕长宽比 sAspect ratio | 长宽比是屏幕的物理宽度与物理高度的比例关系。应用程序可以通过使用限定的资源来为指定的长宽比提供屏幕布局资源, | 31 | | 屏幕分辨率 Resolution | 在屏幕上显示的物理像素总和。需要注意的是:尽管分辨率通常用宽 x 高表示,但分辨率并不意味着具体的屏幕长宽比。在 Andorid 系统中,应用程序不直接使用分辨率, | 32 | | 密度 Density | 根据像素分辨率,在屏幕指定物理宽高范围内能显示的像素数量。在同样的宽高区域,低密度的显示屏能显示的像素较少,而高密度的显示屏则能显示更多的像素。屏幕密度非常重要,因为其它条件不变的情况下,一共宽高固定的 UI 组件(比如一个按钮)在在低密度的显示屏上显得很大,而在高密度显示屏上看起来就很小, | 33 | | 密度无关的像素( DIP ) | 指一个抽象意义上的像素,程序用它来定义界面元素。它作为一个与实际密度无关的单位,帮助程序员构建一个布局方案(界面元素的宽度,高度,位置)。一个与密度无关的像素,在逻辑尺寸上,与一个位于像素密度为 160DPI 的屏幕上的像素是一致的,这也是 Android 平台所假定的默认显示设备。在运行的时候,平台会以目标屏幕的密度作为基准,“透明地”处理所 有需要的 DIP 缩放操作。要把密度无关像素转换为屏幕像素,可以用这样一个简单的公式: pixels = dips \* (density / 160)。举个例子,在 DPI 为 240 的屏幕上,1 个 DIP 等于 1.5 个物理像素。我们强烈推荐你用 DIP 来定义你程序的界面布局,因为这样可以保证你的 UI 在各种分辨率的屏幕上都可以正常显示 | 34 | 35 | > 尺寸获取 36 | 37 | ```java 38 | #获取基本属性信息 39 | DisplayMetrics metric = new DisplayMetrics(); 40 | getWindowManager().getDefaultDisplay().getMetrics(metric); 41 | int width = metric.widthPixels; // 屏幕宽度(像素) 42 | int height = metric.heightPixels; // 屏幕高度(像素) 43 | float density = metric.density; // 屏幕密度(0.75 / 1.0 / 1.5) 44 | int densityDpi = metric.densityDpi; // 屏幕密度DPI(120 / 160 / 240) 45 | // 获取屏幕密度(方法1) 46 | int screenWidth = getWindowManager().getDefaultDisplay().getWidth(); // 屏幕宽(像素,如:480px) 47 | int screenHeight = getWindowManager().getDefaultDisplay().getHeight(); // 屏幕高(像素,如:800p) 48 | Log.e(TAG + " getDefaultDisplay", "screenWidth=" + screenWidth + "; screenHeight=" + screenHeight); 49 | // 获取屏幕密度(方法2) 50 | DisplayMetrics dm = new DisplayMetrics(); 51 | dm = getResources().getDisplayMetrics(); 52 | float density = dm.density; // 屏幕密度(像素比例:0.75/1.0/1.5/2.0) 53 | int densityDPI = dm.densityDpi; // 屏幕密度(每寸像素:120/160/240/320) 54 | float xdpi = dm.xdpi; 55 | float ydpi = dm.ydpi; 56 | Log.e(TAG + " DisplayMetrics", "xdpi=" + xdpi + "; ydpi=" + ydpi); 57 | Log.e(TAG + " DisplayMetrics", "density=" + density + "; densityDPI=" + densityDPI); 58 | screenWidth = dm.widthPixels; // 屏幕宽(像素,如:480px) 59 | screenHeight = dm.heightPixels; // 屏幕高(像素,如:800px) 60 | Log.e(TAG + " DisplayMetrics(111)", "screenWidth=" + screenWidth + "; screenHeight=" + screenHeight); 61 | // 获取屏幕密度(方法3) 62 | dm = new DisplayMetrics(); 63 | getWindowManager().getDefaultDisplay().getMetrics(dm); 64 | density = dm.density; // 屏幕密度(像素比例:0.75/1.0/1.5/2.0) 65 | densityDPI = dm.densityDpi; // 屏幕密度(每寸像素:120/160/240/320) 66 | xdpi = dm.xdpi; 67 | ydpi = dm.ydpi; 68 | Log.e(TAG + " DisplayMetrics", "xdpi=" + xdpi + "; ydpi=" + ydpi); 69 | Log.e(TAG + " DisplayMetrics", "density=" + density + "; densityDPI=" + densityDPI); 70 | int screenWidthDip = dm.widthPixels; // 屏幕宽(dip,如:320dip) 71 | int screenHeightDip = dm.heightPixels; // 屏幕宽(dip,如:533dip) 72 | Log.e(TAG + " DisplayMetrics(222)", "screenWidthDip=" + screenWidthDip + "; screenHeightDip=" + screenHeightDip); 73 | screenWidth = (int)(dm.widthPixels * density + 0.5f); // 屏幕宽(px,如:480px) 74 | screenHeight = (int)(dm.heightPixels * density + 0.5f); // 屏幕高(px,如:800px) 75 | Log.e(TAG + " DisplayMetrics(222)", "screenWidth=" + screenWidth + "; screenHeight=" + screenHeight); 76 | ``` 77 | 78 | ## Components Size(组件尺寸) 79 | 80 | ### StatusBar 81 | 82 | Android 中经常需要获取到状态栏的高度来判断屏幕的尺寸,常用的获取 StatusBar 的代码为: 83 | 84 | ```js 85 | Rect rectgle= new Rect(); 86 | Window window= getWindow(); 87 | window.getDecorView().getWindowVisibleDisplayFrame(rectgle); 88 | int StatusBarHeight= rectgle.top; 89 | int contentViewTop= 90 | window.findViewById(Window.ID_ANDROID_CONTENT).getTop(); 91 | int TitleBarHeight= contentViewTop - StatusBarHeight; 92 | 93 | Log.i("*** Jorgesys::", "StatusBar Height= " + StatusBarHeight + ", TitleBar Height = " + TitleBarHeight); 94 | ``` 95 | 96 | ``` 97 | 而如果在Activity的onCreate函数中需要获取Status的高度,则为如下方法: 98 | ``` 99 | 100 | ```js 101 | public int getStatusBarHeight() { 102 | int result = 0; 103 | int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); 104 | if (resourceId > 0) { 105 | result = getResources().getDimensionPixelSize(resourceId); 106 | } 107 | return result; 108 | } 109 | ``` 110 | 111 | # Style & Themes 112 | 113 | ## Theme 114 | 115 | - Hoho Theme 116 | 117 | 在 4.0 之前 Android 可以说是没有设计可言的,在 4.0 之后推出了 Android Design,从此 Android 在设计上有了很大的改善,而在程序实现上相应的就是 Holo 风格,所以你看到有类似 Theme.Holo.Light、Theme.Holo.Light.DarkActionBar 就是 4.0 的设计风格,但是为了让 4.0 之前的版本也能有这种风格怎么办呢?这个时候就不得不引用 v7 包了,所以对应的就有 Theme.AppCompat.Light、Theme.AppCompat.Light.DarkActionBar,如果你的程序最小支持的版本是 4.0,那么可以不用考虑 v7 的兼容,所以在目前来看,我个人建议不用考虑兼容。 118 | 119 | - Material Design Theme 120 | 121 | 今年的 5.0 版本,Android 推出了 Material Design 的概念,这是在设计上 Android 的又一大突破。对应的程序实现上就有 Theme.Material.Light、Theme.Material.Light.DarkActionBar 等,但是这种风格只能应用在在 5.0 版本的手机,如果在 5.0 之前应用 Material Design 该怎么办呢?同样的引用 appcompat-v7 包,这个时候的 Theme.AppCompat.Light、Theme.AppCompat.Light.DarkActionBar 就是想对应兼容的 Material Design 的 Theme。 122 | 123 | compile ‘com.android.support:appcompat-v7:21.0.3’ 中的 21 代表 API level 21 推出的兼容包,所以如果你引用的是 21 之前的版本,则默认这些 Theme.AppCompat.Light 是 Holo 风格的,从 21 开始的版本默认是 Material 风格 124 | 125 | # View Mechanism 126 | 127 | > 参考资料 128 | > 129 | > - [Android 应用层 View 回执流程与源码分析][2] 130 | 131 | # Layout 132 | 133 | ## LayoutInflater 134 | 135 | > **参考文章** 136 | > 137 | > - [Android LayoutInflater 原理分析,带你一步步深入了解 View 系列][1] > [深入理解 LayoutInflater.inflate()](http://blog.chengdazhi.com/index.php/110) 138 | 139 | ### inflate 140 | 141 | 如果 attachToRoot 是 true 的话,那第一个参数的 layout 文件就会被填充并附加在第二个参数所指定的 ViewGroup 内。方法返回结合后的 View,根元素是第二个参数 ViewGroup。如果是 false 的话,第一个参数所指定的 layout 文件会被填充并作为 View 返回。这个 View 的根元素就是 layout 文件的根元素。不管是 true 还是 false,都需要 ViewGroup 的 LayoutParams 来正确的测量与放置 layout 文件所产生的 View 对象。 142 | 143 | #### attachToRoot 为 True 144 | 145 | (1)子元素尺寸依赖于 ViewGroup 146 | 假设我们在 XML layout 文件中写了一个 Button 并指定了宽高为 match_parent。 147 | 148 | ``` 149 | 154 | 155 | ``` 156 | 157 | 现在我们想动态地把这个按钮添加进 Fragment 或 Activity 的 LinearLayout 中。如果这里 LinearLayout 已经是一个成员变量 mLinearLayout 了,我们只需要通过如下代码达成目标: 158 | 159 | ``` 160 | inflater.inflate(R.layout.custom_button, mLinearLayout, true); 161 | 162 | ``` 163 | 164 | 我们指定了用于填充 button 的 layout 资源文件,然后我们告诉 LayoutInflater 我们想把 button 添加到 mLinearLayout 中。这里 Button 的 LayoutParams 种类为 LinearLayout.LayoutParams。 165 | 166 | 下面的代码也有同样的效果。LayoutInflater 的两个参数的 inflate()方法自动将 attachToRoot 设置为 true。 167 | 168 | ``` 169 | inflater.inflate(R.layout.custom_button, mLinearLayout); 170 | 171 | ``` 172 | 173 | (2)自定义 View 174 | 我们看一个 layout 文件中根元素有标签的例子。标签标识着这个 layout 文件的根 ViewGroup 可以有多种类型。 175 | 176 | ``` 177 | public class MyCustomView extends LinearLayout { 178 | ... 179 | private void init() { 180 | LayoutInflater inflater = LayoutInflater.from(getContext()); 181 | inflater.inflate(R.layout.view_with_merge_tag, this); 182 | } 183 | } 184 | 185 | ``` 186 | 187 | 这就是一个很好的使用 attachToRoot 的例子。这个例子中 layout 文件没有 ViewGroup 作为根元素,所以我们指定我们自定义的 LinearLayout 作为根元素。如果 layout 文件有一个 FrameLayout 作为根元素而不是,那么 FrameLayout 和它的子元素都可以正常填充,而后都会被添加到 LinearLayout 中,LinearLayout 是根 ViewGroup,包含着 FrameLayout 和其子元素。 188 | 189 | #### attachToRoot 为 False 190 | 191 | (1)使用 addView 手动添加 192 | 193 | ``` 194 | Button button = (Button) inflater.inflate(R.layout.custom_button, mLinearLayout, false); 195 | mLinearLayout.addView(button); 196 | ``` 197 | 198 | 这两行代码与刚才 attachToRoot 为 true 时的一行代码等效。通过传入 false,我们告诉 LayoutInflater 我们不暂时还想将 View 添加到根元素 ViewGroup 中,意思是我们一会儿再添加。在这个例子中,一会儿再添加就是在 inflate()后调用 addView()方 法。 199 | (2)RecyclerView 子元素 200 | 每一个 RecyclerView 的子元素都要在 attachToRoot 设置为 false 的情况下填充。这里子 View 在 onCreateViewHolder()中填充。 201 | 202 | ``` 203 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 204 | LayoutInflater inflater = LayoutInflater.from(getActivity()); 205 | View view = inflater.inflate(android.R.layout.list_item_recyclerView, parent, false); 206 | return new ViewHolder(view); 207 | } 208 | 209 | ``` 210 | 211 | RecyclerView 负责决定什么时候展示它的子 View,这个不由我们决定。在任何我们不负责将 View 添加进 ViewGroup 的情况下都应该将 attachToRoot 设置为 false。 212 | (3)Fragment 213 | 当在 Fragment 的 onCreateView()方法中填充并返回 View 时,要将 attachToRoot 设为 false。如果传入 true,会抛出 IllegalStateException,因为指定的子 View 已经有父 View 了。你需要指定在哪里将 Fragment 的 View 放进 Activity 里,而添加、移除或替换 Fragment 则是 FragmentManager 的事情。 214 | 215 | ``` 216 | FragmentManager fragmentManager = getSupportFragmentManager(); 217 | Fragment fragment = fragmentManager.findFragmentById(R.id.root_viewGroup); 218 | 219 | if (fragment == null) { 220 | fragment = new MainFragment(); 221 | fragmentManager.beginTransaction().add(R.id.root_viewGroup, fragment).commit(); 222 | } 223 | 224 | ``` 225 | 226 | 上面代码中 root_viewGroup 就是 Activity 中用于放置 Fragment 的容器,它会作为 inflate()方法中的第二个参数被传入 onCreateView()中。它也是你在 inflate()方法中传入的 ViewGroup。FragmentManager 会将 Fragment 的 View 添加到 ViewGroup 中,你可不想添加两次。 227 | 228 | ``` 229 | public View onCreateView(LayoutInflater inflater, ViewGroup parentViewGroup, Bundle savedInstanceState) { 230 | View view = inflater.inflate(R.layout.fragment_layout, parentViewGroup, false); 231 | … 232 | return view; 233 | } 234 | 235 | ``` 236 | 237 | 具体使用时候要注意: 238 | 239 | - 如果可以传入 ViewGroup 作为根元素,那就传入它。但是避免将 null 作为根 ViewGroup 传入。 240 | - 自定义 View 时很适合将 attachToRoot 设置为 true,但是不要在 View 已经被添加进 ViewGroup 时传入 true。 241 | 242 | # Widgets 243 | 244 | ## Attribute 245 | 246 | ### ID 247 | 248 | 有时候在 Android Studio 里面设置动态的 ID 会报错,可以通过创建资源的方式: 249 | 250 | create folder res/values/ids.xmland 251 | 252 | ```xml 253 | 254 | 255 | 256 | 257 | 258 | ``` 259 | 260 | in Activity class call like this 261 | 262 | ```java 263 | ImageView refreshImg = new ImageView(activity); 264 | ImageView settingsImg = new ImageView(activity); 265 | refreshImg.setId(R.id.refresh); 266 | settingsImg .setId(R.id.settings); 267 | ``` 268 | 269 | ### Size 270 | 271 | 常用的控件layout_width、layout_height、width、height这几种。而layout_*这一系列的布局只有用于被layout包裹起来的情况下才会起作用。譬如: 272 | 273 | ```xml 274 | 279 | ``` 280 | 281 |     这个控件如果直接使用 inflate 方法转化成 View 的话它其中设置的 layout_width 与 layout_height 是不会起作用的。而在 Activity 中,有时候直接在 activity_main 中设置单一控件即可,这是因为 Activity 会自动在最外层包裹一个 FrameLayout: 282 | 283 | ![Activity Framework包裹图][2] 284 | 285 | [1]: http://www.jianshu.com/p/913943d25829 286 | [2]: http://blog.csdn.net/yanbober/article/details/46128379?utm_source=www.race604.com 287 | -------------------------------------------------------------------------------- /移动应用/React Native/语法基础/界面开发.md: -------------------------------------------------------------------------------- 1 | # Styles 2 | 3 | - [react-native-css](https://github.com/sabeurthabti/react-native-css) 4 | 5 | 在讲解 React Native 的样式之前,需要先介绍下 FaceBook 本身推荐的模式,它强烈的推荐重用带样式的组件而不是样式本身。并且 React Native 是不支持样式继承的,如它在文档中所述: 6 | 7 | > 你并没有被允许去为整个节点树设置一个默认的譬如字体这样的属性,推荐的方式是通过创建一个带有一致的字体样式的组件并且在整个应用中通过重用组件的方式来完成样式的统一与继承。 8 | 9 | 在 Web 的 React 开发中,往往会使用独立的样式文件,譬如 CSS、SASS 或者 LESS 样式表来存储声明的样式。但是 React Native 使用了一套截然不同的机制,所有的样式必须要用 JavaScript 进行编写(当然,后面也会提到有第三方库可以帮你自动编译 SASS 到 JS 中),并且强制规定所有的样式必须显性引用到组件的 Style 对象中。毫无疑问,这种方式会银帆很多不良的反应,毕竟它与目前流行的基于 CSS 的样式方式相悖。 10 | 传统的独立的 CSS 毫无疑问存在着一些问题,譬如所有的 CSS 的规则与类名都是处于全局作用域中,也就是说虽然只打算为某个组件添加样式也是有可能无意间影响到其他的组件。譬如如果你引入了 Twitter 最新的 BootStrap 的样式库,等于你引入了 600 多个全局变量。因为 CSS 没有显式地关联到 HTML 元素,也就给冗余或者无效代码地消除带来了很大的困难。 11 | 12 | ## Declaring & Manipulating Styles 13 | 14 | 类似于 SASS 或者 LESS 这样的 CSS 预处理器也在尝试着解决 CSS 中不好的部分,但是还是有很多根本性的问题遗留下来。React Native 实现了部分可用的 CSS 的样式,专注于保证样式的 API 较少但是可用度高。譬如 Position 相关的属性就差别很大,此外,React Native 中也不支持伪类、动画与选择器。所有支持的属性可以查看[这里](https://facebook.github.io/react-native/docs/view.html#style)。 15 | 16 | ### Inline Styles(内联样式) 17 | 18 | 内联样式是句法上最简单的使用方法,不过很明显并不是最佳的使用方法,其语法形式类似于 React Web: 19 | 20 | ``` 21 | 22 | The quick brown fox 23 | jumped over the lazy dog. 24 | 25 | ``` 26 | 27 | 内联样式可以让你快速地实验,不过,这种方式还是要尽可能地避免,毕竟这种方式会非常的低效。内联样式对象可能在每次渲染的时候被重新创建。即使你有可能在 Props 或者 State 发生变化时修正样式值,也要避免使用内联样式。 28 | 29 | ### Styling With Objects 30 | 31 | 对于内联样式最简单的修正就是将这些样式对象提取出来,作为单独的 JS 对象: 32 | 33 | ```js 34 | var italic = { 35 | fontStyle: 'italic' 36 | }; 37 | var bold = { 38 | fontWeight: 'bold' 39 | }; 40 | 41 | ... 42 | 43 | render() { 44 | return ( 45 | 46 | The quick brown fox 47 | jumped over the lazy dog. 48 | 49 | ); 50 | } 51 | ``` 52 | 53 | 不过,ReactNative 官方是推荐我们使用`StyleSheet.create`来创建一个样式对象,而不是仅仅使用简单的 JS 的朴素对象。`StyleSheet.create`是可选的一种方式但是能够提供很多的好处,它通过将样式对象转化为一个内部表的引用来保证了值是不可变对象并且是不透明的。将它放置到文件末尾,可以保证在整个应用的生命周期中只会创建一次而不是每次渲染的时候都会重新创建。由此可见,`StyleSheet.create`是一点语法糖,同时,也方便了你进行 Prop 的类型的验证。通过`StyleSheet.create`创建的样式对象可以用 View.propTypes.Style 与 Text.propTypes.Style 类型进行验证。 54 | 55 | ### Style Concatenation 56 | 57 | 虽然 React 强调了重用带样式的组件而不是样式本身,但是很多时候不可避免的会重用样式。譬如,如果你已经定义了一个叫 button 的样式和一个叫 accentText 的样式,但是需要将它们合并到一个叫 AccentButton 的组件中,最初定义的样式如下所示: 58 | 59 | ``` 60 | var styles = Stylesheet.create({ 61 | button: { 62 | borderRadius: '8px', 63 | backgroundColor: '#99CCFF' 64 | }, 65 | accentText: { 66 | fontSize: '18px', 67 | fontWeight: 'bold' 68 | } 69 | }); 70 | ``` 71 | 72 | 然后希望创建一个对象同时用这两个属性: 73 | 74 | ``` 75 | var AccentButton = React.createClass({ 76 | render: function() { 77 | return ( 78 | 79 | {this.props.children} 80 | 81 | ); 82 | } 83 | }); 84 | ``` 85 | 86 | 可以看出,这个 style 属性可以接收一个数组作为对象,也可以将自定义的内联样式放进去: 87 | 88 | ``` 89 | var AccentButton = React.createClass({ 90 | render: function() { 91 | return ( 92 | 93 | {this.props.children} 94 | 95 | ); 96 | } 97 | }); 98 | ``` 99 | 100 | 如果存在冲突的话,就是为一个属性定义了两个值的时候,React Native 会自动解决该冲突,它会自动选择数组中最右边的定义作为有效定义,并且错误值,也就是 false,null,undefined 这些值会被忽略。这个特性可以被用于处理条件样式,譬如,如果有个`