Master Blue has been very busy in recent months. I haven’t updated my article for a long time. I’m ashamed of it
In the past few months, I was responsible for the development, maintenance and docking of game SDK. The project has been over for some time, so I would like to sort out the knowledge points involved in the development of game SDK.
Some friends may be a little strange to game SDK development, I hope this article will help you.
preface
I remember when I was looking for a job after graduation in 2017, I went to a company for an interview for the position of game SDK. The interviewer immediately asked me if I had done SDK development before. I said no, but after a few words, I went back to wait for the notice
HR may have a problem screening the resume, but from a technical point of view, no SDK development is not qualified for this position?
What is SDK
SDK (Software Development Kit) means Software Development Kit. Generally, we package some functions separately into a library for Development and maintenance, and then provide compiled products (JAR package or AAR) to multiple projects for use, which belongs to SDK Development. Common ones are short video SDKS, push SDKS, share SDKS, and the focus of this article: game SDKS.
Ii. Game SDK
2.1 What is game SDK
Little red is to do social App entertainment company, day tens of millions of living, want to let oneself platform diversity, such as play a game download function, for users to download, the user feel fun, are likely to pay to buy equipment, but there is a problem, the little red will not play games, if open a single product line to develop games, investment is quite large, So I wondered if I could go outside and pick up the game.
Finally, Little Red and Little Green confirm their cooperation:
1. Xiaohong provides game SDK, which needs to include the core login function and payment function; 2. The game developed by Little Green is not connected to the login and payment system of other platforms, but directly connected to the game SDK of Little Red, using little Red’s login and payment system. 3
Summary: The difference between game SDK and ordinary SDK lies in that it provides a game account system and payment system, the core of which is login and payment functions.
2.2 Flow chart of game SDK
The core of the GAME SDK is the login and payment functions, and the rest is operational related, such as burying points, statistics, etc
The login and payment process is as follows:
The picture is simple. To explain, the upper part is the login process, and the lower part is the payment process.
The process is relatively simple ~
Here are a few things to note about game SDK development:
Iii. Notes for game SDK development
3.1 less dependent on
Many developers know that as an SDK, they should use less open source libraries, or not open source libraries, but handwritten web frameworks, handwritten databases, etc., mainly considering two aspects:
- Reduce SDK volume;
- Avoid dependency conflicts during third-party access
Of course, the dependency library does not mean that it cannot be used, sometimes some data statistics library need to rely on a third party, that situation is unavoidable, can provide a solution to the dependency conflict in the docking document
3.2 Resolving dependency Conflicts
Methods a
Add a similar configuration to your app’s build.gradle as follows:
Configurations. All {resolutionStrategy {// Resolve V4 package conflicts by forcing the use of this version of V4 package force'com. Android. Support: support - v4:26.1.0'}}Copy the code
Way 2
exclude
implementation("com.xxx.xxx:xx") {
exclude group: 'com.android.support'
}
Copy the code
Exclude is the most common way to resolve dependency conflicts. However, if multiple dependency libraries introduce different versions of other libraries, you will need to write multiple exclude separately. Obviously, the first method is relatively simple and crude.
3.3 Expose as few interfaces as possible
Interface oriented programming, taking game SDK as an example, exposed interfaces generally include SDK initialization, login, payment, etc. The reference design is as follows:
Define the interface:
// Call funregisterApp (context: ApplicationContext, appId: String) // init(activity: activity) // LoginCallBack) fun pay(product: Product, payCallBack: PayCallBack) ... }Copy the code
The implementation class
Override fun registerApp(context: ApplicationContext, appId: override fun registerApp(context: ApplicationContext, appId: override fun registerApp) String) {// AppID related} override fun init(activity: activity) {// Initialize logic, such as displaying suspension window} Override fun login(loginCallBack: Override fun pay(product: product, payCallBack: payCallBack) {// Pay logic} override fun pay(product: product, payCallBack: payCallBack) {// Pay logic}... }Copy the code
The implementation class is our internal logic, we do not want to be external access, external only need to know the IGame interface method on the line, we can write a singleton management class for external use
/** * object GameSDKManager :IGame private val gameImpl: IGame by lazy { GameImpl() } override fun registerApp(application: Application, appId: String) { gameImpl.registerApp(application,appId) } override fun init(activity: Activity) { gameImpl.init(activity) } override fun login(loginCallBack: LoginCallBack) { gameImpl.login(loginCallBack) } override fun pay(product: Product, payCallBack: PayCallBack) { gameImpl.pay(product,payCallBack) } }Copy the code
Kotlin’s object keyword represents a singleton,
External calls to methods in the SDK are made via gamesdkManager. XXX. To provide other methods in the future, just modify the IGame interface and implement it in GameSDKManager and GameImpl respectively.
Of course, it’s not necessary to split the three classes this way; this is just an example of interface oriented programming.
4. Game SDK bugs
Game SDK early development self-test may be very smooth, not difficult, but with the game docking may appear some problems, such as ClassNotFound, Resource not found, dependency conflict, crash and so on, as for why, the following will be introduced ~
4.1 The SDK should support Eclipse
SDK 1.0 test passed, officially launched, happily threw the document to the receiver, thinking, THIS I tested no problem, the demo also gave, as long as according to the document and demo, no problem.
However, the reply was: “Eclipse access documentation available?”
I was stunned. In what age do people still use Eclipse to develop apps?
I tried to convince them to use Android Studio and was told that every other game SDK provides Eclipse access
I think the last time I used Eclipse was my junior year…
In this way, after downloading Eclipse the next day, I installed APT plug-in according to the tutorial. However, the compilation kept reporting errors and I forgot the specific error information. The final solution is to download an Eclipse VERSION SDK, Eclipse cannot use Android Studio version SDK.
Ok, Eclipse environment is ready, Hello World is running, start writing demo~
Since the SDK is a product of aar, Eclipse can only rely on jar packages and library, and generally use jar packages to rely on, first unzip aar, copy the classes.jar from it and rename it, and then rely on the JAR package in Eclipse. The SDK resource files and jar packages in the LIbs directory also need to be copied into the Eclipse project.
Finally, compile success, install, open, flash back ~
Resources$NotFoundException: Resource ID # 0x13D6b6
4.2 setContentView(XXX) why to collapse?
Take a look at this code
override fun onCreate(savedInstanceState: Bundle?) {super.oncreate (savedInstanceState) // Simple codesetContentView(R.layout.activity_test)
}
Copy the code
When the aar package is packaged, the code will not work in Android Studio, but will crash when Eclipse is installed as a JAR package.
Caused by: android.content.res.Resources$NotFoundException: Resource ID #0x13d6b6
at android.content.res.ResourcesImpl.getValue(ResourcesImpl.java:246)
at android.content.res.Resources.loadXmlResourceParser(Resources.java:2256)
at android.content.res.Resources.getLayout(Resources.java:1228)
at android.view.LayoutInflater.inflate(LayoutInflater.java:427)
at android.view.LayoutInflater.inflate(LayoutInflater.java:380)
at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:555)
at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:161)
at luyao.util.ktx.base.BaseVMActivity.onCreate(BaseVMActivity.kt:25)
Copy the code
Press command and place the mouse over R.layout.activity_test
This is a constant, and this constant is defined in the R file, generated in the AAPT phase,
For those of you who already know what the problem is, let’s review the main process of APK packaging
- The AAPT (or AAPT2) tool packages resource files and generates r.class files, resources. Arsc resource index tables
- AIDL to Jave code (if there is AIDL)
- Java code is compiled into a.class file
- Use the dex tool to convert the. Class file to Dalvik bytecode, which is a. Dex file
- Package.dex files and other resource files into an unsigned APK using the ApkBuilde tool
- Use the signature tool to sign apK, v1 signature using Jarsigner, v2 signature using ApkSigner (SDK 25 version is available)
In the first phase of APK compilation, AAPT packages the resource files, generates the R.class file and the Resources. Arsc resource index table
When the Library project packages the AAR, the procedures above 123 will definitely go through, but the AAR does not generate the resource index table resources.arsc. The mapping between resource ids and resource files is recorded in R.table as shown below:
Eclipse can only access the classes. Jar package. When we copy the resource file to Eclipse and compile apK, the resource file will have a new resource ID, and the ids referenced in the classes.
In classes.jar, setContentView(r.layout.activity_test) is equivalent to setContentView(-1300150),
When we copy activity_test.xml to the Eclipse project and compile it, AAPT generates a new resource ID for it. The corresponding resource ID of R.rayout.activity_test is no longer -1300150.
This is why setContentView(-1300150) in classes.jar reported an error that the resource was not found.
Once the cause of the problem is known, to solve the problem, the resource ID used in the SDK needs to be obtained dynamically, not using the constant ~ in the R file
4.1.2 Dynamically Obtaining resource ids
Google provides apis to obtain resource ids by resource names
Resources#getIdentifier(String name, String defType, String defPackage)
/**
* Return a resource identifier for the given resource name. A fully
* qualified resource name is of the form "package:type/entry". The first
* two components (package and type) are optional if defType and
* defPackage, respectively, are specified here.
*
* <p>Note: use of this function is discouraged. It is much more
* efficient to retrieve resources by identifier than by name.
*
* @param name The name of the desired resource.
* @param defType Optional default resource type to find, if "type/" is
* not included in the name. Can be null to require an
* explicit type.
* @param defPackage Optional default package to find, if "package:" is
* not included in the name. Can be null to require an
* explicit package.
*
* @return int The associated resource identifier. Returns 0 if no such
* resource was found. (0 is not a valid resource ID.)
*/
public int getIdentifier(String name, String defType, String defPackage) {
return mResourcesImpl.getIdentifier(name, defType, defPackage);
}
Copy the code
The first parameter is the resource name, for example a TextView definition with an ID called tv_title; The second argument is the type, such as String, XML, style, layout, and so on, that corresponds to the inner class in the R.class file
If you want to get a layout file ID, pass Layout, if you want a string ID, pass String, and so on.
The third parameter is the package name.
Finally, the tool class is encapsulated as follows
Object ResourceUtil {// Cache resource ID Private val idMap: HashMap<String, Int> = HashMap() private fun getIdByName(context: Context, defType: String, name: String): Int {// Cache val key = defType +"_"+ name val value: Int? = idMap.get(key) value? .let {returnIt} / / access to resources id val identifier. = the context resources. GetIdentifier identifier (name, defType, context. PackageName)? .let { idMap.put(key, identifier) }returnFun getIdFromLayout(context: context, name: String): Int {return getIdByName(context, "layout", name)
}
...
Copy the code
Then setContentView(r.layout.test) needs to be modified to
setContentView(ResourceUtil.getIdFromLayout(context, "test"))
Copy the code
The problem is solved, but we still need to understand the underlying principle, for example, AAPT package resource file, will generate resource ID, resource ID and how to associate it? How to read resource IDS by resource names?
3.1.3 AAPT packaging products
The first phase of compilation, which uses AAPT to package the resource files, produces the following
- Images and XML resources in the RES folder (XML compiled to binary)
- Assets folder (no resource ID generated)
- Binary AndroidManifest. XML
- Resource index table Resources.arsc
- R.c lass file
Focus on the resource index table resources.arsc,
The data format of the resources.arsc file is quite complex, and Android Studio can help us parse it
Build -> Analyze APK in Android Studio, open the Analyze APK and select Resources. arsc
Id (resource ID), name (resource name), and value (resource path) can all be converted to each other through this index table. Resource #getIdentifier(String name, String defType, String defPackage) Of course, use the resources. Arsc resource index table.
3.1.4 Mechanism of Reading Resource ids by Resource Names
Resources#getIdentifier source code I probably followed, the call process is
Resources#getIdentifier
ResourcesImpl#getIdentifier
AssetManager#getResourceIdentifier
AssetManager2.cpp#GetResourceId
Don’t put too much source code, we are interested in can see AssetManager2. CPP this class, the inside connection ApkAssets, frameworks/base/libs/androidfw/ApkAssets CPP
ApkAssets. CPP contains the definition and use of resources. Arsc
The conclusion is that resources.arsc is loaded and parsed in native layer. Through resources.arsc, the resource index table, resource ID, resource name and resource path can be converted to each other.
This is boring. Is that what the GAME SDK is all about? Can we get something practical?
5. Development of game SDK and some operations in the later stage
If it is a normal game SDK, then as long as the access party can successfully access the SDK, it is done. However,
In addition to providing the game SDK, Xiao Hong also needs to check and accept the games connected to the game SDK to ensure the normal functions of the game SDK.
After all, the game is to be operated on xiaohong’s platform. Xiaohong has the responsibility and obligation to test and accept every game to ensure that the basic functions are normal. It can’t be broken when users open it
With the upgrade of SDK version, the functions will be increased, and the functions to be accepted will be more and more, such as: verify signature, SDK has the function to check for updates, token expiration, the game needs to do login logic and so on…
Here’s how I dealt with some of the problems.
5.1 Log Switch
There is a problem with SDK access. If the release version disables the log, we need to open the log to reappear the problem. There are two common ways:
-
You can set the click event of a control by referring to the developer mode switch, for example, to turn on the log switch after five consecutive clicks. The log switch needs to be persisted, such as saved to SP, to be read during SDK initialization.
-
There is another way to do this, which is similar to Fraternite. The initialization method provides the debug parameter, so that the access party can pass true to view the log, but for the sake of SDK internal information security, I did not do this.
5.2 Checking the Configuration
The demo I provided runs normally, but there are often some problems when third parties access it, maybe their Android SDK versions are different, or some configurations are not written in strict accordance with the document. As the SDK developer, I hope that the access party can find and deal with these configuration problems by itself. This requires adding detection logic to the game SDK.
5.2.1 A function to check for updates
Starting from Android 8.0, the user is required to explicitly turn on the unknown source switch, so there is the following code
Once found in access party apk, context. The packageManager. CanRequestPackageInstalls (), always returns false, can’t adjust the installation page, the first thought is that there is no statement from access installation permissions
<! --> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
Copy the code
Then remove the permission statement to verify that the exception will be thrown, indicating that this is not the cause.
Finally found the targetSdkVersion less than 26, packageManager. CanRequestPackageInstalls () returns false, At present, targetSdkVersion must be 26 or above in each major application market. In order to ensure the normal update function of SDK, add the following detection code when SDK initialization
In this way, the access party targetSdkVersion must be 26 or above. Otherwise, exceptions will be thrown and problems can be found from the exception log.
4.2.2 Configuration Check needs to be added for FileProvider
Since installing APK after 7.0 requires retrieving urls via FileProvider, manifest has such code
For Android Studio, the applicationId in build.gradle is automatically read to replace the placeholder ${applicationId}.
If Eclipse wraps, the placeholder ${applicationId} is left intact and will not be replaced, and the following code will report a null pointer
FileProvider.getUriForFile(context,
context.packageName + ".fileprovider", file)
Copy the code
How do I ensure that the access party has configured the FileProvider correctly? Add the configuration detection code as follows
During SDK initialization, go to the private directory to create an empty file, and use the getUriFormFile method to trigger the FileProvider to obtain the URL. If an exception occurs, it indicates that the FileProvider configuration is incorrect.
Later, when accepting APK, as long as it can be installed and opened normally, it indicates that the FileProvider configuration is correct.
5.2.3 Signature Verification
After the game party access the game SDK, it will be packaged into APK, which will be launched on our platform. We hope to unify the APK signature, so we need to confirm the SIGNATURE of APK when accepting the APK.
There are two main ways to view APK signatures:
Signing for V2
keytool -printcert -jarfile xxx.apk
or
apksigner verify -v –print-certs xxx.apk
This command, while crude, requires apK to use v2 signatures,
Signature for V1
If the apK uses the v1 signature, it is difficult to decompress the APK, find cert. RSA in the meta-info directory, and run the command
keytool -printcert -file CERT.RSA
There may be a better way for v1 signature, I haven’t found ~
Code signature check
If you use v2 signature, you can view the signature with a single command, but most game publishers use V1 signature, manual verification of the signature is more troublesome, or the verification of the code is more enjoyable
fun checkSign(context: Context) {
val signCheck = SignCheck(context, "A3:E1:5E:BA:...")
if (signCheck.check()) {
Log.i(TAG, "Signed correctly")}else {
toast("Application signature does not match, please check signature")}}Copy the code
The logic of the SignCheck class is to get the application signature. The check method compares the application signature with the correct signature and returns true if it is the same.
If the signature is incorrect, the game side will play a toast prompt during SDK access
If there are other mandatory configurations, handle them in a similar manner, once and for all
Six game SDK channel package concept
Channel packages are familiar to everyone. They are generally used to collect data about apps in different app markets, such as additions, daily live, retention, etc.
The concept of channel packs for game SDKS is slightly different:
After the launch of the game, the platform relies on users to download the game by themselves, and the starting quantity is very slow, so it needs to be promoted. If users are encouraged to download the game through push, the user experience will be poor. So you need to get those influential people to do the paid promotion. Each request interface in the SDK will transmit channel identification. For example, if user A wants to promote the game, we will give him an APK marked with channel IDENTIFICATION of A. Users registered through this APK belong to User A.
6.1 Selecting a Channel package Mode
There are two popular open source libraries for channel packages based on signature methods
The next generation of Android package tool: https://github.com/mcxiaoke/packer-ng-plugin there are two versions, sign signature v1 and v2.
Walle, wall-e: https://github.com/Meituan-Dianping/walle visual sign only supports the v2.
Using Walle alone is not appropriate for game SDKS, as the default APK signature for most game publishers is V1 signature.
Adults don’t like to choose. They want both
Fungetchannel (context: context) : String {var channel = packerng.getMarket (context)if(TextUtils. IsEmpty (channel)) {/ / sign for v2 channel = WalleChannelReader getChannel (context, Utils getDefaultChannel ())}return channel
}
Copy the code
The value can be packerng-v1 + packerng-v2 or Packerng-v1 + Walle.
6.2 Principle of Channel package
6.2.1 Signing for V1
Packerng-v1 principle:
An APK file is actually a ZIP file with signature information. According to the ZIP file format specification, a part of metadata at the end of the ZIP file represents ZIP file comments. Correct modification of this part of data will not damage the ZIP file
There are other channel package solutions for V1 signature, but most of them have efficiency problems. For example, productFlavors property of Gradle is used to create channel package, which is slow. Add an empty file named channel name to the meta-INF directory, which is not verified by the signature, but it is slow to read the channel because apK needs to be decompressed to read it.
6.2.2 Signing a Signature for V2
For APK with v2 signature, all the schemes for V1 signature are invalid.
Walle’s principle is:
V2 signature block has a block that can add some accessory information, and will not be checked by the signature. The user-defined channel information is written into this block to generate channel package.
Seven decompiling
In the early stage, the APK issued by the game publisher may not use our signature, and it sometimes takes a long time for them to repackage, so we must master the relevant commands of Apktool to unpack, package and sign.
7.1 download apktool
apktool github
Jar and apktool executable scripts, place them in /usr/local/bin/directory, and command + x to set permissions.
7.2 decompiling
apktool d demo.apk
Decompilated demo.apk will be output to the demo directory. The -o argument can specify the output directory.
After decompiling, you can modify the resource file or bytecode
7.3 to compile
apktool b demo -o unsign.apk
The output is an unsigned APK that requires a signature to install on the phone
7.4 apk signature
It’s easy to type a signed APK with Android Studio
However, it is necessary to use the signature tool to sign an unsigned APK. Jarsigner is used for V1 signature, and Apksigner is used for V2 signature.
7.4.1 v1 signature
Jarsigner -verbose -keystore [signature file path] -keypass [password] -storepass [password] -signedjar [output APK path] [APK path to sign] -digestalg Sigalg [sigalg [MD5withRSA] [certificate alias]
For example, my signature file is lizhigame. Keystore and the alias password is lizhigame. Then, the signature command is as follows
jarsigner -verbose -keystore lizhigame.keystore -keypass lizhigame -storepass lizhigame -signedjar sign.apk unsign.apk -digestalg SHA1 -sigalg MD5withRSA lizhigame
The console logs are displayed after the command is executed
7.4.2 v2 signature
V2 signature using apkSigner, in the SDK build-tools, note that the signature is available in version 25 or later
ApkSigner signature command:
Apksigner sign –ks [signature file] –ks-pass pass:[password] –out [output APK path] [apK required signature]
For example, my signature file is lizhigame. Keystore and the alias password is lizhigame. Then, the signature command is as follows
apksigner sign –ks lizhigame.keystore –ks-pass pass:lizhigame –out sign_v2.apk unsign.apk
The apkSigner signature process is not prompted and can be used in conjunction with the verify signature command
Verify the signature
apksigner verify -v –print-certs sign_v2.apk
conclusion
This article is a summary and share of my work on the development of game SDK for more than three months. The development of game SDK is more about dealing with business problems and docking problems. Automating repetitive work and forcing access parties to access the SDK according to our requirements through code checking configuration can reduce unnecessary communication costs.
This article summarizes the knowledge points:
- Introduce the concept and process of game SDK
- This section describes the precautions of game SDK development
- The Eclipse access SDK crashes, and the resource ID needs to be dynamically obtained
- This section describes the process of AAPT packaging resource files and the function of the source index table resources.arsc
- Some details of the game SDK operation
- Channel package scheme and principle
- Apktool decompilation and backcompilation
- Use Jarsigner and apksigner to sign the APK
If you’re looking for a job, there are a number of SDK development positions on the job boards that pay very well, so hopefully this article will help you out.
other
I have been very busy these last few months and have not had much energy to write articles (mainly lazy) ~
I’m going to spend some time writing about quality development, efficient development, architecture, etc. These are the hurdles that must be crossed to become a senior Android engineer. I’m going to finish this series of articles based on actual projects.
Stay tuned ~