Java中使用ByteBuffer处理tcp粘包

 分类:Android, Java, Java语言 阅读 (16)  Java中使用ByteBuffer处理tcp粘包已关闭评论
8月 222019
 
一、通讯数据格式

通讯协议的约定格式如下:

每一个数据包的长度不固定,但是前四个字节代表的意义是固定的

数据的长度取决于第3个字段的值

名称 长度 其他说明  
协议头 2字节 固定的两个字节,用以给协议分段  
数据类型 1字节 数据内容的类型,用以区别多种内容  
数据长度 1字节 后面数据内容的长度,1字节可表示最大长度是255  
数据 n字节 n取决于上一个字节中的数值  
     
     
     
二、处理的难点
  1. 长度不固定,解析每一组数据时需要先想办法获取数据的长度
  2. 如果发送速度很快,收到一包可能有多组数据,需要一个while循环处理收到的一包的数据(这也就是粘包处理的初衷)
  3. 收到一包数据(本例中使用1024长度的缓冲区接收)可能将某个数据包截成两截,当前缓冲区未解析完的要保留下来,否则会丢数据
三、处理流程图
四、代码

在while(true)里面是读取tcp数据和进行粘包处理的代码

五、其他说明

  1. 串口通讯的粘包处理也可以使用此方法

gradle中修改生成的apk的文件

 分类:Android, Java 阅读 (15)  gradle中修改生成的apk的文件已关闭评论
8月 062019
 

在app/build.gradle中的android节点中增加如下代码:

生成的apk的文件名为myapp_debug.apk

 Posted by on 2019-08-06

ByteBuffer详解

 分类:Android, Java, Java语言 阅读 (107)  ByteBuffer详解已关闭评论
7月 022019
 
一、ByteBuffer的父类

  ByteBuffer的父类是Buffer类,意思为缓冲区类,ByteBuffer为字节缓冲区,当然他也可以处理int, long, char等基本数据类型。

  相比于Buffer类的其他继承类CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer 和 ShortBuffer,ByteBuffer类应用更广泛。

 
二、ByteBuffer的几个变量
position 表示当前bytebuffer的数据指针的位置。因为ByteBuffer还可以做为一个数据解析器,比如getShort()可以读出来一个short类型的数据,因此position也是很重要的。
limit 读取时可用作数据索引的上限(此时相当于数据的length – 1),但是写入时又不能有这个limit,否则会造成数据无法写入(写入时可设置limit = capacity)
mark 用于标记读取数据的起始位置,比如我们已经处理完成10个字节的数据,以后不用再管这10个字节,那么我们可以用mark()标记一下,然后配套的时候reset使position回到这个位置(比如后边的处理遇到了异常,或者遇到了不完整的数据)。
capacity 容量,很简单啦,就是最大允许的字节数量。
 
三、几个要点

  1、limit的设置,因为put和get系列函数只会影响position,不会影响limit,limit是数据的结尾位置,是相当的重要。

 
四、ByteBuffer类的方法
put(byte b) 向ByteBuffer中写入一个字节,并且如果成功position+1
put(byte[] src) 将参数中的byte数据写入bytebuffer中,并且如果成功position+src的长度
put(byte[] src, int offset, int length) 将src数组中,在offset处开始,长度为length的数据写入到ByteBuffer中,并且如果成功,position+length
compact() 其实就是删除已读过的数据,将position到limit之间的数据移动到0和limit-position处,并将mark重置为-1,position放到数据结尾,总结一下,就是可以继续写数据了,但是不能读数据。
limit() 获取当前的数据尾的位置(当然,前提是你设置过limit或者调用过flip)
limit(int newLimit) 设置数据的结尾位置。
rewind() 执行后position = 0, mark = -1,数据内容不变。应用场景:新put数据后,使用rewind将指针位置恢复到起始位置。
reset() 将position恢复到mark处的位置。
clear() 重置ByteBuffer的 position = 0; limit = capacity; mark = -1,实际数据内容无变化,其实这个函数会比较少用,不如rewind的实用性强,因为rewind不会改变limit。
remaining() 还未读取的数据limit – position,前提是你正确设置了limit的位置(所有数据的结尾位置)或者调用过flip()函数。
hasRemaining() 是否还有未读取的数据,前提是你正确设置了limit的位置(所有数据的结尾位置)或者调用过flip()函数。
mark() 将当前的position设备为mark(),mark要和reset配合使用。
discardMark() 将mark的位置重置为-1,貌似很少会单独使用这个函数,而是用rewind或者clear来重置mark值
flip() 其实这个函数的意思是,数据准备好了,可以读了,估计是reset、rewind、clear这些名字用的差不多了,起了一个这个难理解的名字,翻转,听着就让人晕。这个函数相当于下面两个函数的组合buf.limit(buf.position());
buf.rewind();。
get系列函数  
 Posted by on 2019-07-02

Android Studio中引入编译好的so文件

 分类:Android, Java 阅读 (146)  Android Studio中引入编译好的so文件已关闭评论
6月 252019
 

so文件单纯的放在源码libs目录下是不能编译到apk中的,需要下面的步骤才可以使用so文件。

一、首先将so文件放到app/src/main/libs目录下,so文件在libs目录下或者libs子目录下均可

二、修改build.gradle(app)文件,在defaultConfig节中加入下面代码

三、然后调用so文件就可以了

 Posted by on 2019-06-25

Android编译系统(mk)路径变量整理

 分类:Android, Java 阅读 (195)  Android编译系统(mk)路径变量整理已关闭评论
6月 062019
 

本文基于Android8.1系统源码,常量的定义在文件./build/make/core/envsetup.mk中

TOPDIR – 源码根目录

OUT_DIR – out目录,默认情况下是源码目录下的out目录,如果指定OUT_DIR_COMMON_BASE这个环境变量,则为这个变量指定的目录

TARGET_BUILD_TYPE – 是release还是debug的编译,在环境变量TARGET_BUILD_TYPE中指定

SOONG_OUT_DIR – soong目录,默认情况下是out/soong

DEBUG_OUT_DIR – TARGET_BUILD_TYPE为debug时会用到此目录,默认是out/debug

TARGET_OUT_ROOT – target目录,默认是out/target,debug时为out/debug/target

TARGET_PRODUCT_OUT_ROOT – 默认是out/target/product目录

TARGET_COMMON_OUT_ROOT – 默认是out/target/common目录,用于生成编译时的中间文件

PRODUCT_OUT – 默认是out/target/product/{设备型号},真正的编译生成文件的目录

TARGET_OUT – $(PRODUCT_OUT)/system目录

TARGET_OUT_GEN – $(PRODUCT_OUT)/gen

TARGET_OUT_EXECUTABLES – $(TARGET_OUT)/bin

TARGET_OUT_OPTIONAL_EXECUTABLES – $(TARGET_OUT)/xbin

TARGET_OUT_RENDERSCRIPT_BITCODE := $(TARGET_OUT_SHARED_LIBRARIES)

TARGET_OUT_JAVA_LIBRARIES :=$(PRODUCT_OUT)/system/framework

TARGET_OUT_APPS :=$(PRODUCT_OUT)/system/app

TARGET_OUT_APPS_PRIVILEGED :=$(PRODUCT_OUT)/system/priv-app

TARGET_OUT_KEYLAYOUT :=$(PRODUCT_OUT)/system/usr/keylayout

TARGET_OUT_KEYCHARS :=$(PRODUCT_OUT)/system/usr/keychars

TARGET_OUT_ETC :=$(PRODUCT_OUT)/system/etc

TARGET_OUT_FAKE := $(PRODUCT_OUT)/fake_packages

TARGET_OUT_TESTCASES := $(PRODUCT_OUT)/testcases

HOST_OUT_ROOT – host目录,默认是out/host

HOST_OUT

SOONG_HOST_OUT

HOST_CROSS_OUT

 Posted by on 2019-06-06

Android编译系统命令总结

 分类:Android, Java 阅读 (56)  Android编译系统命令总结已关闭评论
6月 062019
 

本文基于Android8.1系统源码

– lunch: lunch <product_name>-<build_variant>
– tapas: tapas [<App1> <App2> …] [arm|x86|mips|armv5|arm64|x86_64|mips64] [eng|userdebug|user]
– croot: 回到源码根目录.
– cproject 回到当前项目的根目录
– m: 从根目录开始编译,相当于make整个系统.
– mm: 编译当前目录下的所有模块,但不包括他们的依赖.
– mmm: 编译指定目录下的所有模块,但不包括他们的依赖,.
To limit the modules being built use the syntax: mmm dir/:target1,target2.
– mma: 编译当前目录下的所有模块,包括他们的依赖.
– mmma: 编译指定目录下的所有模块和他们的依赖.
– provision: Flash device with all required partitions. Options will be passed on to fastboot.
– cgrep: 在所有的c或者c++文件中搜索某个关键字.
– ggrep: 在所有的gradle文件中搜索某个关键字.
– jgrep: 在所有的java文件中搜索某个关键字.
– resgrep: 在所有的资源文件中搜索某个关键字.
– mangrep: 在所有的AndroidManifest.xml文件中搜索某个关键字.
– mgrep: 在所有的Makefiles文件中搜索某个关键字.
– sepgrep: 在所有的sepolicy文件中搜索某个关键字.
– sgrep: 在所有的源代码文件中搜索某个关键字.
– rcgrep: 在所有的rc文件中搜索某个关键字
– godir: 跳转到包含某个文件的目录中某个关键字.

具体的函数实现可以查看build/envsetup.sh

 Posted by on 2019-06-06
4月 252019
 
一、启动init进程

init程序是Linux启动的和经一个程序

init程序的源文件在system/core/init/init.cpp里

二、解析init.rc

在init的main函数里,会先做一些系统初始化的工作,然后通过Parser对象来解析init.rc文件

parser对象的源文件在system/core/init/init_parser.cpp里面

在8.1系统源码里面,rc文件分别存在几个不同的目录里面

  • 主rc文件,init.rc在device/设备名/型号名/目录里,init.rc通过import的形式导入其他的rc文件
  • 还有一些其他的rc文件也在这个目录里面
  • init.zygote.rc文件在system/core/rootdir里面

但是在把系统镜像烧录到车机后,rc文件都在根目录/下

三、启动zygote

init进程解析完init.rc,将根据配置文件启动zygote

zygote是所有安卓服务和应用的父进程,承载着创建java虚拟机并且孵化安卓层的SystemServer和安卓应用的功能

zygote源码在frameworks/base/cmds/app_process/app_main.cpp中

四、启动SystemServer

在zygote中其实并没有fork出来一个SystemServer,而是先启动了java层的ZygoteInit类(通过runtime.start(“com.android.internal.os.ZygoteInit”, args, zygote))

当然fork是不能java层直接调用的,ZygoteInit.java中又通过Zygote类,Zygote类又通过jni调用了Linux层的函数fork出来了SystemServer

ZygoteInit.java的源码在:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

Zygote.java的源码在:frameworks/base/core/java/com/android/internal/os/Zygote.java

Zygote jni层的代码在:frameworks/base/core/jni/com_android_internal_os_Zygote.cpp

SystemServer启动后,会启动一大堆系统服务,安卓的基础运行环境就起来了,启动服务的几个函数为:

SystemServer的代码在:frameworks/base/services/java/com/android/SystemServer.java

五、Launcher的启动

在SystemServer的startOtherServices函数中调用了mActivityManagerService.systemReady,

在ActivityManagerService的systemReady函数中通过startHomeActivityLocked来启动Launcher

六、待续问题:

init.rc中的服务是怎么启动的

 Posted by on 2019-04-25
3月 262019
 

一、必要的系统环境

  硬盘200G,内存4G+,交换分区(swap)4G+(可装完系统后添加,见后面错误处理部分)

二、下载源码,国内可以在清华镜像站下载 

  地址:https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/

  网上有很多教程,这里就不再累述了

三、然后安装编译时需要的三方库

sudo apt-get install git-core gnupg flex bison gperf build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev ccache libgl1-mesa-dev libxml2-utils xsltproc unzip

安装open-jdk-8

sudo add-apt-repository ppa:openjdk-r/ppa

sudo apt-get update

sudo apt-get install openjdk-8-jdk

四、开始编译

  然后进入到Android源码目录,假设源码在用户根目录的aosp目录中:

cd ~/aosp

  然后初始化编译环境,使用下面的命令:

source build/envsetup.sh

  然后选择要编译的目标版本,先输入lunch命令

lunch

  然后在显示的列表中选择你的目标版本,输入相应的数字,按回车即可

  然后使用make开始编译,可以使用-j参数增加编译的线程以提高速度,线程数量为cpu核心数的2倍比较合适,比如你的cpu是4核的使用

make -j8

  就可以了

五、遇到的错误

1、错误1:

03:00:11 ckati failed with: signal: killed
build/core/main.mk:21: recipe for target ‘run_soong_ui’ failed
make: *** [run_soong_ui] Error 1

解决方案1:
增加swap空间
增加办法:
cd /
增加4个G的交换空间
sudo dd if=/dev/zero of=/swp bs=10M count=400
sudo mkswap /swp
sudo swapon /swp

用free -m查看交换空间的情况
这个方法增加的是临时交换空间,如需一直保留,需要其他操作
参考链接:
https://blog.csdn.net/yellow_hill/article/details/38894317

解决方案2:
export LC_ALL=C

 

2、错误2:
JackServer提示out of memory

解决方案1:
增加虚拟机内存至最少4G

解决方案2:
修改jack server内存
步骤:
打开./prebuilts/sdk/tools/jack-admin
找到下面这一行
JACK_SERVER_VM_ARGUMENTS=”${JACK_SERVER_VM_ARGUMENTS:=-Dfile.encoding=UTF-8}”
修改为
JACK_SERVER_VM_ARGUMENTS=”${JACK_SERVER_VM_ARGUMENTS:=-Dfile.encoding=UTF-8 -XX:+TieredCompilation -Xmx4096M}”
保存jack-admin文件
在命令行执行
./prebuilts/sdk/tools/jack-admin kill-server
./prebuilts/sdk/tools/jack-admin start-server
重启jack server
然后重新编译

3、错误3:

Error while parsing ‘/media/liuderu/新加卷/android-8.0.0_r1/out/target/common/obj/APPS/QuickSearchBox_intermediates/with-local/classes.dex.flags’:1

原因:路径中包含中文字符

 Posted by on 2019-03-26

Android中aidl开发步骤

 分类:Android, Java 阅读 (67)  Android中aidl开发步骤已关闭评论
2月 242019
 
一、常见问题回顾

1、为什么要使用aidl?

答:aidl可以进行跨进程通讯。

2、aidl文件是怎么变成java代码的?

答:aidl是Android interface definition language的缩写,是一种中间语言,用于描述跨进程通信时的业务逻辑。aidl文件是通过android sdk中的名为aidl的程序生成java文件的,一般在{android-sdk}/build-tools/{android版本}/这个目录下。

3、aidl中都可以使用哪些数据类型?

答:可以使用基本的java数据类型,比如int, boolean, String,也可以使用List类型,如果要使用自己的类则该类需要实现Parcelable接口。

二、aidl服务端开发步骤(使用Android Studio)

1、新建一个android工程

2、新建一个Service,本例为MyAidlService.java

3、新建一个aidl文件,比如IMyAidlInterface

4、为此aidl增加一个函数,比如如下,然后使用Android Studio重新编译一下项目:

5、在MyAidlService.java中,定义一个内部类,继承自IMyAidlInterface.Stub,比如AidlInterfaceStub

6、在MyAidlService.java中定义一个AidlInterfaceStub对象并初始化,如AidlInterfaceStub mAidlInterfaceStub = new AidlInterfaceStub();

7、在MyAidlService.java的onBind函数中返回上一步定义的对象,这样服务端的开发就完成了。

三、aidl客户端开发步骤

1、新建一个android工程

2、将服务端的aidl文件拷贝到客户端工程中,要保持aidl文件所在相对路径与服务端的路径结构一致,即让这个aidl的包名和服务端的包名一致

3、在客户端的Activity中定义一个IMyAidlInterface的对象,如:mIClientAidlInterface

4、在Activity的定义一个ServiceConnection对象,并用new进行实例化,这个ServiceConnection对象会生成以下两个回调函数的结构onServiceConnected和onServiceDisconnected

5、在上一个函数的onServiceConnected的回调中给mIClientAidlInterface赋值,如下

6、通过上一步得到的mIClientAidlInterface就可以调用远端的函数了

7、哦哦、忘了还没有绑定服务,为方便简单说明,我们直接在onCreate里绑定远端服务吧,如下:

8、客户端到此也完成了,看后面的全部代码试着写一下吧

四、代码

客户端:

服务端:

五、完整代码下载地址

https://download.csdn.net/download/wintergoes/11200474

 

 Posted by on 2019-02-24
2月 222019
 

  本文基于Android4.4版本源码进行分析。

  一直以为ActivityThread是一个继承自Thread类的子线程,今天初看这个类发现里面的业务逻辑这么多,看了看网上的其他文章,原来这个Thread指的是Android应用的主线程啊。

问:安卓应用的入口函数是哪个?

答:Application.onCreate。错错错

正确的答案是ActivityThraed的main函数。

Android应用入口,ActivityThread.main  

下面咱们就看看ActivityThread.main函数:

首先,第一步启动SamplingProfilerIntegration,这个封装类集成了Dalvik的性能分析工具,用于监测记录应用的性能参数?待进一步查看代码
  相关源码:./frameworks/base/core/java/com/android/internal/os/SamplingProfilerIntegration.java
./frameworks/base/services/java/com/android/server/SamplingProfilerService.java
./libcore/dalvik/src/main/java/dalvik/system/profiler/SamplingProfiler.java
紧接着CloseGuard.setEnabled(false); 
  CloseGuard类实现了一种机制用于检查是否有内存泄露,默认是关闭,可以通过setEnabled(true)开启。
  源码路径:libcore/dalvik/src/main/java/dalvik/system/CloseGuard.java
Environment.initForCurrentUser();初始化Environment类,主要是一些目录的设置,比如我们通常用的获取外部存储路径函数Environment.getExternalStorageDirectory()的返回值就是在这里进行初始化的。
   源码路径:libcore/luni/src/main/java/libcore/io/EventLogger.java
EventLogger.setReporter(new EventLoggingReporter());
  没看懂怎么用的
  相关源码:libcore/luni/src/main/java/libcore/io/EventLogger.java
Security.addProvider(new AndroidKeyStoreProvider());
  为应用添加安全检测管理器,使用AndroidKeyStoreProvider,keystore就是指打release包时的数字证书吧
 

相关源码:frameworks/base/keystore/java/android/security/AndroidKeyStoreProvider.java
frameworks/base/keystore/java/android/security/AndroidKeyStore.java

Process.setArgV0(“<pre-initialized>”);
  因为应用还未启动完成,先给进程设置一个名字为<pre-initialized>
  相关源码:frameworks/base/core/java/android/os/Process.java
Looper.prepareMainLooper();
  将当前线程初始化为一个Looper,用Looper来管理线程内的各种请求。并将该Looper做为程序的主Looper。
  相关源码:frameworks/base/core/java/android/os/Looper.java
ActivityThread thread = new ActivityThread();
thread.attach(false);
   
   
AsyncTask.init();
  初始化AsyncTask,这是一个静态函数,由此可见AsyncTask的串行执行。
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, “ActivityThread”));
}
   
Looper.loop();
  开始循环主线程里的MessageQueue
 Posted by on 2019-02-22