APP优雅进行SSL证书校验——(一)服务器篇

https://zhuanlan.zhihu.com/p/36628482

生成公钥 pin

https://www.ssllabs.com/ssltest/analyze.html

公钥和证书 pin 介绍

https://m.freebuf.com/articles/endpoint/341407.html

https://medium.com/@Dranko/certificate-pinning-your-android-and-ios-apps-6506972ecb37

证书转换

1
2
3
4
5
6
7
8
// crt -> cer
openssl x509 -in 2023.jin.com.crt -out 2023.jin.com.cer -outform der
openssl s_client -connect pre-service.jfzfund.com:443 </dev/null 2>/dev/null | openssl x509 -outform DER > myWebsite.cer

// cer -> pem
openssl x509 -in dnufzfj0.kecezfj -pubkey -noout > dnufzfj0.pem
// 公钥文件
openssl x509 -inform der -in dnufzfj0.cer -out certificate.pem

通过 chrome 也可以导出.cer 格式证书

android更新apk方式

禁默安装

App 内升级调用 PackageInstaller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public static void installApk1(
final @NonNull File apkFile, final @NonNull PermissionsActivity permissionsActivity) {
String apkName = "appInstall.apk";
long apkFileLength = apkFile.length();

PackageManager pm = permissionsActivity.getPackageManager();
PackageInstaller packageInstaller = pm.getPackageInstaller();
packageInstaller.registerSessionCallback(new PackageInstaller.SessionCallback() {
@Override
public void onCreated(int sessionId) {
Log.e(TAG, "Install Start sessionId-> " + sessionId);
}

@Override
public void onBadgingChanged(int sessionId) {}

@Override
public void onActiveChanged(int sessionId, boolean active) {}

@Override
public void onProgressChanged(int sessionId, float progress) {}

@Override
public void onFinished(int sessionId, boolean success) {
if (success) {
Log.e(TAG, "Silent Install Success");
} else {
Log.e(TAG, "Silent Install Fail");
}
}
});

int count;
int sessionId;
byte[] buffer = new byte[65536];

InputStream inputStream;
OutputStream outputStream;
PackageInstaller.Session session = null;
PackageInstaller.SessionParams sessionParams;

try {
sessionParams = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionId = packageInstaller.createSession(sessionParams);
session = packageInstaller.openSession(sessionId);

inputStream = new FileInputStream(apkFile);
outputStream = session.openWrite(apkName, 0, apkFileLength);

while((count = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, count);
float progress = ((float)count / (float)apkFileLength);
session.setStagingProgress(progress);
}
session.fsync(outputStream);

inputStream.close();
outputStream.flush();
outputStream.close();

Intent intent = new Intent();
intent.setAction("xinan.intent.action.APP_INSTALL");
PendingIntent pendingIntent = PendingIntent.getBroadcast(permissionsActivity.getBaseContext(), 0, intent, 0);
session.commit(pendingIntent.getIntentSender());
} catch (Exception e) {
e.printStackTrace();
if (session != null) {
session.abandon();
}
} finally {
}
}

注册系统服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
public class AutoStartAppReceiver extends BroadcastReceiver {
private final String TAG = "AutoStartReceiver";
private final String ACTION_BOOT = "android.intent.action.BOOT_COMPLETED";
private final String ACTION_CUSTOM_BOOT = "xinan.intent.action.BOOT_COMPLETED";
private final String ACTION_CUSTOM_AFTER_SYSTEM_UI = "xinan.intent.action.AfterStartSystemUI";
private final String ACTION_APP_INSTALL = "xinan.intent.action.APP_INSTALL";

/**
* 接收广播消息后都会进入 onReceive 方法,然后要做的就是对相应的消息做出相应的处理
*
* @param context 表示广播接收器所运行的上下文
* @param intent 表示广播接收器收到的Intent
*/
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.i(TAG, "onReceive:" + action);
if (ACTION_BOOT.equals(action)
|| ACTION_CUSTOM_AFTER_SYSTEM_UI.equals(action)
|| ACTION_CUSTOM_BOOT.equals(action)) {
launchApp(context);
} else if (ACTION_APP_INSTALL.equals(action)) {
Bundle extras = intent.getExtras();
int status = extras.getInt(PackageInstaller.EXTRA_STATUS);
String message = extras.getString(PackageInstaller.EXTRA_STATUS_MESSAGE);
Log.i(TAG, "onReceive status:" + status + " message:" + message);

switch (status) {
case PackageInstaller.STATUS_PENDING_USER_ACTION:
break;
case PackageInstaller.STATUS_SUCCESS:
Log.d(TAG, "Install succeeded!");
launchApp(context);
break;
case PackageInstaller.STATUS_FAILURE:
case PackageInstaller.STATUS_FAILURE_ABORTED:
case PackageInstaller.STATUS_FAILURE_BLOCKED:
case PackageInstaller.STATUS_FAILURE_CONFLICT:
case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE:
case PackageInstaller.STATUS_FAILURE_INVALID:
case PackageInstaller.STATUS_FAILURE_STORAGE:
Log.e(TAG, "Install failed!" + status + ", " + message);
break;
default:
Log.e(TAG, "Unrecognized status received from installer: " + status);
}
}
}

private void launchApp(Context context) {
try {
String packageName = "com.xinan.ubox";
PackageManager pm = context.getPackageManager();
if (checkApp(pm, packageName)) {
if (!isRunning(context, packageName)) {
Log.i(TAG, "app start:" + packageName);
Intent intentMainActivity = pm.getLaunchIntentForPackage(packageName);
intentMainActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intentMainActivity);
} else {
Log.i(TAG, "app is running:" + packageName);
}
} else {
Log.i(TAG, "app not exist:" + packageName);
}
} catch (Exception e) {
Log.i(TAG, "onReceive error:" + e.getLocalizedMessage());
}
}

private boolean checkApp(PackageManager pm, String packageName) {
try {
PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
return packageInfo != null;
} catch (Exception e) {
return false;
}
}

public boolean isRunning(Context context, String packageName) {
try {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//获取当前所有存活task的信息
List<ActivityManager.RunningTaskInfo> processInfos = activityManager.getRunningTasks(Integer.MAX_VALUE);
//遍历,若task的name与当前task的name相同,则返回true,否则,返回false
for (ActivityManager.RunningTaskInfo process : processInfos) {
if (process.baseActivity.getPackageName().equals(packageName)
|| process.topActivity.getPackageName().equals(packageName)) {
return true;
}
}

return false;
} catch (Exception e) {
Log.i(TAG, "isRunning error:" + e.getLocalizedMessage());

return false;
}
}
}

manifest 注册接收器

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--注册接收系统开机广播消息的广播接收者-->
<receiver
android:name=".AutoStartAppReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="xinan.intent.action.AfterStartSystemUI" />
<action android:name="xinan.intent.action.APP_INSTALL" />
<action android:name="xinan.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
</receiver>

参考资料

https://blog.csdn.net/sinat_35622297/article/details/83995957?spm=1001.2101.3001.6650.11&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-11-83995957-blog-128485823.235%5Ev27%5Epc_relevant_multi_platform_whitelistv3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-11-83995957-blog-128485823.235%5Ev27%5Epc_relevant_multi_platform_whitelistv3&utm_relevant_index=17

https://stackoverflow.com/questions/58085899/android-10-no-activity-found-to-handle-intent

https://zhuanlan.zhihu.com/p/183960413

https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/content/InstallApkSessionApi.java

android中backup介绍

  1. adb 备份
    adb backup -f ~/Desktop/log/a.ab -apk com.package.activity

  2. 下载 abe 包

  3. ab 文件解包
    java -jar ~/Downloads/abe/abe.jar unpack ~/Desktop/log/a.ab ~/Desktop/log/a.rar

  4. rar 软件解压 a.rar,即可

android各系统中,formatter适配

Android 中,格式化文件大小(Formatter.formatFileSize),Android8 之前用 1024 为单位,之后采用 1000 为单位。

Formatter 的源码解析

查看 Android 28(Android P)源码

1
2
3
4
5
6
7
8
9
10
11
12
13
public static BytesResult formatBytes(Resources res, long sizeBytes, int flags) {
final int unit = ((flags & FLAG_IEC_UNITS) != 0) ? 1024 : 1000;
final boolean isNegative = (sizeBytes < 0);
float result = isNegative ? -sizeBytes : sizeBytes;
int suffix = com.android.internal.R.string.byteShort;
long mult = 1;
if (result > 900) {
suffix = com.android.internal.R.string.kilobyteShort;
mult = unit;
result = result / unit;
}
......
}

formatFileSize 方法源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 /* <p>As of O, the prefixes are used in their standard meanings in the SI system, so kB = 1000
* bytes, MB = 1,000,000 bytes, etc.</p>
*
* <p class="note">In {@link android.os.Build.VERSION_CODES#N} and earlier, powers of 1024 are
* used instead, with KB = 1024 bytes, MB = 1,048,576 bytes, etc.</p>
*/
public static String formatFileSize(@Nullable Context context, long sizeBytes) {
if (context == null) {
return "";
}
final BytesResult res = formatBytes(context.getResources(), sizeBytes, FLAG_SI_UNITS);
return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix,
res.value, res.units));
}

意思就是在 Android 7 之后单位就变了,使用标准的单位制含义,即国际单位制,就像 1km = 1000 byte 一样;
在 Android 7 及更早的版本是 1024,即 1k = 1024B,这里就不贴代码了,感兴趣的同学可以去看源码。

解决方案

有两个方案,一个是反射设置 FLAG_SI_UNITS 的值,使得与 FLAG_IEC_UNITS 相与不为 0 ,
另外一个方案重新封装一个类,如下,用法与 Android 提供的一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class Formatter {
/** * get file format size * * @param context context * @param roundedBytes file size * @return file format size (like 2.12k) */
public static String formatFileSize(Context context, long roundedBytes) {
return formatFileSize(context, roundedBytes, false, Locale.US);
}

public static String formatFileSize(Context context, long roundedBytes, Locale locale) {
return formatFileSize(context, roundedBytes, false, locale);
}


private static String formatFileSize(Context context, long roundedBytes, boolean shorter, Locale locale) {
if (context == null) {
return "";
}
float result = roundedBytes;
String suffix = "B";
if (result > 900) {
suffix = "KB";
result = result / 1024;
}
if (result > 900) {
suffix = "MB";
result = result / 1024;
}
if (result > 900) {
suffix = "GB";
result = result / 1024;
}
if (result > 900) {
suffix = "TB";
result = result / 1024;
}
if (result > 900) {
suffix = "PB";
result = result / 1024;
}
String value;
if (result < 1) {
value = String.format(locale, "%.2f", result);
} else if (result < 10) {
if (shorter) {
value = String.format(locale, "%.1f", result);
} else {
value = String.format(locale, "%.2f", result);
}
} else if (result < 100) {
if (shorter) {
value = String.format(locale, "%.0f", result);
} else {
value = String.format(locale, "%.2f", result);
}
} else {
value = String.format(locale, "%.0f", result);
}
return String.format("%s%s", value, suffix);
}

}

kotlin返回与跳转

Kotlin 有三种结构化跳转表达式:

return 默认从最直接包围它的函数或者匿名函数返回。
break 终止最直接包围它的循环。
continue 继续下一次最直接包围它的循环。
所有这些表达式都可以用作更大表达式的一部分:

val s = person.name ?: return
这些表达式的类型是 Nothing 类型。

Break 与 Continue 标签
在 Kotlin 中任何表达式都可以用标签来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@、fooBar@。 要为一个表达式加标签,我们只要在其前加标签即可。

loop@ for (i in 1..100) {
// ……
}
现在,我们可以用标签限定 break 或者 continue:

loop@ for (i in 1..100) {
for (j in 1..100) {
if (……) break@loop
}
}
标签限定的 break 跳转到刚好位于该标签指定的循环后面的执行点。 continue 继续标签指定的循环的下一次迭代。

返回到标签
Kotlin 中函数可以使用函数字面量、局部函数与对象表达式实现嵌套。 标签限定的 return 允许我们从外层函数返回。 最重要的一个用途就是从 lambda 表达式中返回。回想一下我们这么写的时候, 这个 return 表达式从最直接包围它的函数——foo 中返回:

//sampleStart
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return // 非局部直接返回到 foo() 的调用者
print(it)
}
println(“this point is unreachable”)
}
//sampleEnd

fun main() {
foo()
}
注意,这种非局部的返回只支持传给内联函数的 lambda 表达式。 如需从 lambda 表达式中返回,可给它加标签并用以限定 return。

//sampleStart
fun foo() {
listOf(1, 2, 3, 4, 5).forEach lit@{
if (it == 3) return@lit // 局部返回到该 lambda 表达式的调用者——forEach 循环
print(it)
}
print(“ done with explicit label”)
}
//sampleEnd

fun main() {
foo()
}
现在,它只会从 lambda 表达式中返回。通常情况下使用隐式标签更方便,因为该标签与接受该 lambda 的函数同名。

//sampleStart
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@forEach // 局部返回到该 lambda 表达式的调用者——forEach 循环
print(it)
}
print(“ done with implicit label”)
}
//sampleEnd

fun main() {
foo()
}
或者,我们用一个匿名函数替代 lambda 表达式。 匿名函数内部的 return 语句将从该匿名函数自身返回

//sampleStart
fun foo() {
listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
if (value == 3) return // 局部返回到匿名函数的调用者——forEach 循环
print(value)
})
print(“ done with anonymous function”)
}
//sampleEnd

fun main() {
foo()
}
请注意,前文三个示例中使用的局部返回类似于在常规循环中使用 continue。

并没有 break 的直接等价形式,不过可以通过增加另一层嵌套 lambda 表达式并从其中非局部返回来模拟:

//sampleStart
fun foo() {
run loop@{
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@loop // 从传入 run 的 lambda 表达式非局部返回
print(it)
}
}
print(“ done with nested loop”)
}
//sampleEnd

fun main() {
foo()
}
当要返一个回值的时候,解析器优先选用标签限定的返回:

return@a 1
这意味着“返回 1 到 @a”,而不是“返回一个标签标注的表达式 (@a 1)”。

android中gradle编译error

  1. The binary version of its metadata is 1.7.1, expected version is 1.5.1
    原因:依赖中某些库使用 kotlin 1.7.1 编译,而工程指定为 1.5.1
    解决方式 1:
    升级 gradle 版本,或 kotlin 版本

方式 2:
降低库的版本,如:

1
2
3
4
5
6
7
subprojects { project ->
println "project name:" + project.name
configurations.all {
resolutionStrategy {
force "androidx.activity:activity:$androidx_activity_version"
}
}