├── AppAddUpdateDemo ├── .gitignore ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── wang │ │ │ └── appaddupdatedemo │ │ │ └── ApplicationTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── wang │ │ │ │ └── appaddupdatedemo │ │ │ │ ├── MainActivity.java │ │ │ │ └── PermissionUtil.java │ │ ├── jniLibs │ │ │ ├── armeabi-v7a │ │ │ │ └── libApkPatchLibrary.so │ │ │ ├── armeabi │ │ │ │ └── libApkPatchLibrary.so │ │ │ └── x86 │ │ │ │ └── libApkPatchLibrary.so │ │ └── res │ │ │ ├── layout │ │ │ └── activity_main.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── wang │ │ └── appaddupdatedemo │ │ └── ExampleUnitTest.java ├── appupdate │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── wang │ │ │ └── appupdate │ │ │ └── util │ │ │ ├── ApkUtil.java │ │ │ ├── PatchUtil.java │ │ │ └── SignUtil.java │ │ └── res │ │ └── values │ │ └── strings.xml ├── bintray.gradle ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── AppUpdate ├── .gitignore ├── app │ ├── .gitignore │ ├── app-release.apk │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── wang │ │ │ └── appupdate │ │ │ └── ApplicationTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── wang │ │ │ │ └── appupdate │ │ │ │ ├── MainActivity.java │ │ │ │ └── util │ │ │ │ ├── ApkUtil.java │ │ │ │ ├── PatchUtil.java │ │ │ │ └── SignUtil.java │ │ ├── jni │ │ │ ├── bzip2 │ │ │ │ ├── blocksort.c │ │ │ │ ├── bzip2.c │ │ │ │ ├── bzip2recover.c │ │ │ │ ├── bzlib.c │ │ │ │ ├── bzlib.h │ │ │ │ ├── bzlib_private.h │ │ │ │ ├── compress.c │ │ │ │ ├── crctable.c │ │ │ │ ├── decompress.c │ │ │ │ ├── huffman.c │ │ │ │ ├── randtable.c │ │ │ │ └── readMe.txt │ │ │ ├── com_wang_appupdate_util_PatchUtil.h │ │ │ ├── diff.c │ │ │ ├── patch.c │ │ │ └── util │ │ │ │ └── android_log_print.h │ │ └── res │ │ │ ├── layout │ │ │ └── activity_main.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── wang │ │ └── appupdate │ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── LICENSE └── README.md /AppAddUpdateDemo/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | -------------------------------------------------------------------------------- /AppAddUpdateDemo/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /AppAddUpdateDemo/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'android-apt' 3 | 4 | android { 5 | compileSdkVersion 25 6 | buildToolsVersion "25.0.0" 7 | 8 | defaultConfig { 9 | applicationId "com.wang.appaddupdatedemo" 10 | minSdkVersion 17 11 | targetSdkVersion 25 12 | versionCode 1 13 | versionName "1.0" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | compile fileTree(dir: 'libs', include: ['*.jar']) 25 | testCompile 'junit:junit:4.12' 26 | compile 'com.android.support:appcompat-v7:25.0.0' 27 | compile project(path: ':appupdate') 28 | compile 'io.reactivex:rxandroid:1.2.1' 29 | // Because RxAndroid releases are few and far between, it is recommended you also 30 | // explicitly depend on RxJava's latest version for bug fixes and new features. 31 | compile 'io.reactivex:rxjava:1.1.10' 32 | compile 'com.jakewharton:butterknife:8.4.0' 33 | apt 'com.jakewharton:butterknife-compiler:8.4.0' 34 | // compile 'com.wang.appupdate:appupdate:1.0.1' 35 | } 36 | -------------------------------------------------------------------------------- /AppAddUpdateDemo/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /AppAddUpdateDemo/app/src/androidTest/java/com/wang/appaddupdatedemo/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.wang.appaddupdatedemo; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /AppAddUpdateDemo/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /AppAddUpdateDemo/app/src/main/java/com/wang/appaddupdatedemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.wang.appaddupdatedemo; 2 | 3 | import android.Manifest; 4 | import android.os.Bundle; 5 | import android.os.Environment; 6 | import android.support.annotation.NonNull; 7 | import android.support.v4.app.ActivityCompat; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.view.View; 10 | import android.widget.Button; 11 | import android.widget.ProgressBar; 12 | import android.widget.TextView; 13 | import android.widget.Toast; 14 | 15 | import com.wang.appupdate.util.PatchUtil; 16 | import com.wang.appupdate.util.SignUtil; 17 | 18 | import java.io.File; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | 22 | import butterknife.BindView; 23 | import butterknife.ButterKnife; 24 | import butterknife.OnClick; 25 | import rx.Observable; 26 | import rx.Subscriber; 27 | import rx.Subscription; 28 | import rx.android.schedulers.AndroidSchedulers; 29 | import rx.functions.Func1; 30 | import rx.schedulers.Schedulers; 31 | 32 | public class MainActivity extends AppCompatActivity { 33 | 34 | private static final String PATH = Environment.getExternalStorageDirectory().getAbsolutePath(); 35 | 36 | private static final int REQUEST_PERMISSIONS = 100; 37 | 38 | private String mOldApk = PATH + "/jiudeng1.apk"; 39 | private String mNewApk = PATH + "/jiudeng2.apk"; 40 | private String mPatchPath = PATH + "/test.patch"; 41 | private String mNewApk2 = PATH + "/jiudeng3.apk"; 42 | 43 | @BindView(R.id.old_apk_tv) 44 | TextView mOldApkTV; 45 | @BindView(R.id.old_apk_size_tv) 46 | TextView mOldApkSizeTV; 47 | @BindView(R.id.old_apk_md5_tv) 48 | TextView mOldApkMd5TV; 49 | @BindView(R.id.new_apk_tv) 50 | TextView mNewApkTV; 51 | @BindView(R.id.new_apk_size_tv) 52 | TextView mNewApkSizeTV; 53 | @BindView(R.id.new_apk_md5_tv) 54 | TextView mNewApkMd5TV; 55 | @BindView(R.id.patch_tv) 56 | TextView mPatchTV; 57 | @BindView(R.id.patch_size_tv) 58 | TextView mPatchSizeTV; 59 | @BindView(R.id.new_apk_2_tv) 60 | TextView mNewApk2TV; 61 | @BindView(R.id.new_apk_2_size_tv) 62 | TextView mNewApk2SizeTV; 63 | @BindView(R.id.new_apk_2_md5_tv) 64 | TextView mNewApk2Md5TV; 65 | @BindView(R.id.msg_tv) 66 | TextView mMsgTV; 67 | @BindView(R.id.get_patch_btn) 68 | Button mGetPatchBtn; 69 | @BindView(R.id.get_new_apk_btn) 70 | Button mGetNewApkBtn; 71 | @BindView(R.id.delete_btn) 72 | Button mDeleteBtn; 73 | @BindView(R.id.loading) 74 | ProgressBar mLoading; 75 | 76 | private Subscription mSubscription; 77 | 78 | private boolean isSuccess; 79 | 80 | @Override 81 | protected void onCreate(Bundle savedInstanceState) { 82 | super.onCreate(savedInstanceState); 83 | setContentView(R.layout.activity_main); 84 | ButterKnife.bind(this); 85 | requestPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE); 86 | 87 | } 88 | 89 | @Override 90 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 91 | List allPermissions = Arrays.asList(permissions); 92 | if (!PermissionUtil.verifyPermissions(allPermissions, grantResults)) { 93 | Toast.makeText(this, "权限请求失败", Toast.LENGTH_SHORT).show(); 94 | isSuccess = false; 95 | } 96 | else { 97 | isSuccess = true; 98 | } 99 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 100 | } 101 | 102 | public void requestPermissions(String... permissions) { 103 | 104 | if (PermissionUtil.checkSelfPermission(this, permissions)) { 105 | isSuccess = false; 106 | ActivityCompat.requestPermissions(this, permissions, 0); 107 | } 108 | else { 109 | isSuccess = true; 110 | } 111 | } 112 | 113 | @OnClick({R.id.get_patch_btn, R.id.get_new_apk_btn, R.id.delete_btn}) 114 | public void onClick(View view) { 115 | if (!isSuccess){ 116 | return; 117 | } 118 | switch (view.getId()) { 119 | case R.id.get_patch_btn: 120 | clearText(); 121 | File file = new File(mOldApk); 122 | File newFile = new File(mNewApk); 123 | if (!file.exists()) { 124 | mMsgTV.setText("旧APK不存在"); 125 | return; 126 | } 127 | if (!newFile.exists()) { 128 | mMsgTV.setText("新APK不存在"); 129 | return; 130 | } 131 | File p = new File(mPatchPath); 132 | if (p.exists()) { 133 | p.delete(); 134 | } 135 | setEnabled(false); 136 | mOldApkTV.setText(mOldApk); 137 | mOldApkSizeTV.setText(String.format("%.2f M", getFileSize(file))); 138 | mOldApkMd5TV.setText(SignUtil.getMd5ByFile(file)); 139 | 140 | mNewApkTV.setText(mNewApk); 141 | mNewApkSizeTV.setText(String.format("%.2f M", getFileSize(newFile))); 142 | mNewApkMd5TV.setText(SignUtil.getMd5ByFile(newFile)); 143 | mSubscription = Observable.just("") 144 | .map(new Func1() { 145 | @Override 146 | public Integer call(String s) { 147 | return PatchUtil.diff(mOldApk, mNewApk, mPatchPath); 148 | } 149 | }) 150 | .map(new Func1() { 151 | @Override 152 | public String call(Integer integer) { 153 | return checkResult(integer); 154 | } 155 | }) 156 | .subscribeOn(Schedulers.io()) 157 | .observeOn(AndroidSchedulers.mainThread()) 158 | .subscribe(new Subscriber() { 159 | @Override 160 | public void onCompleted() { 161 | 162 | } 163 | 164 | @Override 165 | public void onError(Throwable e) { 166 | setEnabled(true); 167 | mMsgTV.setText(e.toString()); 168 | } 169 | 170 | @Override 171 | public void onNext(String s) { 172 | mMsgTV.setText(s); 173 | setEnabled(true); 174 | if (s.equals("success")) { 175 | mPatchTV.setText(mPatchPath); 176 | mPatchSizeTV.setText(String.format("%.2f M", getFileSize(new File(mPatchPath)))); 177 | } 178 | } 179 | }); 180 | break; 181 | case R.id.get_new_apk_btn: 182 | File oldApk = new File(mOldApk); 183 | File patch = new File(mPatchPath); 184 | final File newApk2 = new File(mNewApk2); 185 | if (!oldApk.exists()) { 186 | mMsgTV.setText("旧APK不存在"); 187 | return; 188 | } 189 | if (!patch.exists()) { 190 | mMsgTV.setText("补丁文件不存在"); 191 | return; 192 | } 193 | if (newApk2.exists()) { 194 | newApk2.delete(); 195 | } 196 | setEnabled(false); 197 | mSubscription = Observable.just("") 198 | .map(new Func1() { 199 | @Override 200 | public Integer call(String s) { 201 | return PatchUtil.patch(mOldApk, mNewApk2, mPatchPath); 202 | } 203 | }) 204 | .map(new Func1() { 205 | @Override 206 | public String call(Integer integer) { 207 | return checkResult(integer); 208 | } 209 | }) 210 | .subscribeOn(Schedulers.io()) 211 | .observeOn(AndroidSchedulers.mainThread()) 212 | .subscribe(new Subscriber() { 213 | @Override 214 | public void onCompleted() { 215 | 216 | } 217 | 218 | @Override 219 | public void onError(Throwable e) { 220 | setEnabled(true); 221 | mMsgTV.setText(e.toString()); 222 | } 223 | 224 | @Override 225 | public void onNext(String s) { 226 | mMsgTV.setText(s); 227 | setEnabled(true); 228 | if (s.equals("success")) { 229 | mNewApk2TV.setText(mNewApk2); 230 | mNewApk2SizeTV.setText(String.format("%.2f M", getFileSize(newApk2))); 231 | mNewApk2Md5TV.setText(SignUtil.getMd5ByFile(newApk2)); 232 | } 233 | } 234 | }); 235 | break; 236 | case R.id.delete_btn: 237 | clearText(); 238 | setEnabled(false); 239 | File la = new File(mNewApk2); 240 | if (la.exists()){ 241 | la.delete(); 242 | } 243 | File ji = new File(mPatchPath); 244 | if (ji.exists()){ 245 | ji.delete(); 246 | } 247 | setEnabled(true); 248 | break; 249 | } 250 | } 251 | 252 | private String checkResult(int ret) { 253 | switch (ret) { 254 | case 0: 255 | return "success"; 256 | case 1: 257 | return "缺少文件路径"; 258 | case 2: 259 | return "读取旧apk失败"; 260 | case 3: 261 | return "读取新的apk失败"; 262 | case 4: 263 | return "打开或读取patch文件失败"; 264 | case 5: 265 | return "内存分配失败"; 266 | case 6: 267 | return "创建、打开或读取patch文件失败"; 268 | case 7: 269 | return "计算文件差异性或者写入patch文件失败"; 270 | case 8: 271 | return "计算压缩的大小差异数据失败"; 272 | case 9: 273 | return "无用补丁"; 274 | case 10: 275 | return "合并apk失败"; 276 | } 277 | return "未知错误"; 278 | } 279 | 280 | private float getFileSize(File file) { 281 | return (float) (file.length() / (1024 * 1024 * 1.0)); 282 | } 283 | 284 | private void clearText() { 285 | mOldApkTV.setText(""); 286 | mOldApkSizeTV.setText(""); 287 | mOldApkMd5TV.setText(""); 288 | mNewApkTV.setText(""); 289 | mNewApkSizeTV.setText(""); 290 | mNewApkMd5TV.setText(""); 291 | mPatchTV.setText(""); 292 | mPatchSizeTV.setText(""); 293 | mNewApk2TV.setText(""); 294 | mNewApk2SizeTV.setText(""); 295 | mNewApk2Md5TV.setText(""); 296 | mMsgTV.setText(""); 297 | } 298 | 299 | private void setEnabled(boolean enabled) { 300 | mLoading.setVisibility(!enabled ? View.VISIBLE : View.GONE); 301 | mGetNewApkBtn.setEnabled(enabled); 302 | mGetPatchBtn.setEnabled(enabled); 303 | mDeleteBtn.setEnabled(enabled); 304 | } 305 | 306 | @Override 307 | public void onBackPressed() { 308 | if (mSubscription != null) { 309 | mSubscription.unsubscribe(); 310 | } 311 | super.onBackPressed(); 312 | } 313 | 314 | @Override 315 | protected void onDestroy() { 316 | if (mSubscription != null) { 317 | mSubscription.unsubscribe(); 318 | } 319 | super.onDestroy(); 320 | } 321 | 322 | 323 | } 324 | -------------------------------------------------------------------------------- /AppAddUpdateDemo/app/src/main/java/com/wang/appaddupdatedemo/PermissionUtil.java: -------------------------------------------------------------------------------- 1 | package com.wang.appaddupdatedemo; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.pm.PackageManager; 6 | import android.support.v4.content.ContextCompat; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Utility class that wraps access to the runtime permissions API in M and provides basic helper 12 | * methods. 13 | */ 14 | public abstract class PermissionUtil { 15 | 16 | 17 | public static boolean checkSelfPermission(Context context, String... permissions){ 18 | 19 | if (permissions.length < 1){ 20 | return false; 21 | } 22 | 23 | for (String permission : permissions){ 24 | if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED){ 25 | return true; 26 | } 27 | } 28 | 29 | return false; 30 | } 31 | /** 32 | * Check that all given permissions have been granted by verifying that each entry in the 33 | * given array is of the value {@link PackageManager#PERMISSION_GRANTED}. 34 | * 35 | * @see Activity#onRequestPermissionsResult(int, String[], int[]) 36 | */ 37 | public static boolean verifyPermissions(int[] grantResults) { 38 | // At least one result must be checked. 39 | if(grantResults.length < 1){ 40 | return false; 41 | } 42 | 43 | // Verify that each required permission has been granted, otherwise return false. 44 | for (int result : grantResults) { 45 | if (result != PackageManager.PERMISSION_GRANTED) { 46 | return false; 47 | } 48 | } 49 | return true; 50 | } 51 | 52 | /** 53 | * Check that all given permissions have been granted by verifying that each entry in the 54 | * given array is of the value {@link PackageManager#PERMISSION_GRANTED}. 55 | * 56 | * @see Activity#onRequestPermissionsResult(int, String[], int[]) 57 | * 58 | * @param permissions all permissions 59 | */ 60 | public static boolean verifyPermissions(List permissions, int[] grantResults) { 61 | // At least one result must be checked. 62 | if(grantResults.length < 1){ 63 | return false; 64 | } 65 | 66 | // Verify that each required permission has been granted, otherwise return false. 67 | for (int i = 0; i < grantResults.length ; i++) { 68 | if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { 69 | return false; 70 | } 71 | permissions.remove(i); 72 | } 73 | return true; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /AppAddUpdateDemo/app/src/main/jniLibs/armeabi-v7a/libApkPatchLibrary.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingwang666/AppAddUpdate/2a5fe4dd91c6781eb72b30f452333bbb5f4461da/AppAddUpdateDemo/app/src/main/jniLibs/armeabi-v7a/libApkPatchLibrary.so -------------------------------------------------------------------------------- /AppAddUpdateDemo/app/src/main/jniLibs/armeabi/libApkPatchLibrary.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingwang666/AppAddUpdate/2a5fe4dd91c6781eb72b30f452333bbb5f4461da/AppAddUpdateDemo/app/src/main/jniLibs/armeabi/libApkPatchLibrary.so -------------------------------------------------------------------------------- /AppAddUpdateDemo/app/src/main/jniLibs/x86/libApkPatchLibrary.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingwang666/AppAddUpdate/2a5fe4dd91c6781eb72b30f452333bbb5f4461da/AppAddUpdateDemo/app/src/main/jniLibs/x86/libApkPatchLibrary.so -------------------------------------------------------------------------------- /AppAddUpdateDemo/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 17 | 18 | 24 | 25 | 32 | 33 | 40 | 41 | 48 | 49 | 56 | 57 | 64 | 65 | 71 | 72 | 78 | 79 | 86 | 87 | 94 | 95 | 102 | 103 | 110 | 111 |