Product | technology drops
The author | Jiang Yiwang
Recently, Didi released the open source project DroidAssist, provides a simple and easy to use, non-invasive, configuration, lightweight Java bytecode operation mode, only need to add simple Java code in XML configuration can be implemented during the compilation of the Class file dynamic modification.
DroidAssist differs from other AOP solutions in that it provides an easy-to-use, non-intrusive, configurable, lightweight way to manipulate Java bytecode without requiring any knowledge of Java bytecode. Adding simple Java code to the XML configuration enables dynamic compile-time modifications to the class file without introducing additional dependencies.
▍ origin
As a representative of large APP, Didi Chuxing passenger terminal integrates many business lines and contains a large number of dependency libraries. For each version, multiple teams integrate a large number of codes into the passenger terminal, which are difficult to directly trace back to the source code. At the same time, the passenger terminal is also characterized by large number of users, high daily activity and fast iteration. These situations pose great challenges to the development and maintenance of the passenger side, which are mainly reflected in the following aspects: difficult problem prevention, large problem scale and high maintenance cost.
In May 2018, the passenger side team carried out a special optimization for the lag, and there was a problem: Queuedwork.waittofinish () may cause queuedWork.waittoFinish () to get stuck and ANR when the SharedPreferences.apply() method is frequently called. The main reason is that the system blocks the onPause, onStop, and start and stop life cycles of an Activity and waits for QueuedWork to clear. This ensures that the user completes the file writing of SharedPreferences before leaving the component.
After analyzing the reasons, we believe that the passenger APP is relatively in a single process environment, so it is possible to remove the persistent blocking. To solve this problem, we decided to modify the system’s SharedPreferences and implement our own.
But then the question is, how can our custom SharedPreferences be accessed to the passenger side with minimal cost? It’s easy to think of two scenarios:
-
Modify all calls to the Context. GetSharedPreferences () code, return to our own SharedPreferences object, disadvantages: too many changes, workload is too big, modification, reduction the cost too is high.
-
All Application, Activity, and Service classes derive from the common Base class, overriding the getSharedPreferences method in the Base class to return custom SharedPreferences objects. The code of this method is small, but there are also problems that the third-party library can not be modified, and the workload is relatively large, and the cost of modification and restoration is also high.
The above two methods are relatively invasive, involving a large number of source code and code changes of dependent libraries, and the later maintenance and upgrade costs are also high. In order to find a more ideal solution, we hope to find a non-invasive Mock tool. Mock all calls to the getSharedPreferences() method without modifying the code.
-
Hook: Hook technology needs to deal with compatibility problems of various manufacturers and models all the time, so it has great stability risk.
-
AOP: AOP class frameworks that implement bytecode manipulation at compile time are mature and stable enough to be considered, but our analysis shows that existing AOP frameworks, including AspectJ, do not implement the Mock functionality we need.
There are many requirements like SharedPreferences replacement, so we decided to develop a Mock tool for Android platform. After investigation, we determined the technical direction of bytecode modification, and realized such requirements by modifying bytecode. That’s where DroidAssist comes in.
Project address: github.com/didi/DroidA…
▍The sample
The following example is mentioned in the background of SharedPreferences, add the following DroidAssist configuration, compiled in the project, all calls to the Context. GetSharedPreferences () code, Will all be modified to return custom SharedPreferences instance code:
1 <Replace>
2 <MethodCall>
3DroidAssist
4<Source>android.content.SharedPreferences android.content.Context.getS
5haredPreferences(java.lang.String,int)</Source>
6 <Target>{The $_= com.didi.quicksilver.QuicksilverPreferencesHelper.getShar
7edPreferences($0,?) ; }</Target> 8 </MethodCall> 9</Replace>Copy the code
Class before processing:
1public class MainActivity extends Activity {
2@Override
3 protected void onCreate(Bundle savedInstanceState) {
4 super.onCreate(savedInstanceState);
5 SharedPreferences sp = getSharedPreferences("test", MODE_PRIVATE); 6}}Copy the code
Class after processing:
1public class MainActivity extends Activity {
2 protected void onCreate(Bundle savedInstanceState) {
3 super.onCreate(savedInstanceState);
4 SharedPreferences sp = PreferencesHelper.getSharedPreferences(this
5, "test", MODE_PRIVATE); // The target method return custom SharedPreferen
6ces.
7} }
Copy the code
For details about how to use the DroidAssist WIKI, see the DroidAssist WIKI.
▍ features
DroidAssist has evolved from a Mock tool to a tool with full AOP framework functionality, with the following features.
Simplicity and Use
With flexible configuration, DroidAssist can process all the class files in a project by relying on a plug-in and defining the bytecode handling in the configuration file. No additional dependencies need to be added to the process or to the code after the process, and the original line number is not changed.
Rich bytecode processing functions
In addition to addressing the code substitution problem we initially encountered, other AOP capabilities have been extended, with 28 code modification options in four categories.
-
Replace: To replace a specified location code with a specified code
-
Insert: Inserts the specified code before and after the specified position
-
Wrap: Inserts the specified code around the specified location
-
To enhance
-
TryCatch adds a try catch code to the specified code
-
Timing adds a time statistics code to the specified code
▍Simple and easy to use
Incremental build support, fast processing, takes very little build time.
▍ Q&A
1. What can DroidAssist do?
DroidAssist can easily implement functions such as code replacement and code insertion. Didi Chuxing APP uses DroidAssist to implement log output replacement and system SharedPreferences replacement. SharedPreferences commit = apply, Dialog = getDeviceId, getPackageInfo, getSystemService StartActivity protection, anonymous thread renaming, thread pool creation monitoring, main thread lag monitoring, folder creation monitoring, Activity life cycle time statistics, APP startup time statistics and other functions.
2. What’s the difference between DroidAssist and AspectJ?
DroidAssist takes a configuration approach, writing the configuration to implement AOP functionality without modifying Java code at all; DroidAssist is simple to use and does not require complex annotation configurations; DroidAssist makes it easy to do code replacements that AspectJ can’t easily do. DroidAssist can do most of the work in general, but you can use it with AspectJ in more complex cases.
For installation, usage, and FREQUENTLY asked questions, see the following links:
GitHub:github.com/didi/DroidA…
Wiki:github.com/didi/DroidA…
Welcome to the DroidAssist User Exchange Group.
To join, please reply “DroidAssist” in the background of didi Technical official account
▍ END