Giter Club home page Giter Club logo

smartappupdates's Introduction

Android应用增量更新 - Smart App Updates


介绍

你所看到的,是一个用于Android应用程序增量更新的库。

包括客户端、服务端两部分代码。

原理

自从 Android 4.1 开始, Google Play 引入了应用程序的增量更新功能,App使用该升级方式,可节省约2/3的流量。

Smart app updates is a new feature of Google Play that introduces a better way of delivering app updates to devices. When developers publish an update, Google Play now delivers only the bits that have changed to devices, rather than the entire APK. This makes the updates much lighter-weight in most cases, so they are faster to download, save the device’s battery, and conserve bandwidth usage on users’ mobile data plan. On average, a smart app update is about 1/3 the sizeof a full APK update.

现在国内主流的应用市场也都支持应用的增量更新了。

增量更新的原理,就是将手机上已安装apk与服务器端最新apk进行二进制对比,得到差分包,用户更新程序时,只需要下载差分包,并在本地使用差分包与已安装apk,合成新版apk。

例如,当前手机中已安装微博V1,大小为12.8MB,现在微博发布了最新版V2,大小为15.4MB,我们对两个版本的apk文件差分比对之后,发现差异只有3M,那么用户就只需要要下载一个3M的差分包,使用旧版apk与这个差分包,合成得到一个新版本apk,提醒用户安装即可,不需要整包下载15.4M的微博V2版apk。

apk文件的差分、合成,可以通过 开源的二进制比较工具 bsdiff 来实现,又因为bsdiff依赖bzip2,所以我们还需要用到 bzip2

bsdiff中,bsdiff.c 用于生成差分包,bspatch.c 用于合成文件。

弄清楚原理之后,我们想实现增量更新,共需要做3件事:

  • 在服务器端,生成两个版本apk的差分包;

  • 在手机客户端,使用已安装的apk与这个差分包进行合成,得到新版的微博apk;

  • 校验新合成的apk文件是否完整,MD5或SHA1是否正确,如正确,则引导用户安装;

过程分析

1 生成差分包

这一步需要在服务器端来实现,一般来说,每当apk有新版本需要提示用户升级,都需要运营人员在后台管理端上传新apk,上传时就应该由程序生成与之前所有旧版本们与最新版的差分包。

例如: 你的apk已经发布了3个版,V1.0、V2.0、V3.0,这时候你要在后台发布V4.0,那么,当你在服务器上传最新的V4.0包时,服务器端就应该立即生成以下差分包:

  1. V1.0 ——> V4.0的差分包;
  2. V2.0 ——> V4.0的差分包;
  3. V3.0 ——> V4.0的差分包;

ApkPatchLibraryServer 工程即为 Java 语言实现的服务器端差分程序。

下面对ApkPatchLibraryServer做一些简单说明:

1.1 C部分

ApkPatchLibraryServer/jni 中,除了以下4个:

com_cundong_utils_DiffUtils.c

com_cundong_utils_DiffUtils.h

com_cundong_utils_PatchUtils.c

com_cundong_utils_PatchUtils.h

jni/bzip2目录中的文件,全部来自bzip2项目。

com_cundong_utils_DiffUtils.c

com_cundong_utils_DiffUtils.h

用于生成差分包。

com_cundong_utils_PatchUtils.c

com_cundong_utils_PatchUtils.h

用于合成新apk文件。

com_cundong_utils_DiffUtils.c 修改自 bsdiff/bsdiff.ccom_cundong_utils_PatchUtils.c修改自bsdiff/bspatch.c

我们在需要将jni中的C文件,build输出为动态链接库,以供Java调用(Window环境下生成的文件名为libApkPatchLibraryServer.dll,Unix-like系统下为libApkPatchLibraryServer.so,OSX下为libApkPatchLibraryServer.dylib)。

Build成功后,将该动态链接库文件,加入环境变量,供Java语言调用。

com_cundong_utils_DiffUtils.cJava_com_cundong_utils_DiffUtils_genDiff() 方法,用于生成差分包的:

JNIEXPORT jint JNICALL Java_com_cundong_utils_DiffUtils_genDiff(JNIEnv *env,
		jclass cls, jstring old, jstring new, jstring patch) {
	int argc = 4;
	char * argv[argc];
	argv[0] = "bsdiff";
	argv[1] = (char*) ((*env)->GetStringUTFChars(env, old, 0));
	argv[2] = (char*) ((*env)->GetStringUTFChars(env, new, 0));
	argv[3] = (char*) ((*env)->GetStringUTFChars(env, patch, 0));

	printf("old apk = %s \n", argv[1]);
	printf("new apk = %s \n", argv[2]);
	printf("patch = %s \n", argv[3]);

	int ret = genpatch(argc, argv);

	printf("genDiff result = %d ", ret);

	(*env)->ReleaseStringUTFChars(env, old, argv[1]);
	(*env)->ReleaseStringUTFChars(env, new, argv[2]);
	(*env)->ReleaseStringUTFChars(env, patch, argv[3]);

	return ret;
}

com_cundong_utils_PatchUtils.cJava_com_cundong_utils_PatchUtils_patch() 方法,用于合成新的APK;

JNIEXPORT jint JNICALL Java_com_cundong_utils_PatchUtils_patch
  (JNIEnv *env, jclass cls,
			jstring old, jstring new, jstring patch){
	int argc = 4;
	char * argv[argc];
	argv[0] = "bspatch";
	argv[1] = (char*) ((*env)->GetStringUTFChars(env, old, 0));
	argv[2] = (char*) ((*env)->GetStringUTFChars(env, new, 0));
	argv[3] = (char*) ((*env)->GetStringUTFChars(env, patch, 0));

	printf("old apk = %s \n", argv[1]);
	printf("patch = %s \n", argv[3]);
	printf("new apk = %s \n", argv[2]);

	int ret = applypatch(argc, argv);

	printf("patch result = %d ", ret);

	(*env)->ReleaseStringUTFChars(env, old, argv[1]);
	(*env)->ReleaseStringUTFChars(env, new, argv[2]);
	(*env)->ReleaseStringUTFChars(env, patch, argv[3]);
	return ret;
}

1.2 Java部分

com.cundong.utils包,为调用C语言的Java实现; com.cundong.apkdiff包,为apk差分程序的Demo; com.cundong.apkpatch包,为apk合并程序的Demo;

调用,com.cundong.utils.DiffUtilsgenDiff()方法,可以通过传入的新旧apk路径,得到差分包。

/**
 * 类说明: 	APK Diff工具类
 * 
 * @author     Cundong
 * @date 	      2013-9-6
 * @version  1.0
 */
public class DiffUtils {

	/**
	 * native方法 比较路径为oldPath的apk与newPath的apk之间差异,并生成patch包,存储于patchPath
	 * 
	 * 返回:0,说明操作成功
	 *  
	 * @param oldApkPath 示例:/sdcard/old.apk
	 * @param newApkPath 示例:/sdcard/new.apk
	 * @param patchPath  示例:/sdcard/xx.patch
	 * @return
	 */
	public static native int genDiff(String oldApkPath, String newApkPath, String patchPath);
}

调用,com.cundong.utils.PatchUtilspatch()方法,可以通过旧apk与差分包,合成为新apk。

/**
 * 类说明: 	APK Patch工具类
 * 
 * @author    Cundong
 * @date 	     2013-9-6
 * @version 1.0
 */
public class PatchUtils {

	/**
	 * native方法 使用路径为oldApkPath的apk与路径为patchPath的补丁包,合成新的apk,并存储于newApkPath
	 * 
	 * 返回:0,说明操作成功
	 * 
	 * @param oldApkPath 示例:/sdcard/old.apk
	 * @param newApkPath 示例:/sdcard/new.apk
	 * @param patchPath  示例:/sdcard/xx.patch
	 * @return
	 */
	public static native int patch(String oldApkPath, String newApkPath,
			String patchPath);
}

2.使用旧版apk与差分包,在客户端合成新apk

需要在手机客户端实现,ApkPatchLibrary 工程封装了这个过程。

2.1 C部分

同ApkPatchLibraryServer工程一样,ApkPatchLibrary/jni/bzip2 目录中所有文件都来自bzip2项目。

ApkPatchLibrary/jni/com_cundong_utils_PatchUtils.cApkPatchLibrary/jni/com_cundong_utils_PatchUtils.c实现文件的合并过程,其中com_cundong_utils_PatchUtils.c修改自bsdiff/bspatch.c

我们需要用NDK编译出一个libApkPatchLibrary.so文件,生成的so文件位于libs/armeabi/ 下,其他 Android 工程便可以使用该libApkPatchLibrary.so文件来合成apk(如果需要支持多种CPU架构需要自己配置)。

com_cundong_utils_PatchUtils.Java_com_cundong_utils_PatchUtils_patch()方法,即为生成差分包的代码:

/*
 * Class:     com_cundong_utils_PatchUtils
 * Method:    patch
 * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_com_cundong_utils_PatchUtils_patch(JNIEnv *env,
		jobject obj, jstring old, jstring new, jstring patch) {

	char * ch[4];
	ch[0] = "bspatch";
	ch[1] = (char*) ((*env)->GetStringUTFChars(env, old, 0));
	ch[2] = (char*) ((*env)->GetStringUTFChars(env, new, 0));
	ch[3] = (char*) ((*env)->GetStringUTFChars(env, patch, 0));

	__android_log_print(ANDROID_LOG_INFO, "ApkPatchLibrary", "old = %s ", ch[1]);
	__android_log_print(ANDROID_LOG_INFO, "ApkPatchLibrary", "new = %s ", ch[2]);
	__android_log_print(ANDROID_LOG_INFO, "ApkPatchLibrary", "patch = %s ", ch[3]);

	int ret = applypatch(4, ch);

	__android_log_print(ANDROID_LOG_INFO, "ApkPatchLibrary", "applypatch result = %d ", ret);

	(*env)->ReleaseStringUTFChars(env, old, ch[1]);
	(*env)->ReleaseStringUTFChars(env, new, ch[2]);
	(*env)->ReleaseStringUTFChars(env, patch, ch[3]);

	return ret;
}

2.2 Java部分

com.cundong.utils包,为调用C语言的Java实现;

调用,com.cundong.utils.PatchUtils中patch()方法,可以通过旧apk与差分包,合成为新apk。

/**
 * 类说明: 	APK Patch工具类
 * 
 * @author   Cundong
 * @date      2013-9-6
 * @version 1.0
 */
public class PatchUtils {

	/**
	 * native方法 使用路径为oldApkPath的apk与路径为patchPath的补丁包,合成新的apk,并存储于     newApkPath
	 * 
	 * 返回:0,说明操作成功
	 * 
	 * @param oldApkPath 示例:/sdcard/old.apk
	 * @param newApkPath 示例:/sdcard/new.apk
	 * @param patchPath  示例:/sdcard/xx.patch
	 * @return
	 */
	public static native int patch(String oldApkPath, String newApkPath,
			String patchPath);
}

3.校验新合成的apk文件

在执行patch之前,需要先读取本地安装旧版本APK的MD5或SHA1,判断当前安装的文件是否为合法版本,同样,patch得到新包之后,也需要对它进行MD5或SHA1校验,校验失败,说明合成过程有问题。

注意事项

增量更新的前提条件,是在手机客户端能让我们读取到当前应用程序安装后的源apk,如果获取不到源apk,那么就无法进行增量更新了,另外,如果你的应用程序不是很大,比如只有2、3M,那么完全没有必要使用增量更新,增量更新只适用于apk包比较大的情况,比如手机游戏客户端。

一些说明

  • ApkPatchLibraryServer:服务器端生成差分包工程,使用Java实现;

  • ApkPatchLibrary:客户端使用的apk合成库,用于生成libApkPatchLibrary.so,使用Eclipse开发;

  • ApkPatchLibrarySample:一个Sample,手机上安装 Weibo5.5.apk,通过与SD卡上预先存放的weibo.patch文件进行合并,得到Weibo5.6.apk,使用AndroidStudio开发。

  • 二进制差分或许有更好的实现方案,如:xdelta;

另外, ApkPatchLibraryServer、ApkPatchLibrarySample 中用到的Weibo5.5.apk,Weibo5.6.apk,以及使用ApkPatchLibraryServer生成的差分包(Weibo5.5.apk->Weibo5.6.apk), 都通过云盘共享了

关于我

Update

1.目前的做法只是提供了一个例子,并没有做成开源库,打算这几天改进一下,做成一个开源库,push到GitHub上,开发ing..(2014年,8月31日)

2.已经大幅度重构原代码,并将原来的Demo程序提取成为开源库,欢迎所有人Watch、Star、Fork。(2014年,9月2日)

3.修改ReadMe.md,更加清晰的说明开源库的使用,同时进一步重构代码。(2014年,10月4日晚)

4.调整ApkPatchLibraryServer工程目录。(2015年,4月24日)

5.上传一个演示demo ApkPatchLibrarySample.apk。(2015-4-26)

6.ApkPatchLibrarySample重新使用AndroidStudio开发,修改文件MD5的对比逻辑。(2015-12-22)

License

Copyright 2015 Cundong

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

smartappupdates's People

Contributors

cundong avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

smartappupdates's Issues

差异包问题请教

请教下:如果上个版本是15M,现在新的版本,进行了很多增删改之后,只有8M,这时候的差异包是怎么生成的?

你好,请问下在windows下如何编译成dll文件?

按照jni的几个步骤都很顺利,最后一步在编译成dll文件时,报以下错误。应该是C代码中使用linux系统库的原因。麻烦问下,你这边是如何编译的?而且在项目中也没有看到dll文件。是不是使用cygwin类似的编译器来编译的?
qq 20150311135436

这个差分包的前提是 混淆文件不改动吗?

这个差分包的前提是 混淆文件不改动吗?
如果混淆文件改动了,那么混淆出来的Mapping 文件也会与旧的apk 文件很大不同,这个也可以使用com.cundong.utils.DiffUtils 来找到差分包吗?
原理是什么,可否解释下,谢谢!

版本问题

这个增量升级方案在4.1以下的版本不能用?

工程导入问题

我导入到eclipse 中以后,在String.xml 中报actionBarStyle 和 titleTextStyle 无法找到,怎么改呢?

dlopen failed: cannot locate symbol "__fwrite_chk" referenced by 错误

AndroidRuntime: java.lang.UnsatisfiedLinkError:
dlopen failed: cannot locate symbol "__fwrite_chk" referenced by "/data/app/com
.android.sign.appstore-1/lib/arm64/libApkPatchLibrary.so "...

如上,使用您的https://github.com/cundong/SmartAppUpdates
github上的库,编译成64位so,。mk如下,

include $(CLEAR_VARS)

LOCAL_MODULE := libApkPatchLibrary
LOCAL_SRC_FILES := com_cundong_utils_PatchUtils.c

APP_PLATFORM := android-16
LOCAL_LDLIBS := -lz -llog

include $(BUILD_SHARED_LIBRARY)

但是使用的时候会直接报错,log是最上方那个,求解答是否有碰到过

生成so库的时候报错

D:/Users/""/AppData/Local/Android/sdk/ndk-bundle/build//../toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/lib/gcc/arm-linux-androideabi/4.9.x/../../../../arm-linux-androideabi/bin\ld: error: D:""\workspace-as\mvp\MyNewUpdatelib\app\build\intermediates\ndk\debug\obj/local/armeabi-v7a/objs/ApkPatchLibrary/D_""\workspace-as\mvp\MyNewUpdatelib\app\src\main\jni\bzip2\blocksort.o: multiple definition of 'BZ2_blockSort'
D:/Users/""/AppData/Local/Android/sdk/ndk-bundle/build//../toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/lib/gcc/arm-linux-androideabi/4.9.x/../../../../arm-linux-androideabi/bin\ld: D:""\workspace-as\mvp\MyNewUpdatelib\app\build\intermediates\ndk\debug\obj/local/armeabi-v7a/objs/ApkPatchLibrary/D_""\workspace-as\mvp\MyNewUpdatelib\app\src\main\jni\com_cundong_utils_PatchUtils.o: previous definition here

增量升级的MD5校验问题

你好,我看了下你的demo,那个校验md5的东西,有一个模拟请求服务器的过程,得到当前的md5值和新的md5值,这个两个值,在代码里和本地的一个路径md5值进行比较,那后台的这两个值,他们是怎么确定的呢????????大神期待你的解答。。。。

生成so包得时候怎么老是报这个错误

/Users/intelligentdroid/Desktop/pengpeng/code/apkUpdate/apkpatchlibraryserver/jni/com_cundong_utils_DiffUtils.c:264: error: undefined reference to 'errx'
/Users/intelligentdroid/Desktop/pengpeng/code/apkUpdate/apkpatchlibraryserver/jni/com_cundong_utils_DiffUtils.c:272: error: undefined reference to 'err'
/Users/intelligentdroid/Desktop/pengpeng/code/apkUpdate/apkpatchlibraryserver/jni/com_cundong_utils_DiffUtils.c:275: error: undefined reference to 'err'
/Users/intelligentdroid/Desktop/pengpeng/code/apkUpdate/apkpatchlibraryserver/jni/com_cundong_utils_DiffUtils.c:287: error: undefined reference to 'err'
/Users/intelligentdroid/Desktop/pengpeng/code/apkUpdate/apkpatchlibraryserver/jni/com_cundong_utils_DiffUtils.c:290: error: undefined reference to 'err'
/Users/intelligentdroid/Desktop/pengpeng/code/apkUpdate/apkpatchlibraryserver/jni/com_cundong_utils_DiffUtils.c:316: error: undefined reference to 'BZ2_bzWriteOpen'
/Users/intelligentdroid/Desktop/pengpeng/code/apkUpdate/apkpatchlibraryserver/jni/com_cundong_utils_DiffUtils.c:317: error: undefined reference to 'errx'
/Users/intelligentdroid/Desktop/pengpeng/code/apkUpdate/apkpatchlibraryserver/jni/com_cundong_utils_DiffUtils.c:401: error: undefined reference to 'BZ2_bzWrite'
/Users/intelligentdroid/Desktop/pengpeng/code/apkUpdate/apkpatchlibraryserver/jni/com_cundong_utils_DiffUtils.c:403: error: undefined reference to 'errx'

直接对apk文件进行diff并没有发挥出该算法的优势

apk文件是一种压缩格式,它是基于 ZIP 文件格式的一个预定义组织格式; 类似于java的jar包;
diff算法(包括bsdiff和hdiff)的原理并不能很好的直接处理压缩后的数据,而是在解压状态处理更好;

为什么现在很多时候看起来也能处理?
这是因为一般的apk包里的每个�文件单独压缩的,还有部分文件是原样储存的,比如jpg、png、mp3等无压缩空间的文件,如果2个文件数据一样,压缩参数和算法版本又一致(或原样储存),那么这2个文件的压缩的结果也一样;所以diff算法还能够识别出来�这块数据“没变”; 一旦环境发生变化,diff就会失效;

所以想要发布的补丁稳定且最小化,应该对apk先解压再执行diff;
类似这样的算法流程:
diff过程:遍历2个版本apk包中的文件,将不同名或同名但数据有不同的所有文件解压,按一种固定顺序合并成2个数据块; 对这2个数据块执行diff的到差异数据+记录文件信息(还可以加上校验) 生成即补丁(可能还可以压缩);
patch过程: 旧apk包解压后合并补丁后(�可执行文件可能需要设置可执行属性) 重新打包制作成apk

demo工程已经比较老了

在6.0以上android手机上,点击start报错。
解决方案:
1、动态申请sd权限、安装app权限
2、使用FileProvider

so文件32 64位版本冲突?

我用了项目中自带的libApkPatchLibrary.so 文件, 放到服务器中执行的时候碰到的错误如下
查了下资料说是 jdk 版本冲突, 谁有没有64位的版本so文件?

[root@bogon apkPatch]# java -Djava.library.path=/public/service/apkPatch/so -jar apkDiff2.jar
Java HotSpot(TM) 64-Bit Server VM warning: You have loaded library /public/service/apkPatch/so/libApkPatchLibrary.so which might have disabled stack guard. The VM will try to fix the stack guard now.
It's highly recommended that you fix the library with 'execstack -c <libfile>', or link it with '-z noexecstack'.
Exception in thread "main" java.lang.UnsatisfiedLinkError: /public/service/apkPatch/so/libApkPatchLibrary.so: /public/service/apkPatch/so/libApkPatchLibrary.so: 错误 ELF 类: ELFCLASS32 (Possible cause: architecture word width mismatch)
        at java.lang.ClassLoader$NativeLibrary.load(Native Method)
        at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1929)
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1847)
        at java.lang.Runtime.loadLibrary0(Runtime.java:870)
        at java.lang.System.loadLibrary(System.java:1119)
        at com.cundong.apkdiff.ApkDiffDemo.<clinit>(ApkDiffDemo.java:32)

编码问题

Mac 生成diff包出现这个问题,怎么解决呢?

未能打开文稿“diff”。文本编码“Unicode (UTF-8)”不适用。

64 bit os error

java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.test.test-2/base.apk"],nativeLibraryDirectories=[/data/app/com.liankai.test-2/lib/arm64, /vendor/lib64, /system/lib64]]] couldn't find "libapkpatch.so"

Can you build a 64bit so lib please? Thanks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.