Android RocooFix 热修复框架

来源:互联网 发布:手机淘宝可以开店铺吗 编辑:程序博客网 时间:2024/05/21 15:51

这里我要讲述下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.



Under the Hood: Dalvik patch for Facebook for Android

Facebook is one of the most feature-rich apps available for Android. With features likepush notifications, news feed, and an embedded version of Facebook Messenger (a complete app in its own right) all working together in real-time, the complexity and volume of code creates technical challenges that few, if any, other Android developers face--especially on older versions of the platform. (Our latest apps support Android versions as old as Froyo--Android version 2.2--which is almost three years old.) 

 

One of these challenges is related to the way Android's runtime engine, the Dalvik Virtual Machine, handles Java methods. Late last year we completed a  major rebuild of our Android app (https://www.facebook.com/notes/facebook-engineering/under-the-hood-rebuilding-facebook-for-android/10151189598933920), which involved moving a lot of our code from JavaScript to Java, as well as using newer abstractions that encouraged large numbers of small methods (generally considered a good programming practice). Unfortunately, this caused the number of Java methods in our app to drastically increase.  

 

As we were testing, the problem first showed up as described in this bug(http://code.google.com/p/android/issues/detail?id=22586) , which caused our app installation to fail on older Android phones. During standard installation, a program called "dexopt" runs to prepare your app for the specific phone it's being installed on. Dexopt uses a fixed-size buffer (called the "LinearAlloc" buffer) to store information about all of the methods in your app. Recent versions of Android use an 8 or 16 MB buffer, but Froyo and Gingerbread (versions 2.2 and 2.3) only have 5 MB. Because older versions of Android have a relatively small buffer, our large number of methods was exceeding the buffer size and causing dexopt to crash. 

 

After a bit of panic, we realized that we could work around this problem by breaking our app into multiple dex files, using the technique described here (http://android-developers.blogspot.com/2011/07/custom-class-loading-in-dalvik.html), which focuses on using secondary dex files for extension modules, not core parts of the app. 

 

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. This isn't normally possible, but we examined the Android source code and used Java reflection to directly modify some of its internal structures. We were certainly glad and grateful that Android is open source—otherwise, this change wouldn’t have been possible. 

 

But as we came closer to launching our redesigned app, we ran into another problem. The LinearAlloc buffer doesn't just exist in dexopt--it exists within every running Android program. While dexopt uses LinearAlloc to to store information about all of the methods in your dex file, the running app only needs it for methods in classes that you are actually using. Unfortunately, we were now using too many methods for Android versions up to Gingerbread, and our app was crashing shortly after startup.   

 

There was no way to work around this with dex files since all of our classes were being loaded into one process, and we weren’t able to find any information about anyone who had faced this problem before (since it is only possible once you are already using multiple dex files, which is a difficult technique in itself).  We were on our own. 

 

We tried various techniques to reclaim space, including aggressive use of ProGuard and source code transformations to reduce our method count. We even built a profiler for LinearAlloc usage to figure out what the biggest consumers were. Nothing we tried had a significant impact, and we still needed to write many more methods to support all of the rich content types in our new and improved news feed and timeline.   

 

As it stood, the release of the much-anticipated Facebook for Android 2.0 was at risk. It seemed like we would have to choose between cutting significant features from the app or only shipping our new version to the newest Android phones (ICS and up). Neither seemed acceptable. We needed a better solution.  

 

Once again, we looked to the Android source code. Looking at the definition of the LinearAlloc buffer (https://github.com/android/platform_dalvik/blob/android-2.3.7_r1/vm/LinearAlloc.h#L33), we realized that if we could only increase that buffer from 5 MB to 8 MB, we would be safe! 

 

That's when we had the idea of using a JNI extension to replace the existing buffer with a larger one. At first, this idea seemed completely insane. Modifying the internals of the Java class loader is one thing, but modifying the internals of the Dalvik VM while it was running our code is incredibly dangerous. But as we pored over the code, analyzing all the uses of LinearAlloc, we began to realize that it should be safe as long as we did it at the start of our program. All we had to do was find the LinearAllocHdr object, lock it, and replace the buffer.

 

Finding it turned out to be the hard part. Here’s where it’s stored(https://github.com/android/platform_dalvik/blob/android-2.3.7_r1/vm/Globals.h#L519), buried within the DvmGlobals object, over 700 bytes from the start. Searching the entire object would be risky at best, but fortunately, we had an anchor point: the vmList object just a few bytes before. This contained a value that we could compare to the JavaVM pointer available through JNI.

 

The plan was finally coming together: find the proper value for vmList, scan the DvmGlobals object to find a match, jump a few more bytes to the LinearAlloc header, and replace the buffer. So we built the JNI extension, embedded it in our app, started it up, and...we saw the app running on a Gingerbread phone for the first time in weeks.The plan had worked. 

 

But for some reason it failed on the Samsung Galaxy S II...

The most popular Gingerbread phone...

Of all time...

 

It seems that Samsung made a small change to Android that was confusing our code. Other manufacturers might have done the same, so we realized we needed to make our code more robust. 

 

Manual inspection of the GSII revealed that the LinearAlloc buffer was only 4 bytes from where we expected it, so we adjusted our code to look a few bytes to each side if it failed to find the LinearAlloc buffer in the expected location. This required us to parse our process's memory map to ensure we didn't make any invalid memory references (which would crash the app immediately) and also build some strong heuristics to make sure we would recognize the LinearAlloc buffer when we found it. As a last resort, we found a (mostly) safe way to scan the entire process heap to search for the buffer. 

 

Now we had a version of the code that worked on a few popular phones--but we needed more than just a few. So we bundled our code up into a test app that would run the same procedure we were using for the Facebook app, then just display a large green or red box, indicating success or failure. 

 

We used manual testing, DeviceAnywhere, and a test lab that Google let us borrow to run our test app on 70 different phone models, and fortunately, it worked on every single one!

 

We released this code with Facebook for Android 2.0 in December. It's now running on hundreds of different phone models, and we have yet to find one where it doesn't work. The great speed improvements in that release would not have been possible without this crazy hack. And needless to say, without Android’s open platform, we wouldn’t have had the opportunity to ship our best version of the app. There’s a lot of opportunity for building on Android, and we’re excited to keep bringing the Facebook experience to more people and devices.  

显示更多心情
评论分享
热门评论
928928
139条评论
334次分享
评论
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...展开查看翻译
 · 回复 · 694 · 2013年3月5日上午 8:17
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.查看翻译
 · 回复 · 108 · 2013年3月5日下午 7:09
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...展开查看翻译
 · 回复 · 52 · 2013年3月5日上午 10:30
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.查看翻译
 · 回复 · 72 · 2013年3月5日下午 1:55
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.查看翻译
 · 回复 · 45 · 2013年3月5日下午 3:01
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.查看翻译
 · 回复 · 40 · 2013年3月5日下午 12:04
Artur Termenji
Artur Termenji "I don't usually develop Android apps, but when I do, I hack the Dalvik". This is totally insane.查看翻译
 · 回复 · 38 · 2013年3月6日上午 1:54
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!查看翻译
 · 回复 · 33 · 2013年3月5日下午 7:58
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.查看翻译
 · 回复 · 24 · 2013年3月5日下午 7:18
Joe Pineapple Simpson
Joe Pineapple Simpson Facebook, fire your "android" team. I could do a better job single handedly and I'm 18.查看翻译
 · 回复 · 23 · 2013年3月5日下午 9:56
5条回复
Toni Willberg
Toni Willberg So instead of fixing your application you decided to break the platform it runs on?查看翻译
 · 回复 · 22 · 2013年3月6日下午 5:19
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...展开
查看翻译
 · 回复 · 15 · 2013年3月7日上午 12:13
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....查看翻译
 · 回复 · 15 · 2013年3月5日下午 10:15
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查看翻译
 · 回复 · 14 · 2013年3月5日下午 11:59
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...)查看翻译
 · 回复 · 11 · 2013年3月5日下午 5:54
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.查看翻译
 · 回复 · 12 · 2013年3月5日上午 8:37
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."
查看翻译
 · 回复 · 10 · 2013年3月5日下午 12:08
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.查看翻译
 · 回复 · 8 · 2013年3月5日下午 11:41
2条回复
Mihai Ciuperca
Mihai Ciuperca ok. it's time to uninstall 1 app from my phone....查看翻译
 · 回复 · 8 · 2013年3月5日下午 9:27
Stefan Müller-Stock
Stefan Müller-Stock You will all burn in hell for this :)查看翻译
 · 回复 · 6 · 2013年3月5日下午 9:00
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 ...展开
查看翻译
 · 回复 · 3月12日上午 10:50
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.查看翻译
 · 回复 · 2015年12月28日下午 8:47
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.查看翻译
 · 回复 · 7 · 2013年3月6日上午 4:32
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...查看翻译
 · 回复 · 7 · 2013年3月6日上午 1:34
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...查看翻译
 · 回复 · 7 · 2013年3月5日下午 10:12
Walter Francis
Walter Francis I wonder what Dianne Hackborn would think of this nonsense.查看翻译
 · 回复 · 6 · 2013年3月6日上午 8:08
T-One Hofmann
T-One Hofmann WTF are you doing? Going full retard?查看翻译
 · 回复 · 6 · 2013年3月5日下午 10:07
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+.查看翻译
 · 回复 · 11 · 2013年3月5日下午 6:43
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.查看翻译
 · 回复 · 4 · 2013年3月8日上午 8:28
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...展开
查看翻译
 · 回复 · 5 · 2013年3月5日下午 12:05
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.查看翻译
 · 回复 · 5 · 2013年3月5日上午 10:47
Kevin Carter
Kevin Carter Facebook.

Wat are you doing?!?...展开
查看翻译
 · 回复 · 8 · 2013年3月6日上午 3:32
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.查看翻译
 · 回复 · 4 · 2013年3月6日上午 8:10
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.查看翻译
 · 回复 · 4 · 2013年3月5日下午 11:44
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...查看翻译
 · 回复 · 2 · 2013年3月6日上午 4:01
Oliver Hagel-Doll
Oliver Hagel-Doll Reads like a story from Daily WTF: http://thedailywtf.com/查看翻译
 · 回复 · 3 · 2013年3月5日下午 7:58
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...展开查看翻译
 · 回复 · 3 · 2013年3月5日上午 10:45
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...展开查看翻译
 · 回复 · 2 · 2014年7月13日上午 5:18
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...展开查看翻译
 · 回复 · 1 · 2013年10月27日上午 9:28 · 已编辑
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.查看翻译
 · 回复 · 1 · 2013年9月27日下午 5:28 · 已编辑
David Stenbeck
David Stenbeck All this and sencha releases a demo in JS that's even faster than this pile of crap查看翻译
 · 回复 · 1 · 2013年3月6日下午 4:35
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 · 2013年3月5日下午 11:45
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'...展开查看翻译
 · 回复 · 2 · 2013年3月5日下午 6:07
3条回复
楊來偉
楊來偉 are you developing the mobile Android Facebook app in Java 7?查看翻译
 · 回复 · 2 · 2013年3月5日下午 5:03
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...:-)查看翻译
 · 回复 · 2 · 2013年3月5日上午 11:14
1 条回复
Uday Nandam
Uday Nandam http://android-developers.blogspot.com/.../custom-class... - correct link for multiple dex files technique查看翻译
 · 回复 · 3 · 2013年3月5日上午 6:01





当一个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分包方案和插件化方案配合使用,将一些非核心独立功能做成插件加载,核心功能再分包加载。





1.背景

当一个App发布之后,突然发现了一个严重bug需要进行紧急修复,这时候公司各方就会忙得焦头烂额:重新打包App、测试、向各个应用市场和渠道换包、提示用户升级、用户下载、覆盖安装。有时候仅仅是为了修改了一行代码,也要付出巨大的成本进行换包和重新发布。

这时候就提出一个问题:有没有办法以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装?

虽然Android系统并没有提供这个技术,但是很幸运的告诉大家,答案是:可以,我们QQ空间提出了热补丁动态修复技术来解决以上这些问题。


2.实际案例

空间Android独立版5.2发布后,收到用户反馈,结合版无法跳转到独立版的访客界面,每天都较大的反馈。在以前只能紧急换包,重新发布。成本非常高,也影响用户的口碑。最终决定使用热补丁动态修复技术,向用户下发Patch,在用户无感知的情况下,修复了外网问题,取得非常好的效果。


3.解决方案

该方案基于的是android dex分包方案的,关于dex分包方案,网上有几篇解释了,所以这里就不再赘述,具体可以看这里https://m.oschina.net/blog/308583。

简单的概括一下,就是把多个dex文件塞入到app的classloader之中,但是android dex拆包方案中的类是没有重复的,如果classes.dex和classes1.dex中有重复的类,当用到这个重复的类的时候,系统会选择哪个类进行加载呢?

让我们来看看类加载的代码:


一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。

理论上,如果在不同的dex中有相同的类存在,那么会优先选择排在前面的dex文件的类,如下图:


在此基础上,我们构想了热补丁的方案,把有问题的类打包到一个dex(patch.dex)中去,然后把这个dex插入到Elements的最前面,如下图:


好,该方案基于第二个拆分dex的方案,方案实现如果懂拆分dex的原理的话,大家应该很快就会实现该方案,如果没有拆分dex的项目的话,可以参考一下谷歌的multidex方案实现。然后在插入数组的时候,把补丁包插入到最前面去。

好,看似问题很简单,轻松的搞定了,让我们来试验一下,修改某个类,然后打包成dex,插入到classloader,当加载类的时候出现了(本例中是QzoneActivityManager要被替换):


为什么会出现以上问题呢?

从log的意思上来讲,ModuleManager引用了QzoneActivityManager,但是发现这这两个类所在的dex不在一起,其中:

1. ModuleManager在classes.dex中

2. QzoneActivityManager在patch.dex中

结果发生了错误。

这里有个问题,拆分dex的很多类都不是在同一个dex内的,怎么没有问题?

让我们搜索一下抛出错误的代码所在,嘿咻嘿咻,找到了一下代码:


从代码上来看,如果两个相关联的类在不同的dex中就会报错,但是拆分dex没有报错这是为什么,原来这个校验的前提是:

如果引用者(也就是ModuleManager)这个类被打上了CLASS_ISPREVERIFIED标志,那么就会进行dex的校验。那么这个标志是什么时候被打上去的?让我们在继续搜索一下代码,嘿咻嘿咻~~,在DexPrepare.cpp找到了一下代码:


这段代码是dex转化成odex(dexopt)的代码中的一段,我们知道当一个apk在安装的时候,apk中的classes.dex会被虚拟机(dexopt)优化成odex文件,然后才会拿去执行。
虚拟机在启动的时候,会有许多的启动参数,其中一项就是verify选项,当verify选项被打开的时候,上面doVerify变量为true,那么就会执行dvmVerifyClass进行类的校验,如果dvmVerifyClass校验类成功,那么这个类会被打上CLASS_ISPREVERIFIED的标志,那么具体的校验过程是什么样子的呢?

此代码在DexVerify.cpp中,如下:


1. 验证clazz->directMethods方法,directMethods包含了以下方法:

1. static方法

2. private方法

3. 构造函数

2.clazz->virtualMethods

1. 虚函数=override方法?

概括一下就是如果以上方法中直接引用到的类(第一层级关系,不会进行递归搜索)和clazz都在同一个dex中的话,那么这个类就会被打上CLASS_ISPREVERIFIED


所以为了实现补丁方案,所以必须从这些方法中入手,防止类被打上CLASS_ISPREVERIFIED标志

最终空间的方案是往所有类的构造函数里面插入了一段代码,代码如下:

if (ClassVerifier.PREVENT_VERIFY) {

System.out.println(AntilazyLoad.class);

}


其中AntilazyLoad类会被打包成单独的hack.dex,这样当安装apk的时候,classes.dex内的类都会引用一个在不相同dex中的AntilazyLoad类,这样就防止了类被打上CLASS_ISPREVERIFIED的标志了,只要没被打上这个标志的类都可以进行打补丁操作。

然后在应用启动的时候加载进来.AntilazyLoad类所在的dex包必须被先加载进来,不然AntilazyLoad类会被标记为不存在即使后续加载了hack.dex包那么他也是不存在的这样屏幕就会出现茫茫多的类AntilazyLoad找不到的log。

所以Application作为应用的入口不能插入这段代码。(因为载入hack.dex的代码是在Application中onCreate中执行的,如果在Application的构造函数里面插入了这段代码,那么就是在hack.dex加载之前就使用该类,该类一次找不到,会被永远的打上找不到的标志)

其中:


之所以选择构造函数是因为他不增加方法数,一个类即使没有显式的构造函数,也会有一个隐式的默认构造函数。

空间使用的是在字节码插入代码,而不是源代码插入,使用的是javaassist库来进行字节码插入的。

隐患:

虚拟机在安装期间为类打上CLASS_ISPREVERIFIED标志是为了提高性能的,我们强制防止类被打上标志是否会影响性能?这里我们会做一下更加详细的性能测试.但是在大项目中拆分dex的问题已经比较严重,很多类都没有被打上这个标志。

如何打包补丁包:

1. 空间在正式版本发布的时候,会生成一份缓存文件,里面记录了所有class文件的md5,还有一份mapping混淆文件。

2. 在后续的版本中使用-applymapping选项,应用正式版本的mapping文件,然后计算编译完成后的class文件的md5和正式版本进行比较,把不相同的class文件打包成补丁包。

备注:该方案现在也应用到我们的编译过程当中,编译不需要重新打包dex,只需要把修改过的类的class文件打包成patch dex,然后放到sdcard下,那么就会让改变的代码生效。





最近在学习Android的热修复,使用了两个比较热门的框架,现做下总结:

项目git地址:https://github.com/dodola/RocooFix

源码会编译错误,可以使用下面的包。

修复lib包:RocooFix_libs

需要了解热修复原理的可以看下张鸿洋博客

也可以加下这个群:561394234 (里面有修复的项目,RocooFix作者也在里面!)

前言:

RocooFix支持两种模式: 
1、静态修复某种情况下需要重启应用。 (推荐使用) 
2、动态修复,无需重启应用即可生效。

使用方法:

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//打补丁(需要制作补丁的时候使用)</span>        RocooFix.init(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>);<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//方案1:静态启用,一般在Application里加载补丁</span><span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/**  * 从Assets里取出补丁,一般用于测试  *  *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> context  *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> assetName  */</span>RocooFix.initPathFromAssets(Context context, String assetName); <span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/**   * 从指定目录加载补丁   *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> context   *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> dexPath   */</span>RocooFix.applyPatch(Context context, String dexPath);<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//方案2:动态打补丁,立即生效,有性能问题,适用于补丁方法数较少的情况,建议在ART虚拟机里启用该模式</span><span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/** * 从Asset里加载补丁,一般用于本地测试 *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> context *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> assetName */</span>RocooFix.initPathFromAssetsRuntime(Context context, String assetName) ;<span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/** * 从指定目录加载补丁 *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> context *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> dexPath */</span> RocooFix.applyPatchRuntime(Context context, String dexPath)  ;</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li></ul>

没有加群的朋友可以下载上面的RocooFix_libs包跟着我一步步来做:

项目使用Android Studio开发,所以准备好翻墙工具下载依赖包!!!

一、新建Android项目 app

二、将上面下载文件里面的rocoo作为lib库导入 
在app的build.gradle 里面添加compile project(‘:rocoo’),也可以手动添加依赖。

三、下载下来的文件夹里面还有个buildsrc文件直接放到app项目平级目录中(这个不用做任何操作)

到此我们的目录结构为:

这里写图片描述

四:配置 根目录里面的 build.gradle文件:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">buildscript {    repositories {        jcenter()        maven {            url <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"http://dl.bintray.com/dodola/maven"</span>   <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//添加</span>        }    }    dependencies {        classpath <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'com.android.tools.build:gradle:2.1.2'</span>        classpath <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'org.jacoco:org.jacoco.core:0.7.4.201502262128'</span>   <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//添加</span>        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// NOTE: Do not place your application dependencies here; they belong</span>        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// in the individual module build.gradle files</span>    }}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li></ul>

、配置 app 里面的build.gradle 文件

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">apply plugin: <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'com.android.application'</span>apply plugin: <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'com.dodola.rocoofix'</span>            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//添加</span>android {    compileSdkVersion <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">23</span>    buildToolsVersion <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"23.0.3"</span>    defaultConfig {        applicationId <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"com.test.hotfixtest"</span>        minSdkVersion <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">15</span>        targetSdkVersion <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">23</span>        versionCode <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">6</span>        versionName <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"1.0"</span>    }<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//添加</span>    buildTypes {        release {            minifyEnabled <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>            proguardFiles getDefaultProguardFile(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'proguard-android.txt'</span>), <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'proguard-rules.pro'</span>        }        debug {            minifyEnabled <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>            proguardFiles getDefaultProguardFile(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'proguard-android.txt'</span>), <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'proguard-rules.pro'</span>        }    }}<span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/****必须添加**/</span>rocoo_fix {    preVersionPath = <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'5'</span>    enable = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>}dependencies {    compile fileTree(include: [<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'*.jar'</span>], dir: <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'libs'</span>)    testCompile <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'junit:junit:4.12'</span>    compile <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'com.android.support:appcompat-v7:23.1.1'</span>    compile project(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">':rocoo'</span>)}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li></ul>

preVersionPath = ‘1’ //注意:此项属性只在需要制作补丁的时候才需开启!!如果不需要制作补丁则需要去掉此项(其中的数值是前一个版本的版本号)

enable = true //注意:关掉此项会无法生成Hash.txt文件

重点介绍一下 preVersionPath 的属性: 
rocoo_fix将制作补丁的步骤透明化,用户无需手动备份hash.txt文件,插件会自动根据当前的versionCode生成hash.txt和mapping.txt文件到指定目录; 
上一个版本发布的时候版本号是1,那么生成的文件会放在app源码目录/rocooFix/version1/[debug]|[release]的目录下,如果需要制作补丁那么在配置里指定preVersionPath 属性,它的值是上一个版本的版本号,这里的值是1, 
然后将build.gradle的versionCode的号码修改,这里修改成2,只要和之前的版本不同就可以,没有具体值的要求

六、添加混淆代码(proguard-rules.pro):

<code class="hljs haml has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">-<span class="ruby" style="box-sizing: border-box;">keep <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">com</span>.<span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">dodola</span>.<span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">rocoofix</span>.** {*;</span>}</span>-<span class="ruby" style="box-sizing: border-box;">keep <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">com</span>.<span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">lody</span>.<span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">legend</span>.* {*;</span>}</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

七、添加测试代码:

新建一个Java类:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> HelloHack {         <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> String <span class="hljs-title" style="box-sizing: border-box;">showHello</span>() {                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"rocoofix"</span>;              }    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul>

在建一个继承Application的类:

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">RocooApplication</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">extends</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">Application</span> {</span>    <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">attachBaseContext</span>(Context base) {        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.attachBaseContext(base);        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//需要打补丁时开启</span>        RocooFix.init(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>);        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//测试从assets文件夹读取补丁</span>        RocooFix.initPathFromAssets(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"patch.jar"</span>);    }}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>

八、运行项目看是否报错。测试已没错。

九、生成 patch.jar 文件 
1、首先随便修改下代码如:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> HelloHack {         <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> String <span class="hljs-title" style="box-sizing: border-box;">showHello</span>() {                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"rocoofix——ok"</span>;              }    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul>

2、修改 app 项目中的build.gradle 文件把 versionCode 版本修改一下,把preVersionPath值修改成versionCode没修改之前的值。

最后运行项目就会在app目录下生成app\rocoofix\version4\debug\patch.jar 了,得到这个文件就算成功了,最后我们就可以随便测试补丁了,放在sd目录或者assets文件下,关闭补丁RocooFix.init(this) 运行没修改前的代码看看程序是否变了。

ps:这里做好心理准备补丁在有些机型会失效,目前测试MI 2S 4.4.4系统 成功。后面还会带来阿里的框架AndFix试用。


0 0