json解析
jsonObject.getString() vs jsonObject.optString()
optString会在得不到你想要的值时候返回空字符串“ ”或指定的默认值,而getString会抛出异常。
推荐使用optString,可避免接口字段的缺失、value的数据类型转换等异常
Java 线程池
Callable + Future 获取执行结果
简单使用
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
|
public boolean getTaskFlag() {
//使用线程池执行
Future<Boolean> future = ThreadUtils.getInstance().submit(new MyTask());
try {
//get 方法会阻塞线程
return future.get(5, TimeUnit.SECONDS);
} catch (ExecutionException e) {
e.printStackTrace();
return true;
} catch (InterruptedException e) {
e.printStackTrace();
return true;
} catch (TimeoutException e) {
e.printStackTrace();
return true;
}
}
public class MyTask implements Callable<Boolean> {
boolean taskFlag = false;
@Override
public Boolean call() throws Exception {
try {
System.out.println("执行耗时操作");
Thread.sleep(3000);
taskFlag = true;
System.out.println("得到结果");
} catch (InterruptedException e) {
e.printStackTrace();
}
return taskFlag;
}
}
|
Future 包含方法 cancel(boolean mayInterruptRunning),isCancelled(),isDone(),get(),get(long timeout, TimeUnit unit)
Future 和 FutureTask
Future 是一个接口,代表可以取消的任务,并可以获得任务的执行结果
FutureTask 实现了 RunnableFuture , RunnableFuture 继承了 Runnable, Future
实现 Runnable 接口,说明可以把 FutureTask 实例传入到 Thread 中,在一个新的线程中执行。
实现 Future 接口,说明可以从 FutureTask 中通过 get 取到任务的返回结果,也可以取消任务执行(通过 interreput 中断)
FutureTask 可用于异步获取执行结果或取消执行任务的场景。通过传入 Runnable 或者 Callable 的任务给 FutureTask,直接调用其 run 方法或者放入线程池执行,之后可以在外部通过 FutureTask 的 get 方法异步获取执行结果,因此,FutureTask 非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。另外,FutureTask 还可以确保即使调用了多次 run 方法,它都只会执行一次 Runnable 或者 Callable 任务,或者通过 cancel 取消 FutureTask 的执行等
onInterceptTouchEvent 不被触发,收不到事件问题
某些控件要添加 android:clickable=“true” 或者 onTouch 方法里返回 true(Button 默认是可点击的,所以能正常收到事件)
单例
一般可以用静态内部类模式,性能和安全都比较兼顾
kotlin 一般简单的用 object 对象声明,伴生对象的写法更灵活,比如需要传参数,继承,接口等时候用
双重检查模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class Singleton {
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
|
1
2
3
4
5
6
| class Singleton private constructor() {
companion object {
val instance: Singleton by lazy {
Singleton() }
}
}
|
静态内部类模式
1
2
3
4
5
6
7
8
9
10
11
12
| public class Singleton {
private Singleton() {
}
public static Singleton getSingleton() {
return Inner.instance;
}
private static class Inner {
private static final Singleton instance = new Singleton();
}
}
|
1
2
3
4
5
6
7
8
9
| class Singleton private constructor() {
companion object {
val instance = SingletonHolder.holder
}
private object SingletonHolder {
val holder= Singleton()
}
}
|
枚举
1
2
3
4
5
| public enum Singleton {
INSTANCE;
public void doSth() {
}
}
|
android ndk
官方文档
在 Android studio 中直接新建 Native C++ 的项目,IDE 会自动安装需要的工具,如 CMake 。新版 Android studio 不用另外再安装 LLDB ,所以在 SDK Manger 中看不到。
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
| package com.kalaqiae.test
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.kalaqiae.test.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Example of a call to a native method
binding.sampleText.text = stringFromJNI()
}
/**
* A native method that is implemented by the 'test' native library,
* which is packaged with this application.
*/
external fun stringFromJNI(): String
companion object {
// Used to load the 'test' library on application startup.
init {
//加载的 so 文件为 libtest.so
System.loadLibrary("test")
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
| #include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
//静态注册 native 方法规则 Java+类的路径+类名+方法名
Java_com_kalaqiae_test_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
|
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
| # For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.18.1)
# Declares and names the project.
project("test")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
test
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
test
# Links the target library to the log library
# included in the NDK.
${log-lib})
|
生成 so 文件
可以利用 Gradle 生成。在 Android studio 中选择 Build->Make Project ,在项目的 build\intermediates\cmake 中会生成各个 ABI 的 so
将 so 文件 放在 libs 的对应路径下 如 libs\armeabi-v8a
应用场景:1.加密 2.音视频解码,图像操作等等(ffmpeg) 3.安全相关,比如hook注入 4.增量更新 5.游戏开发
JNI和NDK的区别
.c 和 .h 区别
本质上没有任何区别。 只不过一般:.h文件是头文件,内含函数声明、宏定义、结构体定义等内容
.c文件是程序文件,内含函数实现,变量定义等内容。而且是什么后缀也没有关系,只不过编译器会默认对某些后缀的文件采取某些动作。你可以强制编译器把任何后缀的文件都当作c文件来编。
一般一个 .c 对应一个 .h 方便管理。
.cpp 是 c++(cplusplus)
f将.cpp /.c 转化成 .so 文件的两种方式
通过 ndk-build 工具,需要编辑 Android.mk 文件。
通过 CMake,需要编辑 CMakeLists.txt 文件
android proguard
查看混淆后的错误日志(两种方法)
方法一:使用 proguardgui
- 打包后在 build->outputs->mapping 有 mapping.txt 文件
- 找到 proguardgui.bat 并双击 C:\Users\Administrator\AppData\Local\Android\Sdk\tools\proguard\bin
- 打开的 Proguard 程序中点击左边菜单中的 Retrace ,再选择 mapping.txt 并复制错误日志到对应区域,然后点击 Retrace
方法二:直接打开 mapping.txt 文件,在文本中查找对应信息
参考
Android 上传库到 Meaven
MavenCentral 和 JitPack 都是 Meaven 仓库
谷歌推荐 MavenCentral 看起来更专业,可以绑定域名 可以用发布脚本 JitPack 更简单用 git
Apache Maven:是一个软件(特别是Java软件)项目管理以及自动构建工具,由Apache软件基金会所提供。是基于项目对象模型(缩写:POM)概念,Maven利用一个中央信息片断能管理一个项目的构建、报告和文档等步骤。
Maven仓库你可以理解为和Apache Maven没有直接的关系,他就是一个存放各种工程jar文件、library文件、插件或者任何工程项目的仓库,用Maven来构建项目的时候可能也会用到Maven仓库里面的依赖库。
Maven仓库有三种类型:本地(local)中央(central),在android开发中最常用 远程(remote)
mavenCentral:中央仓库,这个仓库是由Maven社区管理,由Sonatype公司提供服务,是Apache Maven、SBT和其他构建系统默认的仓库,并且很容易被Apache Ant、Gradle和其他的构建工具使用,需要通过网络访问,通过:http://search.maven.org/#browse 开发者就可以在里面找到自己所需要的代码库。
Gradle支持三种不同的仓库,分别是:Maven和Ivy以及文件夹。
Hook 框架
Xposed Xposed is a framework for modules that can change the behavior of the system and apps without touching any APKs.
VirtualApp (简称:VA)是一款运行于Android系统的沙盒产品,可以理解为轻量级的“Android虚拟机”。其产品形态为高可扩展,可定制的集成SDK,您可以基于VA或者使用VA定制开发各种看似不可能完成的项目。VA目前被广泛应用于APP多开、小游戏合集、手游加速器、手游租号、手游手柄免激活、VR程序移植、区块链、移动办公安全、军队政府数据隔离、手机模拟信息、脚本自动化、插件化开发、无感知热更新、云控等技术领域。
VirtualApp 技术黑产利用研究报告
VirtualXposed 是基于VirtualApp 和 epic 在非ROOT环境下运行Xposed模块的实现(支持5.0~10.0)
Magisk Magisk is a suite of open source software for customizing Android, supporting devices higher than Android 5.0.
EdXposed A Riru module trying to provide an ART hooking framework (initially for Android Pie) which delivers consistent APIs with the OG Xposed, leveraging YAHFA (or SandHook) hooking framework, supports Android 8.0 ~ 11.
TaiChi 太极能够运行 Xposed 模块的框架,模块能通过它改变系统和应用的行为,是个类 Xposed 框架.。中文文档
Android Studio Debug
设置调试类型默认是 auto ,可以选择只调试 Java/Kotlin 或者 C/C++ 的代码
Run > Edit Configurations
按住 alt 鼠标点击左侧边栏,可以设置触发一次就取消的断点,还可以设置断点不生效
debug 这个已经挺详细了
debug 这个还包含了 debug smali
view 源码
view 三万行源码
kotlin 自定义控件报错
Caused by: java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet]
构造函数里的参数需要是 Context
1
2
3
4
5
6
7
8
| class MyView : LinearLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
}
|
或者
1
2
3
4
5
6
7
| class MyView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : LinearLayout(context, attrs, defStyle){
}
|
socket 和 websocket
websocket 在建立连接后就是全双工模式了,适合服务端需要主动推数据给客户端
socket 每次交互都是客户端主动发起
马甲包
jetpack compose
官方文档
将 Jetpack Compose 添加到应用中
samples
demo
使用 viewmodel
一个简单的例子
阴影实现方式 elevation
https://developer.android.com/training/material/shadows-clipping?hl=zh-cn
elevation 是宽度 outlineSpotShadowColor 是颜色
1
2
3
4
5
| <TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="3dp"
android:outlineSpotShadowColor="#57000000"/>
|
查看 apk 签名是 v 几
apksigner通常在 sdk/build-tools/版本号
apksigner verify -v apkName.apk
MMKV DataStore
MMKV 基于内存映射所以写入很快,即使写入时应用崩溃也能完成写入,系统级奔溃就没办法了,如断电。有数据丢失的风险。
读取不是在内存上操作相对就没那么快了。
他是在主线程同步运行,因为快所以没影响,但是如果大量写入,还是会卡的。
支持跨进程。
增量式更新。
DataStore 使用 Kotlin 协程和 Flow 以异步、一致的事务方式存储数据。不会丢数据,支持回调,在子线程读写,速度稳定。
SharedPreferences 速度慢 有 ANR 的问题
Matrix
线上没办法用 profiler ,就可以用 Matrix 记录
设置 Dialog 最大高度
在 show 前调用
1
2
3
4
5
6
7
8
| View decorView = dialog.getWindow().getDecorView();
int maxHeight = DisplayUtils.dp2px(mActivity, 456);
decorView.measure(
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
if (decorView.getMeasuredHeight() > maxHeight) {
dialog.getWindow().setLayout(width, maxHeight);
}
|
android studio 搜索
regex 正则表达式搜索
比如 ^((?!(*|//)).)+[\u4e00-\u9fa5] 搜索中文
Java ArrayList LinkedList Vector
- ArrayList 基于数组实现的并且实现了动态扩容。它允许所有元素,包括 null
- LinkedList 基于双向链表实现
- Vector 类似 ArrayList。Vector 是线程安全的。如果线程已经是安全的,直接用 ArrayList 就不用 Vector 了
- ArrayList get/set 比 LinkedList 快。ArrayList 在尾部增加不需要扩容时比 LinkedList 快,中间增加不需要扩容时 LinkedList 要遍历所以 LinkedList可能更慢,在头部增加和时因为要复制数组所以比较慢
- ArrayList 删除时,删除的元素越靠前越慢,LinkedList 删除越靠中间越慢
- for 循环遍历的时候,ArrayList 花费的时间远小于 LinkedList;迭代器遍历的时候,两者性能差不多。所以遍历 LinkedList 的时候,不要使用 for 循环,要使用迭代器
Android 重启
1
2
3
4
5
| final Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(intent);
//杀掉以前进程
android.os.Process.killProcess(android.os.Process.myPid());
|
Android studio 空判断和循环快捷键
object.nn object.null 空判断快捷方式
for 循环,快捷方式 list.fori 或 list.forr
Android studio 提取 style
提取style:在XML文件中,光标选中需要提取样式的控件,然后右键选择–>Refactor–>Extract–>Style
final 修饰作用
final 修饰的类不能被继承,修饰的方法不能被重写
try catch 输出日志
1
2
3
4
5
| try {
} catch (e: Exception) {
e.printStackTrace()
}
|
在单独的类中接收 activity 结果
与其他应用交互 在单独的类中接收 activity 结果
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
| class MyLifecycleObserver(private val registry : ActivityResultRegistry)
: DefaultLifecycleObserver {
lateinit var getContent : ActivityResultLauncher<String>
override fun onCreate(owner: LifecycleOwner) {
getContent = registry.register("key", owner, GetContent()) { uri ->
// Handle the returned Uri
}
}
fun selectImage() {
getContent.launch("image/*")
}
}
class MyFragment : Fragment() {
lateinit var observer : MyLifecycleObserver
override fun onCreate(savedInstanceState: Bundle?) {
// ...
observer = MyLifecycleObserver(requireActivity().activityResultRegistry)
lifecycle.addObserver(observer)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val selectButton = view.findViewById<Button>(R.id.select_button)
selectButton.setOnClickListener {
// Open the activity to select an image
observer.selectImage()
}
}
}
|
压缩工具 zip4j
删除路径下的 zip 和解压出来的文件
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
| public static void deleteZipFile(File file, String filePath) throws ZipException {
ZipFile zFile = new ZipFile(file);
//zFile.setFileNameCharset("GBK");
if (!zFile.isValidZipFile()) { // 验证.zip文件是否合法,包括文件是否存在、是否为zip文件、是否被损坏等
throw new ZipException("压缩文件不合法,可能被损坏.");
}
List<FileHeader> zipFiles = zFile.getFileHeaders();
List<String> tempPath = new ArrayList<>();
for (int i = 0; i < zipFiles.size(); i++) {
File tempFile = new File(filePath + "/" + zipFiles.get(i).getFileName());
if (tempFile.isDirectory()) {
tempPath.add(filePath + "/" + zipFiles.get(i).getFileName());
} else if (tempFile.exists()) {
tempFile.delete();
}
if (i == zipFiles.size() - 1) {
if (tempPath.size() > 0) {
for (String path : tempPath) {
File tempDirectoryFile = new File(path);
String[] tempDirectoryFiles = tempDirectoryFile.list();
if (tempDirectoryFiles != null && tempDirectoryFiles.length > 0) {
} else if (tempDirectoryFile.exists()) {
tempDirectoryFile.delete();
}
}
}
}
}
file.delete();
}
|
解压所有文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| public static void unZipFileWithProgress(final File zipFile, final String filePath,
CallbackUnzipMonitor callback,
final boolean isDeleteZip, boolean isRunThread) throws ZipException {
ZipFile zFile = new ZipFile(zipFile);
if (!zFile.isValidZipFile()) { // 验证.zip文件是否合法,包括文件是否存在、是否为zip文件、是否被损坏等
throw new ZipException("压缩文件不合法,可能被损坏.");
}
zFile.setFileNameCharset("GBK");
File destDir = new File(filePath); // 解压目录
if (destDir.isDirectory() && !destDir.exists()) {
destDir.mkdir();
}
if (callback != null) {
final ProgressMonitor progressMonitor = zFile.getProgressMonitor();
callback.setMonitor(progressMonitor);
}
zFile.setRunInThread(isRunThread);
zFile.extractAll(filePath); // 解压到此文件夹中
}
|
解压 zip 中单独某个文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| public static void unZipFileWithProgressSingle(final File zipFile, final String filePath,
final String singleFilePath,
CallbackUnzipMonitor callback,
final boolean isDeleteZip, boolean isRunThread) throws ZipException {
ZipFile zFile = new ZipFile(zipFile);
if (!zFile.isValidZipFile()) { // 验证.zip文件是否合法,包括文件是否存在、是否为zip文件、是否被损坏等
throw new ZipException("压缩文件不合法,可能被损坏.");
}
zFile.setFileNameCharset("GBK");
File destDir = new File(filePath); // 解压目录
if (destDir.isDirectory() && !destDir.exists()) {
destDir.mkdir();
}
if (callback != null) {
final ProgressMonitor progressMonitor = zFile.getProgressMonitor();
callback.setMonitor(progressMonitor);
}
zFile.setRunInThread(isRunThread);
zFile.extractFile(singleFilePath, filePath); // 解压到此文件夹中
}
|
Android Studio Live Templates
布局文件 宽高相关 lh lw lhw lhm
JS 闭包
js 子对象可以读取到父对象的变量,父对象不能读取到子对象内部的变量
f2可以读取f1中的局部变量,把f2作为返回值,f1外部就读取它的内部变量,f2函数,就是闭包。
用于读取函数内部的变量和让这些变量的值始终保持在内存中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| function f1(){
var n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
|
build.gradle 修改 apk 名称
一般自定义打包出来的 apk 名称可以这么写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| android.applicationVariants.all { variant ->
variant.outputs.each { output ->
if (variant.buildType.name.equals("release")) {
variant.outputs.all {
outputFileName = "kalaqiae_" + variant.buildType.name + "_" + variant.productFlavors[0].name + "_v"+
defaultConfig.versionCode + "_" + new Date().format("yyyy.MM.dd-HH.mm") + ".apk"
}
} else {
variant.outputs.all {
outputFileName = "kalaqiae_" + variant.buildType.name + "_v" +
defaultConfig.versionCode + "_" + new Date().format("yyyy.MM.dd-HH.mm") + "_test" + ".apk"
}
}
}
}
|
在执行某个命令后重命名这么写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| //复制后重命名
task renameApk(type: Copy) {
from 'build/outputs/apk/release/app-release.apk'
into 'build/outputs/apk/release/'
rename { fileName ->
fileName.replace('app-release',
"kalaqiae" + "_v" + android.defaultConfig.versionCode +
"_" + new Date().format("yyyy.MM.dd-HH.mm") +
"_" + (rootProject.ext.IS_TEST ? "test" : "production"))
}
}
//当执行 installRelease 或 assembleRelease 后执行 finalizedBy
tasks.whenTaskAdded { task ->
if (task.name == 'installRelease' || task.name == 'assembleRelease') {
task.finalizedBy(renameApk)
}
}
|
查看 md5
certutil -hashfile example.exe MD5
依赖冲突
app->task->dependcies 查看依赖
1
2
3
4
| //移除重复依赖例子
implementation 'com.example:library:1.0.0', {
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib'
}
|