Skip to content

Commit

Permalink
Optimize server startup speed.
Browse files Browse the repository at this point in the history
  • Loading branch information
yanzhenjie committed Sep 24, 2020
1 parent 75103a4 commit 05899c5
Show file tree
Hide file tree
Showing 13 changed files with 132 additions and 274 deletions.
196 changes: 35 additions & 161 deletions api/src/main/java/com/yanzhenjie/andserver/ComponentRegister.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,32 @@
package com.yanzhenjie.andserver;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.util.Log;
import android.content.res.AssetManager;

import com.yanzhenjie.andserver.register.OnRegister;
import com.yanzhenjie.andserver.register.Register;
import com.yanzhenjie.andserver.util.ObjectUtils;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import dalvik.system.DexFile;

/**
* <p> Load all the components in the dex file. </p>
*
* Created by Zhenjie Yan on 2018/9/23.
*/
public class ComponentRegister {

private static final String ANDSERVER_SUFFIX = ".generator.andserver";
private static final String PROCESSOR_PACKAGE = ".andserver.processor.generator.";

private static final String CODE_CACHE_SECONDARY_DIRECTORY = "code_cache/secondary-dexes";
private static final String EXTRACTED_NAME_EXT = ".classes";
private static final String EXTRACTED_SUFFIX = ".zip";

private static final String PREFS_FILE = "multidex.version";
private static final String KEY_DEX_NUMBER = "dex.number";
private static final List<String> REGISTER_LIST = new ArrayList<>();

static {
REGISTER_LIST.add("AdapterRegister");
REGISTER_LIST.add("ConfigRegister");
REGISTER_LIST.add("ConverterRegister");
REGISTER_LIST.add("InterceptorRegister");
REGISTER_LIST.add("ResolverRegister");
}

private Context mContext;

Expand All @@ -59,158 +50,41 @@ public ComponentRegister(Context context) {
}

public void register(Register register, String group) {
List<String> paths = getDexFilePaths(mContext);

for (final String path : paths) {
DexFile dexfile = null;

try {
if (path.endsWith(EXTRACTED_SUFFIX)) {
dexfile = DexFile.loadDex(path, path + ".tmp", 0);
} else {
dexfile = new DexFile(path);
}

Enumeration<String> dexEntries = dexfile.entries();
while (dexEntries.hasMoreElements()) {
String className = dexEntries.nextElement();
if (className.contains(PROCESSOR_PACKAGE)) {
try {
registerClass(register, group, className);
} catch (Exception ignored) {
}
}
}
} catch (IOException e) {
Log.w(AndServer.TAG, "An exception occurred while registering components.", e);
} finally {
if (dexfile != null) {
try {
dexfile.close();
} catch (Throwable ignore) {
}
}
}
AssetManager manager = mContext.getAssets();
String[] pathList = null;
try {
pathList = manager.list("");
} catch (IOException e) {
e.printStackTrace();
}
}

private void registerClass(Register register, String group, String className) throws Exception {
Class clazz = Class.forName(className);
if (clazz.isInterface()) {
if (ObjectUtils.isEmpty(pathList)) {
return;
}

Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> anInterface : interfaces) {
if (anInterface.isAssignableFrom(OnRegister.class)) {
OnRegister load = (OnRegister) clazz.newInstance();
load.onRegister(mContext, group, register);
break;
}
}
}

private static SharedPreferences getMultiDexPreferences(Context context) {
return context.getSharedPreferences(PREFS_FILE,
Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB ? Context.MODE_PRIVATE : Context.MODE_MULTI_PROCESS);
}

/**
* Obtain all the dex path.
*
* @see MultiDexExtractor#loadExistingExtractions(Context, String)
* @see MultiDex#clearOldDexDir(Context)
*/
public static List<String> getDexFilePaths(Context context) {
ApplicationInfo appInfo = context.getApplicationInfo();

List<String> sourcePaths = new ArrayList<>();
sourcePaths.add(appInfo.sourceDir);

if (!isVMMultidexCapable()) {
File sourceApk = new File(appInfo.sourceDir);
String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);
File dexDir = new File(appInfo.dataDir, CODE_CACHE_SECONDARY_DIRECTORY);

for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
File extractedFile = new File(dexDir, fileName);
if (extractedFile.isFile()) {
sourcePaths.add(extractedFile.getAbsolutePath());
for (String path : pathList) {
if (path.endsWith(ANDSERVER_SUFFIX)) {
String packageName = path.substring(0, path.indexOf(ANDSERVER_SUFFIX));
for (String clazz : REGISTER_LIST) {
String className = String.format("%s%s%s", packageName, PROCESSOR_PACKAGE, clazz);
registerClass(register, group, className);
}
}
}

if (isDebug(context)) {
sourcePaths.addAll(loadInstantRunDexFile(appInfo));
}
return sourcePaths;
}

/**
* Identifies if the current VM has a native support for multidex.
*
* @return true, otherwise is false.
*
* @see MultiDexExtractor#getMultiDexPreferences(Context)
*/
private static boolean isVMMultidexCapable() {
boolean isMultidexCapable = false;
String vmVersion = System.getProperty("java.vm.version");
private void registerClass(Register register, String group, String className) {
try {
Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(vmVersion);
if (matcher.matches()) {
int major = Integer.parseInt(matcher.group(1));
int minor = Integer.parseInt(matcher.group(2));
isMultidexCapable = (major > 2) || ((major == 2) && (minor >= 1));
}
} catch (Exception ignore) {
}
String multidex = isMultidexCapable ? "has Multidex support" : "does not have Multidex support";
Log.i(AndServer.TAG, String.format("VM with version %s %s.", vmVersion, multidex));
return false;
}

/**
* Get instant run dex path, used to catch the branch usingApkSplits=false.
*/
private static List<String> loadInstantRunDexFile(ApplicationInfo appInfo) {
List<String> instantRunDexPaths = new ArrayList<>();

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && appInfo.splitSourceDirs != null) {
instantRunDexPaths.addAll(Arrays.asList(appInfo.splitSourceDirs));
Log.i(AndServer.TAG, "InstantRun support was found.");
} else {
try {
// Reflect instant run sdk to find where is the dex file.
Class pathsByInstantRun = Class.forName("com.android.tools.fd.runtime.Paths");
Method getDexFileDirectory = pathsByInstantRun.getMethod("getDexFileDirectory", String.class);
String dexDirectory = (String) getDexFileDirectory.invoke(null, appInfo.packageName);

File dexFolder = new File(dexDirectory);
if (dexFolder.exists() && dexFolder.isDirectory()) {
File[] dexFiles = dexFolder.listFiles();
for (File file : dexFiles) {
if (file.exists() && file.isFile() && file.getName().endsWith(".dex")) {
instantRunDexPaths.add(file.getAbsolutePath());
}
}
Log.i(AndServer.TAG, "InstantRun support was found.");
}

} catch (ClassNotFoundException e) {
Log.i(AndServer.TAG, "InstantRun support was not found.");
} catch (Exception e) {
Log.w(AndServer.TAG, "Finding InstantRun failed.", e);
Class<?> clazz = Class.forName(className);
if (clazz.isAssignableFrom(OnRegister.class)) {
OnRegister load = (OnRegister) clazz.newInstance();
load.onRegister(mContext, group, register);
}
} catch (ClassNotFoundException e) {
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}

return instantRunDexPaths;
}

private static boolean isDebug(Context context) {
ApplicationInfo appInfo = context.getApplicationInfo();
return (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@
public interface MessageConverter {

/**
* Convert a specific output to the response body. Some of the return values of handlers that cannot be recognized by
* Convert a specific output to the response body. Some of the return values of handlers that cannot be recognized
* by
* {@link ViewResolver} require a message converter to be converted to a response body.
*
* @param output output of handle.
* @param mediaType the content media type specified by the handler.
*/
ResponseBody convert(@NonNull Object output, @Nullable MediaType mediaType);
ResponseBody convert(@Nullable Object output, @Nullable MediaType mediaType);

/**
* Convert RequestBody to a object.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,6 @@ public void resolve(@Nullable View view, @NonNull HttpRequest request, @NonNull
}

Object output = view.output();
if (output == null) {
return;
}

if (view.rest()) {
resolveRest(output, request, response);
Expand All @@ -76,6 +73,8 @@ private void resolveRest(Object output, @NonNull HttpRequest request, @NonNull H
response.setBody((ResponseBody) output);
} else if (mConverter != null) {
response.setBody(mConverter.convert(output, obtainProduce(request)));
} else if (output == null) {
response.setBody(new StringBody(""));
} else if (output instanceof String) {
response.setBody(new StringBody(output.toString(), obtainProduce(request)));
} else {
Expand Down
45 changes: 23 additions & 22 deletions api/src/main/java/com/yanzhenjie/andserver/server/BasicServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,29 +79,30 @@ public void startup() {
Executors.getInstance().execute(new Runnable() {
@Override
public void run() {
mHttpServer = ServerBootstrap.bootstrap()
.setServerSocketFactory(mSocketFactory)
.setSocketConfig(
SocketConfig.custom()
.setSoKeepAlive(true)
.setSoReuseAddress(true)
.setTcpNoDelay(true)
.setSoTimeout(mTimeout)
.setBacklogSize(BUFFER)
.setRcvBufSize(BUFFER)
.setSndBufSize(BUFFER)
.setSoLinger(0)
.build()
)
.setLocalAddress(mInetAddress)
.setListenerPort(mPort)
.setSslContext(mSSLContext)
.setSslSetupHandler(new SSLSetup(mSSLSocketInitializer))
.setServerInfo(AndServer.INFO)
.registerHandler("*", requestHandler())
.setExceptionLogger(ExceptionLogger.NO_OP)
.create();
try {
mHttpServer = ServerBootstrap.bootstrap()
.setServerSocketFactory(mSocketFactory)
.setSocketConfig(
SocketConfig.custom()
.setSoKeepAlive(true)
.setSoReuseAddress(true)
.setTcpNoDelay(true)
.setSoTimeout(mTimeout)
.setBacklogSize(BUFFER)
.setRcvBufSize(BUFFER)
.setSndBufSize(BUFFER)
.setSoLinger(0)
.build()
)
.setLocalAddress(mInetAddress)
.setListenerPort(mPort)
.setSslContext(mSSLContext)
.setSslSetupHandler(new SSLSetup(mSSLSocketInitializer))
.setServerInfo(AndServer.INFO)
.registerHandler("*", requestHandler())
.setExceptionLogger(ExceptionLogger.NO_OP)
.create();

mHttpServer.start();
isRunning = true;

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath 'com.android.tools.build:gradle:3.6.3'
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5'
classpath 'com.yanzhenjie.andserver:plugin:2.1.3'
Expand Down
Loading

0 comments on commit 05899c5

Please sign in to comment.