Android RocooFix 热修复框架[1]

这里我要讲述下android热补丁前世今生


Under the Hood: Rebuilding Facebook for Android

发布者: Frank Qixing DU · 发布时间: 2012年12月14日上午 3:01

Over the last year, we've been retooling our mobile apps to make them faster, more reliable, and easier to use. Several months ago, we embarked on a major step change for iOS and shipped a native re-write of Facebook for iOS. With Android, we've moved to a fixed-date release cycle and have been steadily making improvements over time so that Facebook performs consistently across multiple platforms.  

 

Today, we're releasing a new version of Facebook for Android that's been rebuilt in native code to improve speed and performance. To support the unique complexity of Facebook stories across devices, we're moving from a hybrid native/webview to pure native code, allowing us to optimize the Facebook experience for faster loading, new user interfaces, disk cache, and so on.

 

We rebuilt several of Facebook for Android's core features in native code, including news feed and Timeline, to create a faster experience whether you're opening the app, looking at photos, or interacting with friends. Now, you can comment and like a story more quickly, and photo loading is optimized to be much faster. We also built a new, automatically updated story banner to bubble up the newest stories, no matter where you are in news feed.

 

We found that we had to develop new solutions and abstractions in order to address several performance challenges along the way.

 

Performance Challenges 

a) Reducing Garbage Collection: Memory efficiency has a dramatic effect on UI smoothness. Inefficient memory usage will result in many garbage collection (GC) events, which in turn can pause the application for multiple frames and cause a visible stutter during scrolling. We focused specifically on minimizing, eliminating, or deferring allocations in performance-critical code. We also deferred performing allocation-heavy code (like feed story parsing) until scrolling stopped and moved them off the UI thread.

 

b) Writing a Custom Event Bus: An event bus allows for a publish/subscribe flow, which allows communication between classes that should not be aware of, or dependent on, each other's existence. Common event bus implementations use object iterators and reflection,  which cause long operations and a lot of memory allocations. To solve this, we implemented a light-weight event bus that avoids all reflection and object iterators so it can be registered and unregistered during performance-critical areas of code like scrolling.

 

c) Moving Photos to the Native Heap: Loading photos is memory-intensive, and efficiently loading and handling bitmaps can be challenging. The standard method of allocating them directly on the Java heap can result in significant GCs and out-of-memory errors. We moved our bitmaps to be loaded on the native heap using the inPurgeable flag in the BitmapFactory class.  This allowed photos to be allocated in native memory instead of the Java heap (Honeycomb and up) or in external memory tracked by the VM (Froyo/Gingerbread), which in turn significantly reduced their impact on our allocations and thus performance.

 

d) Writing a Custom ListView Recycler: View recycling speeds up scrolling performance. The stock Android ListView has view recycling support, but it is not efficient for list elements of very different row heights, such as in news feed stories. We wrote a custom view recycler, which detached heavy content views once they were added to the recycling heap.  We also recycled substories in more complicated aggregated feed stories.

 

Looking Ahead

This new release creates a solid foundation for the Facebook for Android app moving forward. The infrastructure in place will let us continue to make the app even faster, smoother, and feature-rich. We hope you enjoy the new Facebook for Android app and use it to celebrate memorable moments, share your stories, and enjoy the holidays with your family and friends.



显示更多心情
评论
139条评论
评论
Mai Smallfish
写评论…
按 Enter 键发布。
Chris Schmitz
Chris Schmitz  This is madness. Instead of admitting you had simply too many methods to manage (I can't imagine what a mess the code must look like), you blindly moved forward, hacking away until you were lucky enough to find a way to make it work. Instead of a vic...展开 查看翻译
18条回复
Flo Poworotznik
Flo Poworotznik "Facebook is one of the most feature-rich apps available for Android." Not to be disrespectful, but a glorified RSS reader with an embedded instant messenger does not even make the top 30 of apps on my devices when it comes to feature-richness. And if you cannot fit THAT into the constraints of the Android platform I am not sure what to make of your compentence in software engineering. 查看翻译
Justin McManus
Justin McManus  @Chris Why should they refactor exactly? What are you basing your opinion on? What insider information about their app do you have that we don't? I'm sure the LinearAlloc buffer size was increased for good reason in newer versions of android, and Faceb...展开 查看翻译
9条回复
Christopher Smith
Christopher Smith Honestly, the Facebook app on my Nexus 4, with modern Jelly Bean is still more painful and less useful than the web interface. I think perhaps the problem with your app is more severe than you realize. 查看翻译
Frank Ritter
Frank Ritter Despite your valuable efforts, Facebook for Android is still a relatively bad app, UI wise and regarding battery consumption. Please work on the app's kernel wakelocks and get rid of this utterly useless GPS checking on app start. 查看翻译
3条回复
Supun Kamburugamuve
Supun Kamburugamuve I also agree with Chris Schmitz. I can understand Facebook developers hacking the JVM code for getting this fixed as a quick solution. But bosting about it as they've done something amazing is completely different thing. 查看翻译
Artur Termenji
Artur Termenji "I don't usually develop Android apps, but when I do, I hack the Dalvik". This is totally insane. 查看翻译
Alex Tomic
Alex Tomic The thing which is most interesting about this is not the hack itself, but, the fact that Facebook appears to be *proud* of such a hack! 查看翻译
Isaac Sigurd Veidt
Isaac Sigurd Veidt Your app is too complex, big and unstable for what it delivers. Reading this I'd even argue you should start from scratch. 查看翻译
Joe Pineapple Simpson
Joe Pineapple Simpson Facebook, fire your "android" team. I could do a better job single handedly and I'm 18. 查看翻译
5条回复
Toni Willberg
Toni Willberg So instead of fixing your application you decided to break the platform it runs on? 查看翻译
Luis Santos del Val
Luis Santos del Val Way too many classes and methods. There are multiple reasons why you *should not* do this in Android. This also explains why the news feed scroll is so sluggish (probably too many method calls when recycling views).

Slightly longer methods are a bette ... 展开 查看翻译
Punsr.com
Punsr.com sounds like you have a bloat on that app guys, it doesn't do that much. Try coding a p2p client with search, bittorrent support, media playback, streaming, location. Stop bragging and underestimating what others have built, you're only making a joke of your bad coding practices. For the love of god.... 查看翻译
Dan Buchal
Dan Buchal Facebook's app is flat out horrible and you guys should be ashamed. When is contact sync coming?... You know that other apps have had for years now? The Google+ app makes Facebook's look like a middle school student project 查看翻译
4条回复
Karl Koscher
Karl Koscher So many things to say about this, but I think I can sum it up with: WAT. ( http://24.media.tumblr.com/tumblr_lfm5b0esis1qfzkwzo1_500...) 查看翻译
Bruce Williams
Bruce Williams That's very cool - and a little scary that your app is allowed to muck with the VM internals that way. 查看翻译
Michael Borsuk
Michael Borsuk Chris Schmitz so the more methods the worse the code must look? How many methods is too many? And there's no possible design flaws in Android that might require a native code hack like this?

"It's now running on hundreds of different phone models, and we have yet to find one where it doesn't work." 查看翻译
8条回复
Joseph Lee
Joseph Lee So Facebook never heard of using the NDK. It would've commonized code with iOS without all of these Dalvik acrobatics. 查看翻译
2条回复
Mihai Ciuperca
Mihai Ciuperca ok. it's time to uninstall 1 app from my phone.... 查看翻译
Stefan Müller-Stock
Stefan Müller-Stock  You will all burn in hell for this :) 查看翻译
Scott Weber
Scott Weber So I get up this morning, and my Facebook app on my Galaxy Tablet is gone, but there is a little triangle named "False".
I go to the application manager to see what "False" is, and I see that it is 100's of meg, and has rights to do everything... Like  ... 展开 查看翻译
Shawky Masry
Shawky Masry that was only with dalvik, I can't imagine the mess with ART now I can understand why it crash on Installation, maybe you need just to have multiple versions with an installer app. hope it will get fixed soon as for now no Facebook on ART. 查看翻译
Alex Lockwood
Alex Lockwood can someone please explain to me why the Google+ app works flawlessly on all of my Android devices (2.2 and above), but the Facebook app just never seems to work? even the newer native release comes nowhere close. 查看翻译
Max Roeleveld
Max Roeleveld Well, that neatly explains the abysmal performance and stability of the FB app on Android, especially on less-than-stellar devices. It's held together by hacks and duct tape... 查看翻译
1 条回复
Sebastian VanderVoort
Sebastian VanderVoort Thank you guys, I think this was the last reason I needed to uninstall this **** app from my android device. Just use the browser/mobile version again... 查看翻译
Walter Francis
Walter Francis I wonder what Dianne Hackborn would think of this nonsense. 查看翻译
T-One Hofmann
T-One Hofmann WTF are you doing? Going full retard? 查看翻译
Sven Teresniak
Sven Teresniak Now I know why the Facebook app is this awful. In fact the FB app is the reason why I prefer G+. 查看翻译
Noah Yetter
Noah Yetter What's odd is that the Facebook for Android app which shipped with my Nexus One, running Android 2.1, was better than the current Facebook for Android app running on my 4.1 Galaxy Nexus. That's both in terms of features and usability, and also performance. It truly boggles the mind how far backwards you have moved in such a short time. 查看翻译
Michael Borsuk
Michael Borsuk "Bruce Williams That's very cool - and a little scary that your app is allowed to muck with the VM internals that way."

They aren't mucking with any VM internals, they're only modifying a heap buffer which should only be used in the java process their ... 展开 查看翻译
David Banham
David Banham Super, but when will the app load the actual content reliably? News posts, sometimes. Images, almost never. Loading the site through Chrome on the phone is vastly faster. Loading Twitter is faster still. 查看翻译
Kevin Carter
Kevin Carter Facebook.

Wat are you doing?!? ... 展开 查看翻译
Chris Haun
Chris Haun This kind of programming isn't anything I would admit to, let alone brag about in public. I'm super happy the FB app was one of the first things I removed from my phone. 查看翻译
Andrew Dodd
Andrew Dodd GS2 - most popular Gingerbread phone of all time? Hello, it's March 2013, that device received ICS almost a year ago. Even the most delayed carrier-mangled version has had ICS for many months. 查看翻译
2条回复
Fernando F. Gallego
Fernando F. Gallego Hey, it is easier, make an apk for versions below 2.3 that still uses a webview and html5 and another apk for higher android versions... it is better that developing and maintaining such a hack. Many mobile brands customize the android code so it can still be broken on some devices... 查看翻译
Oliver Hagel-Doll
Oliver Hagel-Doll  Reads like a story from Daily WTF: http://thedailywtf.com/ 查看翻译
Chris Bice
Chris Bice  I applaud your attempts at fixing these issues, but some of these changes have had negative side effects on usability- i have an og droid razr, and since the latest update i have gotten network error messages, stop errors switching from notification...展开 查看翻译
Wellington Moreno
Wellington Moreno  As an Software Engineer, I must say Android's limitation is absurd. Modularizing your code into many small methods is one of the best practices defined in books like Code Complete and Clean Code. You want lots of small manageable functions and not a fe...展开 查看翻译
James Wald
James Wald  Awesome hack. This should buy Facebook plenty of time to write a more focused library to replace Guava. That library is a behemoth for an embedded system such as Android, consuming around 20% of the 64,000 method limit with 12,000 methods. Not to me...展开 查看翻译
Manuel Martín-Vivaldi
Manuel Martín-Vivaldi Probably that´s the reason there is always a Facebook Service in the background wasting around 50 MB of RAM, with tha FB App closed and all notifications disabled. Can you stop that Service when Facebook is not running, please? Our pones will work much better, thanks. 查看翻译
David Stenbeck
David Stenbeck All this and sencha releases a demo in JS that's even faster than this pile of crap 查看翻译
Justin David Kruger
Justin David Kruger Why do they have so many method calls, did they just cross compile javascript into java? how is this possible? are they using strongly typed polymorphism linked to a WSDL compiler. WTF, why is this even a problem?

This is what happens when you go too far down a rabbit hole. No wonder the Javascript version of Facebook was slow. 查看翻译
2条回复
Tim Baverstock
Tim Baverstock  It's nice to see a company trying to apply proper OO principles to Android programming, rather than the 'bang the examples together' mentality caused by the poor Android documentation and following the unfortunate 'use as few classes as possible, don'...展开 查看翻译
3条回复
楊來偉
楊來偉 are you developing the mobile Android Facebook app in Java 7? 查看翻译
Gaurav Kumar
Gaurav Kumar  @Chris you are absolutely right!!! I said this is a great hack, I didn't say this ain't a bad design...:-) 查看翻译
1 条回复





当一个app的功能越来越复杂,代码量越来越多,也许有一天便会突然遇到下列现象:

1. 生成的apk在2.3以前的机器无法安装,提示INSTALL_FAILED_DEXOPT

2. 方法数量过多,编译时出错,提示:

Conversion to Dalvik format failed:Unable to execute dex: method ID not in [0, 0xffff]: 65536  


出现这种问题的原因是:

1. Android2.3及以前版本用来执行dexopt(用于优化dex文件)的内存只分配了5M

2. 一个dex文件最多只支持65536个方法。


针对上述问题,也出现了诸多解决方案,使用的最多的是插件化,即将一些独立的功能做成一个单独的apk,当打开的时候使用DexClassLoader动态加载,然后使用反射机制来调用插件中的类和方法。这固然是一种解决问题的方案:但这种方案存在着以下两个问题:

1. 插件化只适合一些比较独立的模块;

2. 必须通过反射机制去调用插件的类和方法,因此,必须搭配一套插件框架来配合使用;


由于上述问题的存在,通过不断研究,便有了dex分包的解决方案。简单来说,其原理是将编译好的class文件拆分打包成两个dex,绕过dex方法数量的限制以及安装时的检查,在运行时再动态加载第二个dex文件中。faceBook曾经遇到相似的问题,具体可参考:

https://www.facebook.com/notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920

文中有这么一段话:

However, there was no way we could break our app up this way--too many of our classes are accessed directly by the Android framework. Instead, we needed to inject our secondary dex files directly into the system class loader。

文中说得比较简单,我们来完善一下该方案:除了第一个dex文件(即正常apk包唯一包含的Dex文件),其它dex文件都以资源的方式放在安装包中,并在Application的onCreate回调中被注入到系统的ClassLoader。因此,对于那些在注入之前已经引用到的类(以及它们所在的jar),必须放入第一个Dex文件中。


下面通过一个简单的demo来讲述dex分包方案,该方案分为两步执行:


整个demo的目录结构是这样,我打算将SecondActivity,MyContainer以及DropDownView放入第二个dex包中,其它保留在第一个dex包。

一、编译时分包

整个编译流程如下:



除了框出来的两Target,其它都是编译的标准流程。而这两个Target正是我们的分包操作。首先来看看spliteClasses target。



由于我们这里仅仅是一个demo,因此放到第二个包中的文件很少,就是上面提到的三个文件。分好包之后就要开始生成dex文件,首先打包第一个dex文件: 



由这里将${classes}(该文件夹下都是要打包到第一个dex的文件)打包生成第一个dex。接着生成第二个dex,并将其打包到资资源文件中:



可以看到,此时是将${secclasses}中的文件打包生成dex,并将其加入ap文件(打包的资源文件)中。到此,分包完毕,接下来,便来分析一下如何动态将第二个dex包注入系统的ClassLoader。


二、将dex分包注入ClassLoader

这里谈到注入,就要谈到Android的ClassLoader体系。



由上图可以看出,在叶子节点上,我们能使用到的是DexClassLoader和PathClassLoader,通过查阅开发文档,我们发现他们有如下使用场景:

1. 关于PathClassLoader,文档中写到: Android uses this class for its system class loader and for its application class loader(s),

由此可知,Android应用就是用它来加载;

2. DexClass可以加载apk,jar,及dex文件,但PathClassLoader只能加载已安装到系统中(即/data/app目录下)的apk文件。


知道了两者的使用场景,下面来分析下具体的加载原理,由上图可以看到,两个叶子节点的类都继承BaseDexClassLoader中,而具体的类加载逻辑也在此类中:

BaseDexClassLoader:  

[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. @Override  

  2. protected Class<?> findClass(String name) throws ClassNotFoundException {  

  3.     List<Throwable> suppressedExceptions = new ArrayList<Throwable>();  

  4.     Class c = pathList.findClass(name, suppressedExceptions);  

  5.     if (c == null) {  

  6.         ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);  

  7.         for (Throwable t : suppressedExceptions) {  

  8.             cnfe.addSuppressed(t);  

  9.        }  

  10.         throw cnfe;  

  11.     }  

  12.      return c;  

  13. }  


由上述函数可知,当我们需要加载一个class时,实际是从pathList中去需要的,查阅源码,发现pathList是DexPathList类的一个实例。ok,接着去分析DexPathList类中的findClass函数,

DexPathList:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. public Class findClass(String name, List<Throwable> suppressed) {  

  2.     for (Element element : dexElements) {  

  3.         DexFile dex = element.dexFile;  

  4.         if (dex != null) {  

  5.             Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);  

  6.             if (clazz != null) {  

  7.                 return clazz;  

  8.             }  

  9.         }  

  10.    }  

  11.     if (dexElementsSuppressedExceptions != null) {  

  12.         suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));  

  13.     }  

  14.     return null;  

  15. }  

上述函数的大致逻辑为:遍历一个装在dex文件(每个dex文件实际上是一个DexFile对象)的数组(Element数组,Element是一个内部类),然后依次去加载所需要的class文件,直到找到为止。

看到这里,注入的解决方案也就浮出水面,假如我们将第二个dex文件放入Element数组中,那么在加载第二个dex包中的类时,应该可以直接找到。

带着这个假设,来完善demo。

在我们自定义的BaseApplication的onCreate中,我们执行注入操作:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. public String inject(String libPath) {  

  2.     boolean hasBaseDexClassLoader = true;  

  3.     try {  

  4.         Class.forName("dalvik.system.BaseDexClassLoader");  

  5.     } catch (ClassNotFoundException e) {  

  6.         hasBaseDexClassLoader = false;  

  7.     }  

  8.     if (hasBaseDexClassLoader) {  

  9.         PathClassLoader pathClassLoader = (PathClassLoader)sApplication.getClassLoader();  

  10.         DexClassLoader dexClassLoader = new DexClassLoader(libPath, sApplication.getDir("dex", 0).getAbsolutePath(), libPath, sApplication.getClassLoader());  

  11.         try {  

  12.             Object dexElements = combineArray(getDexElements(getPathList(pathClassLoader)), getDexElements(getPathList(dexClassLoader)));  

  13.             Object pathList = getPathList(pathClassLoader);  

  14.             setField(pathList, pathList.getClass(), "dexElements", dexElements);  

  15.             return "SUCCESS";  

  16.         } catch (Throwable e) {  

  17.             e.printStackTrace();  

  18.             return android.util.Log.getStackTraceString(e);  

  19.         }  

  20.     }  

  21.     return "SUCCESS";  

  22. }   

这是注入的关键函数,分析一下这个函数:

参数libPath是第二个dex包的文件信息(包含完整路径,我们当初将其打包到了assets目录下),然后将其使用DexClassLoader来加载(这里为什么必须使用DexClassLoader加载,回顾以上的使用场景),然后通过反射获取PathClassLoader中的DexPathList中的Element数组(已加载了第一个dex包,由系统加载),以及DexClassLoader中的DexPathList中的Element数组(刚将第二个dex包加载进去),将两个Element数组合并之后,再将其赋值给PathClassLoader的Element数组,到此,注入完毕。


现在试着启动app,并在TestUrlActivity(在第一个dex包中)中去启动SecondActivity(在第二个dex包中),启动成功。这种方案是可行。


但是使用dex分包方案仍然有几个注意点:

1. 由于第二个dex包是在Application的onCreate中动态注入的,如果dex包过大,会使app的启动速度变慢,因此,在dex分包过程中一定要注意,第二个dex包不宜过大。

2. 由于上述第一点的限制,假如我们的app越来越臃肿和庞大,往往会采取dex分包方案和插件化方案配合使用,将一些非核心独立功能做成插件加载,核心功能再分包加载。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值