diff --git "a/androidweekly/Kotlin for Android (II)\345\210\233\345\273\272\344\270\200\344\270\252\345\267\245\347\250\213/readme.md" "b/androidweekly/Kotlin for Android (II)\345\210\233\345\273\272\344\270\200\344\270\252\345\267\245\347\250\213/readme.md" new file mode 100644 index 00000000..11d64f52 --- /dev/null +++ "b/androidweekly/Kotlin for Android (II)\345\210\233\345\273\272\344\270\200\344\270\252\345\267\245\347\250\213/readme.md" @@ -0,0 +1,142 @@ +Kotlin for Android (II)创建一个工程 +--- + +> +* 原文标题 : Kotlin for Android (II): Create a new project +* 原文链接 : [Kotlin for Android (II): Create a new project](http://antonioleiva.com/kotlin-android-create-project/) +* 译者 : [Lollypo](https://github.com/Lollypo) +* 校对者: [chaossss](https://github.com/chaossss) +* 状态 : 完成 + + +当我从[what Kotlin is and what it can do for us](http://antonioleiva.com/kotlin-for-android-introduction/)获得一些启发之后,觉得是时候配置下 Android Studio来帮助我们使用Kotlin开发Android应用程序了. 其中有些步骤只需要在初次使用时完成一次, 但是其他一些Gradle配置需要为每一个新项目做一遍. + +对于本系列文章, 我将创建一个我早些时候创建的[Bandhook](https://play.google.com/store/apps/details?id=com.limecreativelabs.bandhook)的简化版本, 它基本上就是连接到一个基于RESTful的音乐API然后接收一些乐队的信息. 链接到 [Bandhook Kotlin on Github](https://github.com/antoniolg/Bandhook-Kotlin) 查看源代码. + + +###创建一个新项目然后下载Kotlin插件### + +就像你平常做的那样,我们只需要用Android Studio创建一个带Activity的基本Android项目。 + +一旦完成,我们需要做的第一件事就是去下载Kotlin插件. 去到Android Studio的系统设置中然后查找plugins.之后,再次使用搜索找到Kotlin插件,安装并重启IDE。 + +![kotlin-plugin](http://7xi8kj.com1.z0.glb.clouddn.com/kotlin-plugin-e1424632570741.png) + +###添加Kotlin插件的依赖到的应用程序的build.gradle中### + +该项目的build.gradle需要添加一个新的依赖,这个依赖将会被Kotlin插件要求以在主Module中使用: +```gradle +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.1.3' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.11.91' + } +} +``` + + + +###配置Module的build.grade### + +首先, 应用Kotlin插件: +```gradle +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +``` +接着, 添加Kotlin库到你的依赖: +```gradle +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'org.jetbrains.kotlin:kotlin-stdlib:0.11.91' +} +``` +最后, 你需要添加我们在下一个步骤创建的Kotlin文件夹: +```gradle +android { + compileSdkVersion 22 + buildToolsVersion "22.0.0" + + ... + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } +} +``` +或者,你可以跳过这一步,当做完下一个步骤时,使用这个Android Studio的操作: + +![configure-kotlin-project](http://7xi8kj.com1.z0.glb.clouddn.com/configure-kotlin-project.png) + +我更倾向于手动去做以保持我的Gradle文件有整洁有序, 但第二个选项可能较为容易些。 + + + +###创建Kotlin文件夹### + +如果你将项目的视图从‘Android’转到‘Project’,那将会非常容易。依次选择‘app->src->main’ 然后创建一个名为 ‘kotlin'的文件夹: + +![kotlin-folder](http://7xi8kj.com1.z0.glb.clouddn.com/kotlin-folder.png) + + + +###将Java activity转换成Kotlin文件### + +Kotlin插件能将Java转换为Kotlin类. 我们可以轻松的通过‘Code’菜单中的‘Convert Java File to Kotlin File'选项转换当前的Activity到Kotlin类 : + +![convert-java-to-kotlin](http://7xi8kj.com1.z0.glb.clouddn.com/convert-java-to-kotlin-e1424633562637.png) + +IDE将建议你移动新文件到Kotlin文件夹,点击‘Move File’(或者手动完成,假如你没看到这个选项). +```java +public class MainActivity : ActionBarActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + } + + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + val id = item.getItemId() + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true + } + + return super.onOptionsItemSelected(item) + } +} +``` + + + +###主要区别### + +看一看之前的代码, 我们可以看到一些明显的差异。 其中很大一部分我们将会在下一篇文章讲解到: + +- 使用冒号,而不是'extends'。 +- 显式使用‘override': 在Java中, 我们可以使用一个注释使我们的代码更清晰,但它不是必要条件. Kotlin将迫使我们使用它. +- 函数则使用‘fun’关键字: Kotlin是一个面向对象的函数式语言, 因此可能会与其他语言类似,例如Scala. Java方法被函数的形式表示。 +- 函数参数命名规则不同: 类型和名称都写在相反的位置,并用冒号隔开。 +- 分号可选: 我们不需要在行的结尾处加上分号。如果我们想要也可以加上, 但如果我们不这样做,它就可以节省大量的时间,并使我们的代码整洁。 +- 其他小细节: 在简介一文中, 我已经说到了 ‘?’ 的意义. 这表明参数可以为空。NULL的处理方式不同于Java。 + + + +###总结### + +也许我们会认为使用一门新语言将会非常困难, Kotlin被JetBrains团队开发出来的,要成为最容易和可交互的语言用来覆盖那些Java的不足之处。由于Android Studio也是基于JetBrains的产品,这将让集成到这个IDE中并且开始工作非常简单。 + +下一篇文章将介绍一些让我们在使用Kotlin开发Android应用程序时,能让开发过程更简单的奇巧淫技。 diff --git "a/androidweekly/Square \345\274\200\346\272\220\345\272\223Flow\345\222\214Mortar\347\232\204\344\273\213\347\273\215/readme.md" "b/androidweekly/Square \345\274\200\346\272\220\345\272\223Flow\345\222\214Mortar\347\232\204\344\273\213\347\273\215/readme.md" index 5eb8384a..3e9eb5ed 100644 --- "a/androidweekly/Square \345\274\200\346\272\220\345\272\223Flow\345\222\214Mortar\347\232\204\344\273\213\347\273\215/readme.md" +++ "b/androidweekly/Square \345\274\200\346\272\220\345\272\223Flow\345\222\214Mortar\347\232\204\344\273\213\347\273\215/readme.md" @@ -57,7 +57,7 @@ Flow 将一个应用分成一个逻辑上的 Screen组合,Screen不是任何 我们应用中的每一个Activity将会成为一个 Flow 对象,Flow对象在返回栈中保存了 Screen 的记录,和 Activity 或者 FragmentManager 的返回栈有些类似,通过这样的设计允许我们在 Screen 之间通过简单地实例化就可以轻松的切换,而不需要在应用中包含很多Activity。这里有一小部分 Activity(最好是一个)来持有这些 Screen。他们之间的关系下图类似: ![screen](http://www.bignerdranch.com/img/blog/2015/02/screen.png) -我们我们想切换到一个新的 Screen,我们只需简单地实例化这个 Screen,并且告诉我们 Flow 对象帮助我们切换为这个 Screen。除此以外,正如我们所期待的,Flow 被实例化后也会实现 goBack() 和 goUp() 方法。然而,许多开发者都把 Java 中的 goto 语句看作洪水猛兽,但事实上 Java 中的 goto 语句并没有它听起来那么恐怖。 +如果我们想切换到一个新的 Screen,我们只需简单地实例化这个 Screen,并且告诉我们 Flow 对象帮助我们切换为这个 Screen。除此以外,正如我们所期待的,Flow 被实例化后也会实现 goBack() 和 goUp() 方法。然而,许多开发者都把 Java 中的 goto 语句看作洪水猛兽,但事实上 Java 中的 goto 语句并没有它听起来那么恐怖。 ![flow](http://www.bignerdranch.com/img/blog/2015/02/flow.png) 从本质上看,Flow 的作用仅仅是在 App 中告诉我们将要切换到哪一个 Screen。而这样设计的好处在于,Flow 通过这样的设计让我们能够方便地在我们定义的各种不同的自定义 View 中切换,并使我们免受在 Activity 或 Fragment 需要考虑的种种麻烦,让我们把注意力都集中在处理 View上。Flow 为我们创造了一个简单,方便,以 View 为中心的应用架构。 @@ -84,13 +84,6 @@ Presenter 是一个拥有简单生命周期和伴随其生命周期的 Bundle 完全没有 Fragment 那样复杂的生命周期,这可不是我吹的! -There are a lot of moving parts and new terms and classes and all sorts of room for confusion. So in sum, we have the following pieces of the puzzle: - -- Screen: A particular location in the application’s navigation hierarchy -- Blueprint: A section of an application with its own Dagger module -- Presenter: A View-controller object -- Custom Views: Views defined by Java and usually some XML - 文章写到这里,你会发现在 Flow 和 Mortar 中有许多发生改变的部分,新的术语和类,还有新的使用规范,这难免会让人一头雾水。所以为了方便大家的理解,总的来说,我们需要重视的是下面几个部分: - Screen: 在应用导航层次结构中的一个特殊存在,用来代表我们视图的对象 @@ -104,8 +97,6 @@ Here’s what our final Mortar and Flow architecture looks like: ![](https://www.bignerdranch.com/img/blog/2015/02/mortar-and-flow.png) -Instead of sticking with Model View Controller, the architecture has morphed into more of a Model View Presenter style. The big difference concerns the handling of runtime configuration changes like rotation. In MVC, our Controller (Activities and Fragments) will be destroyed alongside our Views, whereas in MVP, only our View will be destroyed and recreated. Nifty. - 抛弃了对 MVC 模式的执念,这个架构在完成之后变得更像 MVP 模式。这样巨大的转变使得新的架构需要关注如何处理应用在运行时配置信息改变的问题,例如:旋转。在 MVC 模式中,我们的控制器(Activity 和 Fragment)会随着我们的 View 一起被杀死。然而,在 MVP 模式中,我们只有 View 被杀死,又在需要它的时候重现它。挺有趣的对吧? diff --git "a/others/FaceBook\346\216\250\345\207\272\347\232\204Android\345\233\276\347\211\207\345\212\240\350\275\275\345\272\223-Fresco/readme.md" "b/others/FaceBook\346\216\250\345\207\272\347\232\204Android\345\233\276\347\211\207\345\212\240\350\275\275\345\272\223-Fresco/readme.md" new file mode 100644 index 00000000..2a8eba4c --- /dev/null +++ "b/others/FaceBook\346\216\250\345\207\272\347\232\204Android\345\233\276\347\211\207\345\212\240\350\275\275\345\272\223-Fresco/readme.md" @@ -0,0 +1,131 @@ +FaceBook推出的Android图片加载库-Fresco +--- + +> +* 原文链接:[Introducing Fresco: A new image library for Android](https://code.facebook.com/posts/366199913563917/introducing-fresco-a-new-image-library-for-android/) +* 译者 : [ZhaoKaiQiang](https://github.com/ZhaoKaiQiang) +* 校对者: [Chaossss](https://github.com/chaossss) +* 校对者: [bboyfeiyu](https://github.com/bboyfeiyu) +* 校对者: [BillionWang](https://github.com/BillionWang) +* 状态 : 完成 + +在Android设备上面,快速高效的显示图片是极为重要的。过去的几年里,我们在如何高效的存储图像这方面遇到了很多问题。图片太大,但是手机的内存却很小。每一个像素的R、G、B和alpha通道总共要占用4byte的空间。如果手机的屏幕是480*800,那么一张屏幕大小的图片就要占用1.5M的内存。手机的内存通常很小,特别是Android设备还要给各个应用分配内存。在某些设备上,分给Facebook App的内存仅仅有16MB。一张图片就要占据其内存的十分之一。 + +当你的App内存溢出会发生什么呢?它当然会崩溃!我们开发了一个库来解决这个问题,我们叫它Fresco。它可以管理使用到的图片和内存,从此App不再崩溃。 + +##内存区 +为了理解Facebook到底做了什么工作,在此之前我们需要了解在Android可以使用的堆内存之间的区别。Android中每个App的Java堆内存大小都是被严格的限制的。每个对象都是使用Java的new在堆内存实例化,这是内存中相对安全的一块区域。内存有垃圾回收机制,所以当App不在使用内存的时候,系统就会自动把这块内存回收。 + +不幸的是,内存进行垃圾回收的过程正是问题所在。当内存进行垃圾回收时,内存不仅仅进行了垃圾回收,还把 Android 应用完全终止了。这也是用户在使用 App 时最常见的卡顿或短暂假死的原因之一。这会让正在使用 App 的用户非常郁闷,然后他们可能会焦躁地滑动屏幕或者点击按钮,但 App 唯一的响应就是:在 App 恢复正常之前,请求用户耐心等待 + +相比之下,Native堆是由C++程序的new进行分配的。在Native堆里面有更多可用内存,App只被设备的物理可用内存限制,而且没有垃圾回收机制或其他东西拖后腿。但是c++程序员必须自己回收所分配的每一块内存,否则就会造成内存泄露,最终导致程序崩溃。 + +Android有另外一种内存区域,叫做Ashmem。它操作起来更像Native堆,但是也有额外的系统调用。Android 在操作 Ashmem 堆时,会把该堆中存有数据的内存区域从 Ashmem 堆中抽取出来,而不是把它释放掉,这是一种弱内存释放模式;被抽取出来的这部分内存只有当系统真正需要更多的内存时(系统内存不够用)才会被释放。当 Android 把被抽取出来的这部分内存放回 Ashmem 堆,只要被抽取的内存空间没有被释放,之前的数据就会恢复到相应的位置。 + +##可消除的Bitmap +Ashmem不能被Java应用直接处理,但是也有一些例外,图片就是其中之一。当你创建一张没有经过压缩的Bitmap的时候,Android的API允许你指定是否是可清除的。 + +``` +BitmapFactory.Options = new BitmapFactory.Options(); +options.inPurgeable = true; +Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options); +``` +经过上面的代码处理后,可清除的Bitmap会驻留在 Ashmem 堆中。不管发生什么,垃圾回收器都不会自动回收这些 Bitmap。当 Android 绘制系统在渲染这些图片,Android 的系统库就会把这些 Bitmap 从 Ashmem 堆中抽取出来,而当渲染结束后,这些 Bitmap 又会被放回到原来的位置。如果一个被抽取的图片需要再绘制一次,系统仅仅需要把它再解码一次,这个操作非常迅速。 + +这听起来像一个完美的解决方案,但是问题是Bitmap解码的操作是运行在UI线程的。Bitmap解码是非常消耗CPU资源的,当消耗过大时会引起UI阻塞。因为这个原因,所以Google不推荐使用这个[特性](http://developer.android.com/intl/zh-cn/reference/android/graphics/BitmapFactory.Options.html#inPurgeable)。现在它们推荐使用另外一个特性——inBitmap。但是这个特性直到Android3.0之后才被支持。即使是这样,这个特性也不是非常有用,除非 App 里的所有图片大小都相同,这对Fackbook来说显然是不适用的。一直到4.4版本,这个限制才被移除了。但我们需要的是能够运行在 Android 2.3 - 最新版本中的通用解决方案。 + +##自力更生 +对于上面提到的“解码操作致使 UI 假死”的问题,我们找到了一种同时使 UI 显示和内存管理都表现良好的解决方法。如果我们在 UI 线程进行渲染之前把被抽取的内存区域放回到原来的位置,并确保它再也不会被抽取,那我们就可以把这些图片放在 Ashmem 里,同时不会出现 UI 假死的问题。幸运的是,Android 的 NDK 中有一个函数可以完美地实现这个需求,名字叫做 AndroidBitmap_lockPixels。这个函数最初的目的就是:在调用 unlockPixels 再次抽取内存区域后被执行。 + +当我们意识到我们没有必要这样做的时候,我们取得了突破。如果我们只调用lockPixels而不调用对应的unlockPixels,那么我们就可以在Java的堆内存里面创建一个内存安全的图像,并且不会导致UI线程加载缓慢。只需要几行c++代码,我们就完美的解决了这个问题。 + +##用C++的思想写Java代码 +就像《蜘蛛侠》里面说的:“能力越强,责任越大。”可清除的 Bitmap 既不会被垃圾回收器回收,也不会被 Ashmem 内置的清除机制处理,这使得使用它们可能会造成内存泄露。所以我们只能靠自己啦。 + +在c++中,通常的解决方案是建立智能指针类,实现引用计数。这些需要利用到c++的语言特性——拷贝构造函数、赋值操作符和确定的析构函数。这种语法在Java之中不存在,因为垃圾回收器能够处理这一切。所以我们必须以某种方式在Java中实现C++的这些保证机制。 + +我们创建了两个类去完成这件事。其中一个叫做“SharedReference”,它有addReference和deleteReference两个方法,调用者调用时必须采取基类对象或让它在范围之外。一旦引用计数器归零,资源处理(Bitmap.recycle)就会发生。 + +然而,很显然,让Java开发者去调用这些方法是很容易出错的。Java语言就是为了避免做这样的事情的!所以SharedReference之上,我们构建了CloseableReference类。它不仅实现了Java的Closeable接口,而且也实现了Cloneable接口。它的构造器和clone()方法会调用addReference(),而close()方法会调用deleteReference()。所以Java开发者需要遵守下面两条简单的的规则: + +1. 在分配CloseableReference新对象的时候,调用.clone()。 +2. 在超出作用域范围的时候,调用.close(),这通常是在finally代码块中。 + +这些规则可以有效地防止内存泄漏,并让我们在像Fackbook的Android客户端这种大型的Java程序中享受Native内存管理和通信。 + +##不仅仅是加载程序,它是一个管道 +在移动设备上显示图片需要很多的步骤: +![](http://i2.tietuku.com/4480c88a0d8004bf.png) +几个优秀的开源库都是按照这个顺序执行的,比如 Picasso,Universal Image Loader,Glide和 Volley等等。上面这些开源库为Android的发展做出了非常重要的贡献。我们相信Fresco在几个重要方面会表现的更好。 + +我们的不同之处在于把上面的这些步骤看作是管道,而不仅仅是加载器。每一个步骤和其他方面应该是尽可能独立的,把数据和参数传递进去,然后产生一个输出,就这么简单。它应该可以做一些操作,不管是并行还是串行。一些操作只能在特性条件下才能执行。一些有特殊要求的在线程上执行。除此之外,当我们考虑改进图像的时候,所有的图片就会变得非常复杂。很多人在低网速情况下使用Facebook,我们想要这些人能够尽快的看到图片,甚至经常是在图片没有完全下载完之前。 + +##不要烦恼,拥抱stream +在Java中,异步代码历来都是通过Future机制来执行的。在另外的线程里面代码被提交执行,然后一个类似Future的对象可以检查执行的结果是不是已经完成了。但是,这只在假设只有一种结果的情况下行得通。在处理渐进的图像的时候,我们希望可以完整而且连续的显示结果。 + +我们的解决方式是定义一个更广义的Future版本,叫做DataSource。它提供了一个订阅方法,调用者必须传入一个DataSubscriber和Executor。DataSubscriber可以从DataSource获取到处理中和处理完毕的结果,并且提供了很简单的方法来区分。因为我们需要非常频繁的处理这些对象,所以必须有一个明确的close调用,幸运的是,DataSource本身就是Closeable。 + +在后台,每一个箱子上面都实现了一个叫做“生产者/消费者”的新框架。在这个问题是,我们是从[ReactiveX](http://reactivex.io/)获取的灵感。我们的系统拥有和[RxJava](https://github.com/ReactiveX/RxJava)相似的接口,但是更加适合移动设备,并且有内置的对Closeables的支持。 + +保持简单的接口。Producer只有一个叫做produceResults的方法,这个方法需要一个Consumer对象。反过来,Consumer有一个onNewResult方法。 + +我们使用像这样的系统把Producer联系起来。假设我们有一个producer的工作是把类型I转化为类型O,那么它看起来应该是这个样子: + +``` +public class OutputProducer implements Producer { + + private final Producer mInputProducer; + + public OutputProducer(Producer inputProducer) { + this.mInputProducer = inputProducer; + } + + public void produceResults(Consumer outputConsumer, ProducerContext context) { + Consumer inputConsumer = new InputConsumer(outputConsumer); + mInputProducer.produceResults(inputConsumer, context); + } + + private static class InputConsumer implements Consumer { + private final Consumer mOutputConsumer; + + public InputConsumer(Consumer outputConsumer) { + mOutputConsumer = outputConsumer; + } + + public void onNewResult(I newResult, boolean isLast) { + O output = doActualWork(newResult); + mOutputConsumer.onNewResult(output, isLast); + } + } +} +``` + +这可以使我们把非常复杂的步骤串起来,同时也可以保持他们逻辑的独立性。 + +##动画全覆盖 +使用Facebook的人都非常喜欢Stickers,因为它可以以动画形式存储GIF和Web格式。如果支持这些格式,就需要面临新的挑战。因为每一个动画都是由不止一张图片组成的,你需要解码每一张图片,存储在内存里,然后显示出来。对于大一点的动画,把每一帧图片放在内存是不可行的。 + +我们建立了AnimatedDrawable,一个强大的可以呈现动画的Drawable,同时支持GIF和WebP格式。AnimatedDrawable实现标准的Android Animatable接口,所以调用者可以随意的启动或者停止动画。为了优化内存使用,如果图片足够小的时候,我们就在内存里面缓存这些图片,但是如果太大,我们可以迅速的解码这些图片。这些行为调用者是完全可控的。 + +所有的后台都用c++代码实现。我们保持一份解码数据和元数据解析,如宽度和高度。我们引用技术数据,它允许多个Java端的Drawables同时访问一个WebP图像。 + +##如何去爱你?我来告诉你... +当一张图片从网络上下载下来之后,我们想显示一张占位图。如果下载失败了,我们就会显示一个错误标志。当图片加载完之后,我们有一个渐变动画。通过使用硬件加速,我们可以按比例放缩,或者是矩阵变换成我们想要的大小然后渲染。我们不总是按照图片的中心进行放缩,那么我们可以自己定义放缩的聚焦点。有些时候,我们想显示圆角甚至是圆形的图片。所有的这些操作都应该是迅速而平滑的。 + +我们之前的实现是使用Android的View对象——时机到了,可以使用ImageView替换出占位的View。这个操作是非常慢的。改变View会让Android强制刷新整个布局,当用户滑动的时候,这绝对不是你想看到的效果。比较明智的做法是使用Android的Drawables,它可以迅速的被替换。 + +所以我们创建了Drawee。这是一个像MVC架构的图片显示框架。该模型被称为DraweeHierarchy。它被实现为Drawables的一个层,对于底层的图像而言,每一个曾都有特定的功能——成像、层叠、渐变或者是放缩。 + +DraweeControllers通过管道的方式连接到图像上——或者是其他的图片加载库——并且处理后台的图片操作。他们从管道接收事件并决定如何处理他们。他们控制DraweeHierarchy实际上的操作——无论是占位图片,错误条件或是完成的图片。 + +DraweeViews 的功能不多,但都是至关重要的。他们监听Android的View不再显示在屏幕上的系统事件。当图片离开屏幕的时候,DraweeView可以告诉DraweeController关闭使用的图像资源。这可以避免内存泄露。此外,如果它已经不在屏幕范围内的话,控制器会告诉图片管道取消网络请求。因此,像Fackbook那样滚动一长串的图片的时候,不会频繁的网络请求。 + +通过这些努力,显示图片的辛苦操作一去不复返了。调用代码只需要实例化一个DraweeView,然后指定一个URI和其他可选的参数就可以了。剩下的一切都会自动完成。开发人员不需要担心管理图像内存,或更新图像流。Fresco为他们把一切都做了。 + +##Fresco +完成这个图像显示和操作复杂的工具库之后,我们想要把它分享到Android开发者社区。我们很高兴的宣布,从今天起,这个项目已经作为[开源代码](http://github.com/facebook/fresco)了! + +壁画是绘画技术,几个世纪以来一直受到世界各地人们的欢迎。我们许多伟大的艺术家使用这种名字,从意大利文艺复兴时期的大师拉斐尔到壁画艺术家斯里兰卡。我们并不是假装达到这个伟大的水平,我们真的希望Android开发者能像我们当初享受创建这个开源库的过程一样,非常享受的使用它。 + +##更多 +[Fresco中文文档](http://fresco-cn.org/) \ No newline at end of file