昨日:篇  今日:篇   总帖:篇   会员:
今日:0    总帖:32
admin
140
那天有一个小伙伴问我: seo极度依赖搜索引擎的重要性,当大家获取可靠信息的途径从漫漫无边的搜索引擎转移到社交问答网站中时,seo的前途将去向何方?  其实这个问题也不是一个小伙伴问过我了,很多学过SEO的都有这个疑惑;当初说好的出任CEO,迎娶白富美,走上人生巅峰的呢?即使做了一两年的SEO,收入也很难满足月入过万;  其实我想说,SEO的行业比你们预想的好得多;不光是搜索引擎,还有其他的只要有搜索功能的平台,只要了解其算法,很容易就能获取到流量,挣到钱;可以说的互联网不灭,SEO不死;  举一个例子:做演员只有做到金字塔顶端才能叫做明星,一年挣几千万那是很容易的事情;然而还是会有很多演员只是一个跑龙套的,一个月辛辛苦苦挣的钱还没有你做SEO的工资高呢;那么为什么同样是演员,差距这么大呢?  还有就是网上的那些做主播的,只有那些有大量粉丝的,才是挣钱的主播,才能叫网红。还有很多小主播挣扎在温饱线上呢,同样是主播,差距咋就这么大呢?这就是一个技术和思路的问题,做SEO也是如此,只有你不断的钻研技术,不断地思索出路,只有掌握了核心技术才会实现自己的梦想,仅仅只是靠着自己上班,每天重复自己的工作是没有什么出路的;  最后,祝大家心想事成! 
0 0 52天前
admin
173
1.搜索增加回车搜索功能2.云短信功能发送验证码时校验是否已被注册3.后台文章审核功能文章列表排序优化4.新增文章“所有回复仅我能看”功能5.文章页seo进一步优化,增加智能关键词选项(后台-全局-seo优化)6.优化IP黑名单,增加不可将自己ip录入限制并解决误报问题7.优化ip黑名单判断机制8.新增db()数据库表调用函数,不存在的表将不会报错(返回false)9.修复云登录一个第三方账号可以绑定多个账号的问题,现改为绑定第三方账号将会解绑该账号绑定的其他账号10.增加全局置顶功能,后台-全局-帖子显示-全局置顶文章ID11.增加应用下载/升级版本校验,防止误安装12.“阅读权限”改名“权限值”13.用户组增加“智能权限值”选项14.发帖页增加“权限值”说明15.系统编辑器图片上传支持浮动选项16.文章、回复及消息内容最小最大提示进行了优化,改为字数,具体设置:后台-全局-发帖相关17.优化个人中心减少服务器开销18.所有官方api接口改为https19.修复用户反映的一处版块标题显示bug20.个人中心链接支持伪静态链接21.修复充值记录页面标题不显示的bug22.fly模板增加logo上传功能23.优化上传头像功能24.优化登录/注册页面25.增加手机版通用logo功能26.优化文章回复查看提示注:是跳转到官方下载点击下载:HadSky v7.10.0 正式版(3.04M)
0 0 68天前
9in
217
9in
百度收录排名:喜厌 第一、要知道百度收录的原则,百度喜欢收录什么,不喜欢收录什么要清楚。第二、关键字要做好,就必须先知道什么关键词是最好,要明白不一定多人应用的关键词就是好关键词,这些可以叫红海关键词;相反我们要深挖蓝海关键字。第三、最重要的就是执行力。百度收录原则:1、百度不喜欢页面与标题不相干的网页,这是一些作弊行为,百度是很不喜欢的,如果这种情况很多或者说是主要的“优化”形式,这个站的收录内容应该是很少了,而且是越来越少,因为用户体验上不去,权重也会越来越低。2、百度要求内容与题目要相关性。如果只是标题中有这种关键词,而内容中出现的很少这就属于作弊行为,百度会察觉到的,如果百度不在信任你的网站,那百度收录的内容一定会越来越少。3、百度不喜欢千篇一律文章,不喜欢收录同样的内容,即使很精彩很经典,也不如新的更能吸引眼球。百度喜新厌旧,如果网站每天都更新,而且内容原创,受客户欢迎,那何愁百度不来收录呢?所以想增加收录量,网站内容一定要新颖。关键词选择1、做热门关键字。一个热门的关键字即使排到第二页第三页,带来的流量也是可观的。比如“新顺德人”,从百度第三页后面来的也相当可观,新顺德人论坛每天每天依然可以来一两百IP左右。而普通的词即使在第一位一天也只能给你带来几个IP。2、做冷门关键字,冷门当然是相对的,有的词一天也没几个人搜索,那简直不能叫关键字。应该叫“生僻字”。冷门的好处就是很容易排到搜索引擎前面去,没什么竞争。缺点就是流量少,不过这个可以通过增加量来解决。比如小说站,如果每篇小说你都能排到前面去(一二页),那么每个就能给你带来1-50的IP,如果你的网站有10万篇小说,有十分之一可以排到前面。那么你的流量级别就可以上十万了。这个也就是所谓做“长尾”。适合海量数据的网站。3、首选,做蓝海关键字,没有竞争那么你就是王者,跳出红海,开创蓝海关键字。很多关键字搜索的人也很多,但是却没人竞争。其优点就是前2者之和,又好做流量又多。当然这种关键字要你去挖掘,去捕捉。4、如何挖掘关键字?所谓蓝海当然是一些大家都想不到,当然也包括你和我。人人都知道那就变成红海了。那我们如何去寻找这样的关键字?5、看统计数据,都可以看到搜索引擎来路、关键字、和流量。你就会发现一些你意想不到的词却在给你带来不错的流量。比如“百度小游戏”,这个关键字每天给我带来100左右的流量,还是yahoo过来的。随便做一下,我就排到雅虎的第二位。如果在百度也能排上,相信至少能来2-3百。这类关键字仔细挖掘还是不少的。‘6、百度搜索框的相关搜索 和 底部相关搜索。你搜索一个词,百度都会给你列出。比如你搜SEO,会得到相关搜索:SEO教育,SEO培训,SEO优化、SEO信息传播等。在更多相关搜索里就可以看到前100个,这个基本是排热门的排行的。你可以寻找一些热门的,又没有什么竞争的词。你可以去百度里面查下他的指数就知道他的热闹程度了。当然其他搜索引擎也有类似功能,比如雅虎,搜狗,google等。7、风云榜,搜索引擎都有每日榜单。百度风云榜,谷歌热榜,雅虎风向标,搜狗指数。都是值得关注和研究的。不单是SEO,研究互联网趋势和热点也是不错的东西。8、SEO信息传播网,网络运营专家潘晨炜谈到,SEO现在基本会慢慢普及的。作为一个企业家肯定是先下手为强,预测热门的关键字,你先做自然占先机。方式很多,充分使用你的大脑吧。
2 0 70天前
cndc
285
下面这个实例是弹出一个对话框,模仿windows样式,大家可以下载看看,还是挺不错的。 效果图如下: 主要是通过一个自定义布局来实现的,布局xml文件如下: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <RelativeLayout android:id="@+id/customviewlayTitle" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="#1A94F9" > <TextView android:id="@+id/customviewtvTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:padding="10dp" android:text="关于我们" android:textColor="#000000" /> <ImageButton android:id="@+id/customviewtvimgCancel" android:layout_width="40dp" android:layout_height="40dp" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:background="@drawable/canceltor" /> </RelativeLayout> <LinearLayout android:id="@+id/customviewlayMessage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/customviewlayTitle" android:orientation="vertical" android:padding="20dp" > <TextView android:id="@+id/orthertv0" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="原作者:asinzuo" android:textColor="#000000" /> <TextView android:id="@+id/orthertv1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:linksClickable="true" android:text="网站:http://www.androidchina.net/" android:textColor="#000000" /> <TextView android:id="@+id/orthertv2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:linksClickable="true" android:text="Email:admin@androidchina.net" android:textColor="#000000" /> </LinearLayout> <LinearLayout android:id="@+id/customviewlayLink" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@id/customviewlayMessage" android:orientation="horizontal" android:paddingBottom="20dp" android:paddingLeft="20dp" android:paddingRight="20dp" > <Button android:id="@+id/ortherbtnemil" android:layout_width="fill_parent" android:layout_height="40dp" android:layout_marginRight="10dp" android:layout_weight="1" android:background="@drawable/linkbtnbged" android:linksClickable="true" android:text="Email给作者" /> <Button android:id="@+id/ortherbtnweb" android:layout_width="fill_parent" android:layout_height="40dp" android:layout_marginLeft="10dp" android:layout_weight="1" android:background="@drawable/linkbtnbged" android:linksClickable="true" android:text="访问网站" /> </LinearLayout> </RelativeLayout> 源码下载:点击下载 原文章地址:http://www.androidchina.net/779.html
0 0 109天前
admin
365
12月28日消息,在百度AI开发者大会上,百度ARM云基础产品矩阵的最新产品“磐玉”蜂巢服务器正式亮相。据百度ARM云业务负责人陶孝禹介绍,“磐玉”蜂巢服务器基于ARM架构打造,是业界首创的基于仿生学的蜂巢式ARM架构云,拥有仿生算力、高能效比、高性价比、高密设计等四大核心优势,可以应用在智慧城市、智慧医疗、智慧教育、智慧金融、智慧文娱等行业的数字化转型过程中。近年来,随着云计算的飞速发展,云原生技术迅速崛起,越来越多的企业认识到云原生是加速企业IT基础设施和业务变革的最佳路径。百度云原生杰出研发架构师郑然在演讲中表示,2021年,百度业务全面实现了全栈云原生基础设施架构的进阶。百度以弹性为基础设施变革的核心要素,底层是高速可编程网络,配合深度定制的百度太行服务器、AI服务器,以及以高密存储、高性能存储为核心的分布式存储系统,通过统一的虚拟化网络、虚拟计算、服务器构建统一的资源池,结合云原生技术建立了一套高效混合云产品技术形态。云原生带给业务应用的最直观感受就是高弹性,而如何把弹性能力发挥到极致,让资源利用率提升和IT成本降低也一直是这里的重要课题。论坛上,百度基础架构部资深工程师张慕华和百度基础架构部资深系统工程师李志就介绍了百度的混部调度和潮汐算力两大核心技术。混部调度的核心,是通过将在线、离线等不同类型的业务混合调度在同一台机器上,在保障业务质量的前提下,让资源利用率达到最优,降低业务成本。目前,百度在离线混部服务器规模超过30万台,是在国内率先实现大数据整体云原生化的容器批量计算平台。在混部技术应用之后,就需要构建极致的离线填谷能力,让资源利用更极致。百度通过潮汐算力方案,将整体夜间闲时的资源利用率提高了1.5倍,并同时自主满足部分业务场景30%的新增资源需求。郑然表示,2021年,百度业务就通过全栈云原生基础设施架构的进阶,使得资源利用率提升超过45%,单位IT成本下降超过30%。此外,百度基础架构部主任架构师王雁鹏认为,未来的数据中心将由三部分构成:负责逻辑控制、系统管理、生态兼容的CPU;实现科学计算、矩阵计算、AI计算、数据并行计算的GPU,以及解决所有数据中心基础设施和数据相关计算问题的DPU。在DPU的定位下,百度推出了“太行”系列产品。太行DPU1.0解决了虚拟化功能卸载的问题,明年即将上线的2.0版本将实现数据路径硬件加速。王雁鹏表示,希望下一代DPU能够真正成为数据中心的大脑,支持海量细粒度的计算实例、硬件全面解耦池化、各个层次的通信接口,实现“CloudNativeIOEngine”的愿景。
10 0 144天前
admin
377
铁血社区发布声明宣布:将于2021年12月20日(今日)停止用户发帖、回帖功能,2022年3月1日起,铁血社区将正式永久关闭。在铁血社区正式停止服务前,用户可登录账号下载历史贴文,转移重要的个人信息,数据将保留3个月。资料显示,铁血社区,又称铁血论坛,创建于2001年,前身是创始人蒋磊于2001年创建的“虚拟军事”,2001年9月“虚拟军事”更名为“铁血军事网”。以下为声明全文:对不起,要和大家告别了 ——致亲爱的铁血家人们新的旅程,往往是从告别开始,是时候说再见了。这一天终于还是来临。我们万分不舍却又不得不遗憾地通知大家,铁血社区将于2021年12月20日停止用户发帖、回帖功能,2022年3月1日起,铁血社区将正式永久关闭。铁血社区起始于Web1.0,壮大在Web2.0。如今Web3.0已经来临,遗憾的是,在移动互联和万物互联的热潮中,我们没能跟上时代的发展。怀念当初少年时,意气风发,激扬文字。我们追看原创军事小说,探讨武器装备,发掘历史故事;一言不合,仗键激辩;呼朋唤友,彻夜“盖楼”……2001年,铁血网成立,是中国原创军事网络小说的摇篮。2002年,铁血军事社区诞生,当年日访问人数就突破2万。2003年,社区日访问人数20万。期间铁血服务器多次不堪重负崩溃,铁血网友组织募捐,两天时间就给铁血凑够了新服务器和租用更大带宽的费用。2004年,“保钓风波”把铁血推向风口浪尖,作为著名的“愤青”聚集地,由于部分网友言辞激烈,网友们经历了十天的“避难所”流浪,凝聚力反而更强,再次为铁血捐款升级服务器。2007年,随着第一批17件M65风衣在铁血社区被抢购一空,铁血终于有了较为稳定的收入来源。2008年,日访问人数突破200万。2011年,龙牙品牌创立,我们要做“中国人自主研发的高性能战术服装”。2013年,与中华社会救助基金会签约成立铁血老兵公益基金,对参战老兵进行物质与精神帮扶。2021年,铁血老兵公益项目已在全国27个地区开展活动,共有1010名志愿者参与活动,已累计帮扶老兵16191人次。以上种种,恍如昨日。我们遗憾,伤感,还是只能选择放下。因为我们还要踏上新的征程,为用户提供更多优质服务,请铁血的新老朋友们多加关注。二十年光阴,铁血社区的亿万网友,历任版主、管理员团队和工作人员,用热血、梦想和坚守共同建设我们的军迷家园,共同缔造了“中国军迷之家”的辉煌,铁血也稳居中国十大独立军事类网站榜首。离别之际,向你们郑重地道一声感谢!聚散两相依。聚,是激情的汇集,散,是火种的传播。我们没有离开,只是转移到了新的战场。尤其是铁血老兵公益,以及龙牙战术服装,我们会矢志不移地继续发展壮大,不忘初心,不负初心。在铁血社区正式停止服务前,用户可登录账号下载历史贴文,转移重要的个人信息,数据将保留3个月。在此期间,如有问题需要沟通,可添加QQ:1969217715或电话:17716447133与我们取得联系,我们将竭诚为大家服务。在此,由衷感谢您对铁血社区的支持与关注,并对关闭业务给您带来的不便深感歉意!铁血的家人们,谢谢你们一路的相伴,一路的温暖。
1 1 152天前
poiok
455
一、JVM内存模型 常见jvm内存模型,主要分为堆区,本地方法栈,虚拟机栈,程序计数器,和方法区。如下图所示: (1)程序计数器 每个线程都会有自己私有的程序计数器(PC)。可以看作是当前线程所执行的字节码的行号指示器。 也可以理解为下一条将要执行的指令的地址或者行号。字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、 循环、 跳转、 异常处理、 线程上下文切换,线程恢复时,都要依赖PC. 如果线程正在执行的是一个Java方法,PC值为正在执行的虚拟机字节码指令的地址 如果线程正在执行的是Native方法,PC值为空(未定义) (2)虚拟机栈(VM Stack) 简介 VM Stack也是线程私有的区域。他是java方法执行时的字典:它里面记录了局部变量表、 操作数栈、 动态链接、 方法出口等信息。 **在《java虚拟机规范》一书中对这部分的描述如下:**栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接 (Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。栈帧的存储空间分配在 Java 虚拟机栈( §2.5.5)之中,每一个栈帧都有自己的局部变量表( Local Variables, §2.6.1)、操作数栈( OperandStack, §2.6.2)和指向当前方法所属的类的运行时常量池( §2.5.5)的引用。 VM-Stack 说白了,VM Stack是一个栈,也是一块内存区域。 所以,他是有大小的。虽然有大小,但是一般而言,各种虚拟机的实现都支持动态扩展这部分内存。 如果线程请求的栈深度太大,则抛出StackOverflowError 如果动态扩展时没有足够的大小,则抛出OutOfMemoryError以下代码肯定会导致StackOverflowError: StackOverflowError public static void method() { method(); } public static void main(String[] args) { method(); } Exception in thread "main" java.lang.StackOverflowError at xxx.xxx.xxx.method(JavaVMStackSOF.java:10) (3)本地方法栈 Java 虚拟机实现可能会使用到传统的栈(通常称之为“ C Stacks”)来支持 native 方法( 指使用 Java 以外的其他语言编写的方法)的执行,这个栈就是本地方法栈( Native MethodStack)。 VM Stack是为执行java方法服务的,此处的Native Method Stack是为执行本地方法服务的。 此处的本地方法指定是和具体的底层操作系统层面相关的接口调用了(这部分太高高级了,不想深究……)。 《java虚拟机规范》中没有对这部分做具体的规定。所以就由VM的实现者自由发挥了。 有的虚拟机(比如HotSpot)将VM Stack和Native Method Stack合二为一,所以VM的另一种内存区域图就如下面所示了: (4)方法区 方法区是由所有线程共享的内存区域。 方法区存储的大致内容如下: 每一个类的结构信息 运行时常量池( Runtime Constant Pool) 字段和方法数据 构造函数和普通方法的字节码内容 类、实例、接口初始化时用到的特殊方法 以下是本人对《java虚拟机规范》一书中对方法区的介绍的总结: 在虚拟机启动的时候被创建 虽然方法区是堆的逻辑组成部分,但是简单的虚拟机实现可以选择在这个区域不实现垃圾收集 不限定实现方法区的内存位置和编译代码的管理策略 容量可以是固定大小的,也可以随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩。 方法区在实际内存空间中可以是不连续的 Java 虚拟机实现应当提供给程序员或者最终用户调节方法区初始容量的手段 对于可以动态扩展和收缩方法区来说,则应当提供调节其最大、最小容量的手段 如果方法区的内存空间不能满足内存分配请求,那 Java 虚拟机将抛出一个OutOfMemoryError 异常 (5)堆 简介 在 Java 虚拟机中,堆( Heap)是可供各条线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域。 以下是本人对《java虚拟机规范》一书中对Java堆的介绍的总结: 在虚拟机启动的时候就被创建 是所有线程共享的内存区域 存储了被自动内存管理系统所管理的各种对象 Java 堆的容量可以是固定大小的,也可以随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩 Java 堆所使用的内存不需要保证是连续的 如果实际所需的堆超过了自动内存管理系统能提供的最大容量,那 Java 虚拟机将会抛出一个OutOfMemoryError 异常 实现者应当提供给程序员或者最终用户调节 Java 堆初始容量的手段 所有的对象实例以及数组都要在堆上分配 至于堆内存的详细情况,将在后续的GC相关文章中介绍。 堆内存中的OutOfMemoryError以下示例代码肯定导致堆内存溢出: public static void main(String[] args) {    ArrayList<Integer> list = Lists.newArrayList();    while (true) {        list.add(1);   } } 无限制的往list中添加元素,无论你的堆内存分配的多大,都会有溢出的时候。 java.lang.OutOfMemoryError: Java heap space 二、内存优化工具 (1)LeakCanary 介绍 leakCanary这是一个集成方便, 使用便捷,配置超级简单的框架,实现的功能却是极为强大的线下内存检测工具。 如何使用 dependencies { // debugImplementation because LeakCanary should only run in debug builds. debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' } 在项目集成之后,在Android Studio Logcat日志通过筛选LeakCanary可以看到如下日志,标志LeakCanary已经安装成功,并且已经启动。 内存泄漏 我们经常会用到很多单例的工具类,往往这些单例也是经常容易发生内存泄漏的地方。下面我们模拟一下单例工具类造成内存泄漏的情况,封装一个ToastUtils的单例类,内部持有context的引用,这样在页面销毁之后依然持有context的引用,造成无法销毁,从而造成内存泄漏。 object ToastUtils { private var context: Context? = null fun toast(context: Context, text: String) { this.context =context Toast.makeText(context, text, Toast.LENGTH_LONG) } } 在控制台和手机上都可以看到内存泄漏的信息。如下所示: 手机上可以明显看到内存泄漏的列表,通过点击item即可看到详细的堆栈信息。 ==================================== 1 APPLICATION LEAKS References underlined with "~~~" are likely causes. Learn more at https://squ.re/leaks. 76243 bytes retained by leaking objects Signature: 409f986871fac1acf6527c76b4d658d03ffa8e11 ┬─── │ GC Root: Local variable in native code │ ├─ android.os.HandlerThread instance │ Leaking: NO (PathClassLoader↓ is not leaking) │ Thread name: 'LeakCanary-Heap-Dump' │ ↓ Thread.contextClassLoader ├─ dalvik.system.PathClassLoader instance │ Leaking: NO (ToastUtils↓ is not leaking and A ClassLoader is never leaking) │ ↓ ClassLoader.runtimeInternalObjects ├─ java.lang.Object[] array │ Leaking: NO (ToastUtils↓ is not leaking) │ ↓ Object[].[620] ├─ com.caichen.article_caichen.ToastUtils class │ Leaking: NO (a class is never leaking) │ ↓ static ToastUtils.context │ ~~~~~~~ ╰→ com.caichen.article_caichen.LeakActivity instance ​ Leaking: YES (ObjectWatcher was watching this because com.caichen.article_caichen.LeakActivity received ​ Activity#onDestroy() callback and Activity#mDestroyed is true) ​ Retaining 76.2 kB in 1135 objects ​ key = 42b604fb-b746-48f5-8ff5-f56cd01a570e ​ watchDurationMillis = 5204 ​ retainedDurationMillis = 190 ​ mApplication instance of android.app.Application ​ mBase instance of androidx.appcompat.view.ContextThemeWrapper ==================================== 同时通过控制台也可以看到详细的信息。 如何分析 ├─ com.caichen.article_caichen.ToastUtils class │ Leaking: NO (a class is never leaking) │ ↓ static ToastUtils.context │ ~~~~~~~ ╰→ com.caichen.article_caichen.LeakActivity instance 通过堆栈信息,大致会得到内存泄漏的大致引用调用路径,最终定位到ToastUtils类中,存在内存泄漏的地方就是内部的context变量,因为被单例对象所持有那么他引用的context和单例的生命周期相同。所以当页面消失时,无法被垃圾回收器销毁从而造成内存泄漏。 (2)Profile Memory 介绍 Profile Memory是 Android Profiler 中的一个组件,可帮助您识别可能会导致应用卡顿、冻结甚至崩溃的内存泄漏和内存抖动。它显示一个应用内存使用量的实时图表,让您可以捕获堆转储、强制执行垃圾回收以及跟踪内存分配。 如何使用 如需打开内存性能分析器,请按以下步骤操作: 依次点击 View > Tool Windows > Profiler(您也可以点击工具栏中的 Profile 图标 )。 从 Android Profiler 工具栏中选择要分析的设备和应用进程。如果您已通过 USB 连接设备但系统未列出该设备,请确保您已启用 USB 调试。 点击 MEMORY 时间轴上的任意位置以打开内存性能分析器。 如图所示: 点击Memory选项可以进入内存性能分析器界面, 右上角分别展示出jvm对应的内存的情况。 内存计数中的类别如下: Total:内存占用的总和值 Java:从 Java 或 Kotlin 代码分配的对象的内存。 Native:从 C 或 C++ 代码分配的对象的内存。即使您的应用中不使用 C++,您也可能会看到此处使用了一些原生内存,因为即使您编写的代码采用 Java 或 Kotlin 语言,Android 框架仍使用原生内存代表您处理各种任务,如处理图像资源和其他图形。 Graphics:图形缓冲区队列为向屏幕显示像素(包括 GL 表面、GL 纹理等等)所使用的内存。(请注意,这是与 CPU 共享的内存,不是 GPU 专用内存。) Stack:您的应用中的原生堆栈和 Java 堆栈使用的内存。这通常与您的应用运行多少线程有关。 Code:您的应用用于处理代码和资源(如 dex 字节码、经过优化或编译的 dex 代码、.so 库和字体)的内存。 Others:您的应用使用的系统不确定如何分类的内存。 Allocated:您的应用分配的 Java/Kotlin 对象数。此数字没有计入 C 或 C++ 中分配的对象。如果连接到搭载 Android 7.1 及更低版本的设备,只有在内存性能分析器连接到您运行的应用时,才开始此分配计数。因此,您开始分析之前分配的任何对象都不会被计入。但是,Android 8.0 及更高版本附带一个设备内置性能剖析工具,该工具可跟踪所有分配,因此,在 Android 8.0 及更高版本上,此数字始终表示您的应用中待处理的 Java 对象总数。 内存抖动 什么是内存抖动,内存抖动就是内存在短暂时间频繁的GC和分配内存,导致内存不稳定。体现在profile中就是内存曲线呈现锯齿状的形状。 影响 频繁的创建对象造成内存碎片和不足 碎片的内存无法被分配从而容易导致内存溢出 频繁GC导致应用性能下降 如何分析 通过点击Record然后可以记录出内存片的内存信息 通过分析可以看出,string,char,int和StringBuilder都是占用内存比较多的对象 分析源码可以看出,在handleMessage中不断的进行创建intArray的对象,然后handlerMessage执行完毕之后创建的对象进行销毁,所以内存曲线呈现出来的形状呈锯齿状。 其中可以看出int占用的内存并没有string占有的内存大,那是因为通过log日志打印的字符串,通过StringBuilder进行了拼接,所以string和stringBuilder占用的内存比Int占用的内存还要大。 private fun initHandler() { handler = Handler(Looper.getMainLooper(), Handler.Callback { for (index in 0..1000) { val asc = IntArray(100000) { 0 } Log.d("YI", "$asc") } handler?.sendEmptyMessageDelayed(0, 300) true }) handler?.sendEmptyMessageDelayed(0, 300) } (3)Memory Analyzer 介绍 Eclipse Memory Analysis Tools (MAT) 是一个分析 Java堆数据的专业工具,用它可以定位内存泄漏的原因。 如何使用 通过As导出的Heap文件不能直接使用需要通过SDK/platform-tools中的Hprof可执行文件将androidhea文件进行转化。 打开MAT程序,通过Open heap打开文件。 如何分析 通过筛选类进行查看实例 通过包名进行查看实例 图中1处可可以指定分类的方式,图中2处可以找到自己包名下面的类,图中3处可以看到所有的包名下的实例和个数。可以看出图中LeakActivity的实例的个数11个,我们可以大致猜测出这个Activity是泄漏了。 接着我们查看Dominator Tree然后可以看出,罗列出leakActivity的所有的实例。 右键选中,然后点击Merge Shortest Paths to GC Roots可以看到引用路径。LeakActivity的引用呗Toastutils所持有。然后通过检查源码,可以看出内部context持有外部的引用,ToastUtils又是单例造成了activity的泄漏。 三、常见内存泄漏问题及解决方法 (1)非静态内部类默认持有外部类的引用会导致内存泄漏 静态内部类与非静态内部类之间存在一个最大的区别,就是非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。 代码: public class Outer { private void outerDo() {} class Inter { private void innerDo() { // 内部类可以直接访问外部类成员,原因在于隐式持有了一个外部类引用 outerDo(); // Outer.this 就是内部类隐式持有的外部类引用 Outer.this.outerDo(); } } } 如果Inter的实例为静态的会导致内存泄漏。 解决方法:将Inter改成静态内部类 (2)Handler持有当前类的Context对象,导致对象无法释放 例如: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); start(); } private void start() { Message msg = Message.obtain(); msg.what = 1; mHandler.sendMessage(msg); } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 1) { // 做相应逻辑 } } }; } 熟悉Handler消息机制的都知道,mHandler会作为成员变量保存在发送的消息msg中,即msg持有mHandler的引用,而mHandler是Activity的非静态内部类实例,即mHandler持有Activity的引用,那么我们就可以理解为msg间接持有Activity的引用。msg被发送后先放到消息队列MessageQueue中,然后等待Looper的轮询处理(MessageQueue和Looper都是与线程相关联的,MessageQueue是Looper引用的成员变量,而Looper是保存在ThreadLocal中的)。那么当Activity退出后,msg可能仍然存在于消息对列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,以致发生Activity的内存泄露。 解决办法:静态内部类+弱引用 例如: public class MainActivity extends AppCompatActivity { private static class ParseHandler extends XyHandler<MainActivity> { private ParseHandler(MainActivity activity) { super(activity); } @Override protected void handleMessage(Message msg, MainActivity activity) { switch (msg.what) { case 0: activity.doSomething(); break; default: break; } } } private Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler = new ParseHandler(this); start(); } private void start() { Message msg = Message.obtain(); msg.what = 1; mHandler.sendMessage(msg); } public void doSomething(){ } } public abstract class XyHandler<T> extends Handler { private WeakReference<T> mWeak; public XyHandler(T t) { mWeak = new WeakReference<>(t); } @Override public void handleMessage(Message msg) { if (mWeak == null || mWeak.get() == null) { return; } handleMessage(msg, mWeak.get()); super.handleMessage(msg); } protected abstract void handleMessage(Message msg, T t); } mHandler通过弱引用持有Activity时,在GC操作时,Activity就会被正常回收。 关于消息发送机制看博客:Android系统源码分析–消息循环机制,上面的解决办法也可以在进一步封装,可以看博客最后的Handler正确使用方法。 关于强引用、软引用、弱引用、虚引用可以看文章:内存泄漏:使用弱应用处理外部类引用 (3) 静态对象引用,无法释放对象导致内存泄漏 这种情况不常遇到,一般有些新手会犯这个错误。这种就是一些对象的引用是静态的,导致无法回收。 例如: public class MainActivity extends AppCompatActivity { privite static MainActivity mMainActivity; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mMainActivity = this; } } 还有自定义View或者ViewGroup中也是出现这种状况,导致内存无法释放,出现内存溢出。对于这种Context不能用静态引用,如果需要调用这些对象内的方法可以将对应方法代码提取出来。 (4) 线程内引用有生命周期的外部对象 线程一般是指Thread和AsyncTask Thread,例如: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { // 模拟相应耗时逻辑 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } } AsyncTask,例如: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { // 模拟相应耗时逻辑 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } }.execute(); } } 我们在刚开始写代码的时候都是像上面一样写线程和异步任务,但是这种方式使用Thread和AsyncTask都是匿名内部类对象,默认持有外部类Activity的引用,在Activity回收时可能会有Thread和AsyncTask没有执行完的情况,所以可能会造成内存泄漏。 (5)资源未关闭或者未释放造成的内存泄漏 IO流、File流或者数据库、Cursor等资源在使用完成后要及时关闭,如果没有及时关闭,会导致缓冲对象一直被占用,不能得到释放,发生内存泄漏。Bitmap未回收。 (6)单例引用Context导致内存泄漏 例如: public class AppSettings { private static AppSettings sInstance; private Context mContext; private AppSettings(Context context) { this.mContext = context; } public static AppSettings getInstance(Context context) { if (sInstance == null) { sInstance = new AppSettings(context); } return sInstance; } } 单例模式的生命周期会和整个应用的生命周期一样,如果在使用单例模式时传入的是Activity或者Service等声明周期短于Application的声明周期时,都会造成内存泄漏。因此我们传入的Context应该是Application的Context,这样生命周期就和Application的声明周期一样,避免造成内存泄漏 (7)广播未及时注销造成内存泄漏 public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.registerReceiver(mReceiver, new IntentFilter()); } private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // 接收到广播需要做的逻辑 } }; @Override protected void onDestroy() { super.onDestroy(); this.unregisterReceiver(mReceiver); } } 广播是非静态内部类,会持有Activity引用,而广播注册会将广播对象注册到系统内部,如果没有取消注册,那么系统中会存在Activity的应用,因此不能释放Activity,造成内存泄漏。 解决办法:就是在OnDestroy方法中取消注册广播。 (8)集合类泄漏 集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。 (9)WebView内存泄漏 原文里说的webview引起的内存泄漏主要是因为org.chromium.android_webview.AwContents 类中注册了component callbacks,但是未正常反注册而导致的。 org.chromium.android_webview.AwContents 类中有这两个方法 onAttachedToWindow 和 onDetachedFromWindow;系统会在attach和detach处进行注册和反注册component callback; 在onDetachedFromWindow() 方法的第一行中: if (isDestroyed()) return;, 如果 isDestroyed() 返回 true 的话,那么后续的逻辑就不能正常走到,所以就不会执行unregister的操作;我们的activity退出的时候,都会主动调用 WebView.destroy() 方法,这会导致 isDestroyed() 返回 true;destroy()的执行时间又在onDetachedFromWindow之前,所以就会导致不能正常进行unregister()。 然后解决方法就是:让onDetachedFromWindow先走,在主动调用destroy()之前,把webview从它的parent上面移除掉。 ViewParent parent = mWebView.getParent(); if (parent != null) { ((ViewGroup) parent).removeView(mWebView); } mWebView.destroy(); 完整的activity的onDestroy()方法: @Override protected void onDestroy() { if( mWebView!=null) { // 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再 // destory() ViewParent parent = mWebView.getParent(); if (parent != null) { ((ViewGroup) parent).removeView(mWebView); } mWebView.stopLoading(); // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错 mWebView.getSettings().setJavaScriptEnabled(false); mWebView.clearHistory(); mWebView.clearView(); mWebView.removeAllViews(); mWebView.destroy(); } super.on Destroy(); } 本文字作者:自如大前端研发中心-邹鑫 原文章地址:http://www.androidchina.net/11885.html
21 0 166天前
poiok
469
使用 OpenGL 做图像的转场效果或者图片轮播器,可以实现很多令人惊艳的效果。 GLTransitions 熟悉的 OpenGL 开发的朋友已经非常了解 GLTransitions 项目,该项目主要用来收集各种 GL 转场特效及其 GLSL 实现代码,开发者可以很方便地移植到自己的项目中。 GLTransitions 项目网站地址: gl-transitions.com/gallery GLTransitions 项目已经有接近 100 种转场特效,能够非常方便地运用在视频处理中,**很多转场特效包含了混合、边缘检测、腐蚀膨胀等常见的图像处理方法,由易到难。 ** 对于想学习 GLSL 的同学,既能快速上手,又能学习到一些高阶图像处理方法 GLSL 实现,强烈推荐。 另外 GLTransitions 也支持 GLSL 脚本在线编辑、实时运行,非常方便学习和实践。 Android OpenGL 怎样移植转场特效 由于 GLSL 脚本基本上是通用的,所以 GLTransitions 特效可以很方便地移植到各个平台,本文以 GLTransitions 的 HelloWorld 项目来介绍下特效移植需要注意的几个点。 GLTransitions 的 HelloWorld 项目是一个混合渐变的特效: // transition of a simple fade. vec4 transition (vec2 uv) { return mix( getFromColor(uv), getToColor(uv), progress ); } transition 是转场函数,功能类似于纹理采样函数,根据纹理坐标 uv 输出 rgba ,getFromColor(uv) 表示对源纹理进行采样,getToColor(uv) 表示对目标纹理进行采样,输出 rgba ,progress 是一个 0.0~1.0 数值之间的渐变量,mix 是 glsl 内置混合函数,根据第三个参数混合 2 个颜色。 根据以上信息,我们在 shader 中只需要准备 2 个纹理,一个取值在 0.0~1.0 的(uniform)渐变量,对应的 shader 脚本可以写成: #version 300 es precision mediump float; in vec2 v_texCoord; layout(location = 0) out vec4 outColor; uniform sampler2D u_texture0; uniform sampler2D u_texture1; uniform float u_offset;//一个取值在 0.0~1.0 的(uniform)渐变量 vec4 transition(vec2 uv) { return mix( texture(u_texture0, uv);, texture(u_texture1, uv);, u_offset ); } void main() { outColor = transition(v_texCoord); } 代码中设置纹理和变量: glUseProgram (m_ProgramObj); glBindVertexArray(m_VaoId); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, m_TextureIds[0]); GLUtils::setInt(m_ProgramObj, "u_texture0", 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, m_TextureIds[1]); GLUtils::setInt(m_ProgramObj, "u_texture1", 1); GLUtils::setFloat(m_ProgramObj, "u_offset", offset); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0); 本文的 demo 实现的是一个图像轮播翻页效果,Android 实现代码见项目: github.com/githubhaoha… 转场特效移植是不是很简单,动手试试吧。   原文章地址:http://www.androidchina.net/11875.html
2 0 166天前
poiok
455
大家在 Android 上做数据持久化经常会用到数据库。除了借助 SQLiteHelper 以外,业界也有不少成熟的三方库供大家使用。 本文就这些三方库做一个横向对比,供大家在技术选型时做个参考。 Room Relam GreenDAO ObjectBox SQLDelight 以 Article 类型的数据存储为例,我们如下设计数据库表: Field Name Type Length Primary Description id Long 20 yes 文章id author Text 10 作者 title Text 20 标题 desc Text 50 摘要 url Text 50 文章链接 likes Int 10 点赞数 updateDate Text 20 更新日期 1. Room Room 是 Android 官方推出的 ORM 框架,它提供了一个基于 SQLite 抽象层,屏蔽了 SQLite 的访问细节,更容易与官方推荐的 AAC 组件搭配实现单一事件来源(Single Source of Truth)。 developer.android.com/training/da… 工程依赖 implementation "androidx.room:room-runtime:$latest_version" implementation "androidx.room:room-ktx:$latest_version" kapt "androidx.room:room-compiler:$latest_version" // 注解处理器 复制代码 Entity 定义数据库表结构 Room 使用 data class 定义 Entity 代表 db 的表结构, @PrimaryKey 标识主键, @ColumnInfo 定义属性在 db 中的字段名 @Entity data class Article( @PrimaryKey val id: Long, val author: String, val title: String, val desc: String, val url: String, val likes: Int, @ColumnInfo(name = "updateDate") @TypeConverters(DateTypeConverter::class) val date: Date, ) Room 底层基于 SQLite 所以只能存储基本型数据,任何对象类型必须通过 TypeConvert 转化为基本型: class Converters { @TypeConverter fun fromString(value: String?): Date? { return format.parse(value) } @TypeConverter fun dateToString(date: Date?): String? { return SimpleDateFormat("yyyy-MM-dd", Locale.US).format(date) } } DAO Room 的最主要特点是基于注解生成 CURD 代码,减少手写代码的工作量。 首先通过 @Dao 创建 DAO @Dao interface ArticleDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun saveArticls(vararg articles: Article) @Query("SELECT * FROM Article") fun getArticles(): Flow<List<Article>> } 然后通过 @Insert, @Update, @Delete 等定义相关方法用来更新数据;定义 @Query 方法从数据库读取信息,SELECT 的 SQL 语句作为其注解的参数。 @Query 方法支持 RxJava 或者 Coroutine Flow 类型的返回值,KAPT 会根据返回值类型生成相应代码。当 db 的数据更新造成 query 的 Observable 或者 Flow 结果发生变化时,订阅方会自动收到新的数据。 注意:虽然 Room 也支持 LiveData 类型的返回值,LiveData 是一个 Androd 平台对象。一个比较理想的 MVVM 架构,其数据层最好是 Android 无关的,所以不推荐使用 LiveData 作为返回值类型 AppDatabase 实例 最后,通过创建个 Database 实例来获取 DAO @Database(entities = [Article::class], version = 1) // 定义当前db的版本以及数据库表(数组可定义多张表) @TypeConverters(value = [DateTypeConverter::class]) // 定义使用到的 type converters abstract class AppDatabase : RoomDatabase() { abstract fun articleDao(): ArticleDao companion object { @Volatile private var instance: AppDatabase? = null fun getInstance(context: Context): AppDatabase = instance ?: synchronized(this) { instance ?: buildDatabase(context).also { instance = it } } private fun buildDatabase(context: Context): AppDatabase = Room.databaseBuilder(context, AppDatabase::class.java, "ArticleDb") .fallbackToDestructiveMigration() // 数据库升级策略 .build() } } 2. Realm Realm 是一个专门针对移动端设计的数据库,不同于 Room 等其他 ORM 框架,Realm 底层并不依赖 SQLite,有自己的一套基于零拷贝的存储引擎,在速度上明显优于其他 ORM 框架。 docs.mongodb.com/realm/sdk/a… 工程依赖 //root build.gradle dependencies { ... classpath "io.realm:realm-gradle-plugin:$realmVersion" ... } // module build.gradle apply plugin: 'com.android.application' apply plugin: 'realm-android' Entity Realm 要求 Entity 必须要有一个空构造函数,所以不能使用 data class 定义。 Entity 必须继承自 RealmObject open class RealmArticle : RealmObject() { @PrimaryKey val id: Long = 0L, val author: String = "", val title: String = "", val desc: String = "", val url: String = "", val likes: Int = 0, val updateDate: Date = Date(), } 除了整形、字符串等基本型,Realm 也支持存储例如 Date 这类的常见的对象类型,Realm 内部会做兼容处理。你也可以在 Entity 中使用自定义类型,但需要保证这个类也是 RealmObject 的派生类。 初始化 要使用 Realm 需要传入 Application 进行初始化 Realm.init(context) DAO 定义 DAO 的关键是获取一个 Realm 实例,然后通过 executeTransactionAwait 开启事务,在内部完成 CURD 操作。 class RealmDao() { private val realm: Realm = Realm.getDefaultInstance() suspend fun save(articles: List<Article>) { realm.executeTransactionAwait { r -> // open a realm transaction for (article in articles) { if (r.where(RealmArticle::class.java).equalTo("id", article.id).findFirst() != null) { continue } val realmArticle = r.createObject(Article::class.java, article.id) // create object (table) // save data realmArticle.author = article.author realmArticle.desc = article.desc realmArticle.title = article.title realmArticle.url = article.url realmArticle.likes = article.likes realmArticle.updateDate = article.updateDate } } } fun getArticles(): Flow<List<Article>> = callbackFlow { // wrap result in callback flow `` realm.executeTransactionAwait { r -> val articles = r.where(RealmArticle::class.java).findAll() articles.forEach { offer(it) } } awaitClose { println("End Realm") } } } 除了获取默认配置的 Realm ,还可以基于自定义配置获取实例 val config = RealmConfiguration.Builder() .name("default-realm") .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .compactOnLaunch() .inMemory() .build() // set this config as the default realm Realm.setDefaultConfiguration(config) 3. GreenDAO greenDao 是 Android 平台上的开源框架,跟 Room 一样也是一套基于 SQLite 的轻量级 ORM 解决方案。greenDAO 针对 Android 平台进行了优化,运行时的内存开销非常小。 github.com/greenrobot/… 工程依赖 //root build.gradle buildscript { repositories { jcenter() mavenCentral() // add repository } dependencies { ... classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0' // greenDao 插件 ... } } //module build.gradle //添加 GreenDao插件 apply plugin: 'org.greenrobot.greendao' dependencies { //GreenDao依赖添加 implementation 'org.greenrobot:greendao:latest_version' } greendao { // 数据库版本号 schemaVersion 1 // 生成数据库文件的目录 targetGenDir 'src/main/java' // 生成的数据库相关文件的包名 daoPackage 'com.sample.greendao.gen' } Entity greenDAO 的 Entity 定义和 Room 类似,@Property 用来定义属性在 db 中的名字 @Entity data class Article( @Id(assignable = true) val id: Long, val author: String, val title: String, val desc: String, val url: String, val likes: Int, @Property(nameInDb = "updateDate") @Convert(converter = DateConvert::class.java, columnType = String.class) val date: Date, ) greenDAO 只支持基本型数据,复杂类型通过 PropertyConverter 进行类型转换 class DateConverter : PropertyConverter<Date, String>{ @Override fun convertToEntityProperty(value: Integer): Date { return format.parse(value) } @Override fun convertToDatabaseValue(date: Date): String { return SimpleDateFormat("yyyy-MM-dd", Locale.US).format(date) } } 生成 DAO 相关文件 定义 Entity 后,编译工程会在我们配置的 com.sample.greendao.ge 目录下生成 DAO 相关的三个文件:DaoMaster,DaoSessiion,ArticleDao , DaoMaster: 管理数据库连接,内部持有着数据库对象 SQLiteDatabase, DaoSession:每个数据库连接可以开放多个 session,而 session 的开销很小,无需反复创建 connection XXDao:通过 DaoSessioin 获取访问具体 XX 实体的 DAO 初始化 DaoSession 的过程如下: fun initDao(){ val helper = DaoMaster.DevOpenHelper(this, "test") //创建的数据库名 val db = helper.writableDb daoSession = DaoMaster(db).newSession() // 创建 DaoMaster 和 DaoSession } 数据读写 //插入一条数据,数据类型为 Article 实体类 fun insertArticle(article: Article){ daoSession.articleDao.insertOrReplace(article) } //返回全部文章 fun getArticles(): List<Article> { return daoSession.articleDao.queryBuilder().list() } //按名字查找一条数据,并返回List fun getArticle(name :String): List<Article> { return daoSession.articleDao.queryBuilder() .where(ArticleDao.Properties.Title.eq(name)) .list() } 通过 daoSession 获取 ArticleDao,而后可以通过 QueryBuilder 添加条件进行调价查询。 4.ObjectBox ObjectBox 是专为小型物联网和移动设备打造的 NoSQL 数据库,它是一个键值存储数据库,非列式存储,在非关系型数据的存储场景中性能上更具优势。ObjectBox 和 GreenDAO 使用一个团队。 docs.objectbox.io/kotlin-supp… 工程依赖 //root build.gradle dependencies { ... classpath "io.objectbox:objectbox-gradle-plugin:$latest_version" ... } // module build.gradle apply plugin: 'com.android.application' apply plugin: 'io.objectbox' ... dependencies { ... implementation "io.objectbox:objectbox-kotlin:$latest_version" ... } Entity @Entity data class Article( @Id(assignable = true) val id: Long, val author: String, val title: String, val desc: String, val url: String, val likes: Int, @NameInDb("updateDate") val date: Date, ) ObjectBox 的 Entity 和自家的 greenDAO 很像,只是个别注解的名字不同,例如使用 @NameInDb 替代 @Property 等 BoxStore 需要为 ObjectBox 创建一个 BoxStore来管理数据 object ObjectBox { lateinit var boxStore: BoxStore private set fun init(context: Context) { boxStore = MyObjectBox.builder() .androidContext(context.applicationContext) .build() } } BoxStore 的创建需要使用 Application 实例 ObjectBox.init(context) DAO ObjectBox 为实体类提供 Box 对象, 通过 Box 对象实现数据读写 class ObjectBoxDao() : DbRepository { // 基于 Article 创建 Box 实例 private val articlesBox: Box<Article> = ObjectBox.boxStore.boxFor(Article::class.java) override suspend fun save(articles: List<Article>) { articlesBox.put(articles) } override fun getArticles(): Flow<List<Article>> = callbackFlow { // 将 query 结果转换为 Flow val subscription = articlesBox.query().build().subscribe() .observer { offer(it) } awaitClose { subscription.cancel() } } } ObjectBox 的 query 可以返回 RxJava 的结果, 如果要使用 Flow 等其他形式,需要自己做一个转换。 5. SQLDelight SQLDelight 是 Square 家的开源库,可以基于 SQL 语句生成类型安全的 Kotlin 以及其他平台语言的 API。 cashapp.github.io/sqldelight/… 工程依赖 //root build.gradle dependencies { ... classpath "com.squareup.sqldelight:gradle-plugin:$latest_version" ... } // module build.gradle apply plugin: 'com.android.application' apply plugin: 'com.squareup.sqldelight' ... dependencies { ... implementation "com.squareup.sqldelight:android-driver:$latest_version" implementation "com.squareup.sqldelight:coroutines-extensions-jvm:$delightVersion" ... } .sq 文件 DqlDelight 的工程结构与其他框架有所不同,需要在 src/main/java 的同级创建 src/main/sqldelight 目录,并按照包名建立子目录,添加 .sq 文件 # Article.sq import java.util.Date; CREATE TABLE Article( id INTEGER PRIMARY KEY, author TEXT, title TEXT, desc TEXT, url TEXT, likes INTEGER, updateDate TEXT as Date ); selectAll: #label: selectAll SELECT * FROM Article; insert: #label: insert INSERT OR IGNORE INTO Article(id, author, title, desc, url, likes, updateDate) VALUES ?; Article.sq 中对 SQL 语句添加 label 会生成对应的 .kt 文件 ArticleQueries.kt。 我们创建的 DAO 也是通过 ArticleQueries 完成 SQL 的 CURD DAO 首先需要创建一个 SqlDriver 用来进行 SQL 数据库的连接、事务等管理,Android平台需要传入 Context, 基于 SqlDriver 获取 ArticleQueries 实例 class SqlDelightDao() { // 创建SQL驱动 private val driver: SqlDriver = AndroidSqliteDriver(Database.Schema, context, "test.db") // 基于驱动创建db实例 private val database = Database(driver, Article.Adapter(DateAdapter())) // 获取 ArticleQueries 实例 private val queries = database.articleQueries override suspend fun save(artilces: List<Article>) { artilces.forEach { article -> queries.insert(article) // insert 是 Article.sq 中的定义的 label } } override fun getArticles(): Flow<List<Article>> = queries.selectAll() // selectAll 是 Article.sq 中的定义的 label .asFlow() // convert to Coroutines Flow .map { query -> query.executeAsList().map { article -> Article( id = article.id, author = article.author desc = article.desc title = article.title url = article.url likes = article.likes updateDate = article.updateDate ) } } } 类似于 Room 的 TypeConverter,SQLDelight 提供了 ColumnAdapter 用来进行数据类型的转换: class DateAdapter : ColumnAdapter<Date, String> { companion object { private val format = SimpleDateFormat("yyyy-MM-dd", Locale.US) } override fun decode(databaseValue: String): Date = format.parse(databaseValue) ?: Date() override fun encode(value: Date): String = format.format(value) } 6. 总结 前文走马观花地介绍了各种数据库的基本使用,更详细的内容还请移步官网。各框架在 Entity 定义以及 DAO 的生成上各具特色,但是设计目的殊途同归:减少对 SQL 的直接操作,更加类型安全的读写数据库。 最后,通过一张表格总结一下各种框架的特点: 出身 存储引擎 RxJava Coroutine 附件文件 数据类型 Room Google亲生 SQLite 支持 支持 编译期代码生成 基本型 + TypeConverter Realm 三方 C++ Core 支持 部分支持 无 支持复杂类型 GreenDAO 三方 SQLite 不支持 不支持 编译期代码生成 基本型+ PropertyConverter ObjectBox 三方 Json 支持 不支持 无 支持复杂类型 SQLDelight 三方 SQLite 支持 支持 手写.sq 基本型 + ColumnAdapter 关于性能方面的比较可以参考下图,横坐标是读写的数据量,纵坐标是耗时: 从实验结果可知 Room 和 GreenDAO 底层都是基于 SQLite,性能接近,在查询速度上 GreenDAO 表现更好一些; Realm 自有引擎的数据拷贝效率高,复杂对象也无需做映射,在性能表现上优势明显; ObjectBox 作为一个 KV 数据库,性能由于 SQL 也是预期中的。 图片缺少 SQLDelight 的曲线,实际性能与 GreeDAO 相近,在查询速度上优于 Room。 空间性能方面可参考上图( 50K 条记录的内存占用情况)。 Realm 需要加载 so 同时为了提高性能缓存数据较多,运行时内存占用最大,SQLite 系的数据库依托平台服务,内存开销较小,其中 GreenDAO 在运行时内存的优化是最好的。 ObjectBox 介于 SQLite 与 Realm 之间。 数据来源: proandroiddev.com/android-dat… 选型建议 上述个框架目前都在维护中,都存在不少用户,大家在选型上可以遵循以下原则: Room 虽然在性能上不具优势,但是作为 Google 的亲儿子,与 Jetpack 全家桶兼容最好,而且天然支持协程,如果你的项目只用在 Android 平台上且对性能不敏感,首推 Room ; 如果你的项目是一个 KMM 或其他跨平台应用,那么建议选择 SQLDelight ; 如果你对性能有比较高的需求,那么 Realm 无疑是更好的选择 ; 如果对查询条件没有过多要求,那么可以考虑 KV 型数据库的 ObjectBox,如果只用在 Android 平台,那么前不久 stable 的 DataStore 也是不错的选择。   原文章地址:http://www.androidchina.net/11980.html
1 0 166天前
poiok
477
本文作者:烧麦 目前,国内对应用程序安全隐私问题监管变的越来越严格。各个应用市场对APP上架也有比较严格的检查。云音乐今年也在Google Play上上架了一些海外的社交类业务。Google Play在审核应用的时候,也有相应的政策。当我们每次遇到问题的时候,需要根据检查方的信息对一些代码逻辑进行排查。 这是一个相对来说非常低效的过程。开发在平时写代码的时候一般不会使用敏感的API。大部分的敏感 API 调用都在一些三方 SDK 里面,或者一些敏感级别不是很高的 API,会存在多次调用的情况, 例如: 某应用在Google Play上架,云音乐内部的基础 SDK 里包括了一些国内的三方 SDK,这些SDK 使用了热修复或者动态下发 so 的功能。被 Google Play 发现拒审。 某应用在三方的检查中发现对地理位置的获取存在每 30 秒一次的频繁调用。 为了避免这类问题拖累 APP 上架,也为了提升检查的准确性和效率。笔者开发了一个针对 Android APK 的敏感方法调用的静态检查工具。 检查关键字,对于一些敏感 API 调用,例如 oaid、androidId 相关的调用。我们其实只要能检测到这些相关 API 里的一些关键字,找出整个 APP 里面有哪些地方直接调用了这些方法就可以了。 针对的上述的一些场景,这个工具具有两个方向的工作: APK 包的扫描,检查出整个APK中,哪些地方有对包含上面这些 API 关键字的直接调用。 运行时检查。针对运行时频繁调用这个场景,还是需要在运行时辅助检查特定API的调用情况。 工具方案 运行时 运行时的检测需要知道我们的方法在什么时候被调用了。那么被检测方法如果能有调用栈,那么我们在整改运行时的一些场景就会比较容易。这里我们用一个 Gradle 插件在 transfrom 里给我们需要检测的方法插入一行打印调用栈的代码即可。 这里利用 Javassist 给找到的方法插入一行调用栈打印就可以。 method.insertBefore( "android.util.Log.e("隐私方法调用栈", android.util.Log.getStackTraceString(new Throwable()));" ) 产物扫描 对APK的扫描思路其实也很简单,我们的诉求就是检查所有的代码。但是我们这时候只有一个 APK 文件。那最直接并且扫描简单的的就是想办法把我们的包转成Java代码,逐行扫描我们的 Java 代码,检查是否有敏感的 API 调用。 如果我们平时想看一个APK包里面的代码我们会怎么做呢,最简单的就是反编译这个APK,然后把里面的 dex 文件转成 Java 去查看。我们可以用脚本把这个流程再实现一遍。 第一步先解压APK文件,把里面的dex文件单独拿出来。 使用 dex2jar 把 dex 文件转成 jar 文件。 jar文件转成java文件 逐行扫描java文件 那么如何把jar文件转成java文件呢?我们平时点开Android Studio里面的jar或者aar就可以看到Java文件。我们也可以参考Android Studio的做法。 在 IDEA 的目录里面,我们其实可以找到相关功能依赖的 jar 包,也可以clone IDEA源码里面的相关模块自己打一个jar。 扫描工具的工作流程如图: 多APP配置 云音乐目前旗下APP比较多,不同的APP也有可能会有不同的扫描类型和不同的关键字规则。 配置如下: 每个 APP 目前最多会有两份配置: gp.json 和 privacy.json 分别对应 google play扫描和隐私合规扫描。 里面的配置包括 keys 扫描的关键字。 filterPackages 过滤掉的包名。如果我们关注是不是某些三方 SDK 写了一些不合规的代码,那么我们可以把自己的包名给过滤。避免输出结果太多。 扫描结果会输出一个 json 文件和 html 文件。json 文件可以对比上次的扫描结果,增量的输出新增的扫描结果。html 文件则用来展示扫描结果,辅助对应的排查人排查相关的问题。 例如,热修复等动态下发的相关技术,都会有关于 getField、ClassLoader 之类的关键字存在。 我们可以找到直接调用这些 API 的地方,从下图我们可以看到,很多调用都是在三方 SDK 里面找到的。 优化 第一版的合规扫描开发完后,在使用上还是有一些问题: 运行时:检查我们自己代码内的方法很容易,但是如果想要检测系统 API 的时候就无效了。因为Android Framework 的 API 不会参与打包。自然也不可能插入字节码。 产物扫描:jar 转 java 的过程非常的耗时。整体扫描时间会被拉到3-5分钟。 jar 转 java 的过程实际上也是一种反编译过程。因为 java 和 kotlin 语法的问题,某些会decompile 失败。这种情况多了的话,其实扫描是会有遗漏的。 扫描 java 文件是逐行遍历,把其他地方的关键字也扫描进来了,比如 field、import。这些扫描结果实际上是多余的。 针对上面这些问题,进行了针对性的优化。 运行时如果要检测系统 API 的调用,想到两种方案: transform 处理每个 class 和 jar 文件的时候,都去看下 class 内部的 method 有没有去调用这个系统 API。但是这个依赖字节码操作库的支持。 用一个专门的手机,用 xposed 之类的插件去 hook 系统 API。 第二种实现成本会比较高,不适合。但是运气比较好的是使用的 javassist 是支持第一种思路的操作的。 javassist 的 CtMethod 继承自 CtBehavior 对象。包括一个 instrument 方法。这个方法会找到方法内的表达式并允许替换。这里的表达式就包括 MethodCall 。 这样我们通过这个功能找到所有的调用就可以给直接调用了系统 API 的这个方法插入调用栈的打印。 运行期的检查就变成了: 完成这个优化之后,我们可以发现实际上在编译期的方法扫描,我们是通过直接读取 class 文件去做的。那么对于 APK 包,我们也可以采取类似的思路。用相同方法去读取 dex2jar 之后解压出来的 jar包里的 class 文件。 但是再仔细想想,Android 在 class 文件之后会有 dex 文件。Android 虚拟机直接执行的应该是 dex文件。而 dex 文件本质上只是一种二进制格式,最终会根据这个文件格式里的内容,按照汇编去执行。 思路到这里就清晰了,如果我们试着把 dex 文件直接反汇编成 smali 文件,去遍历 smali 文件可能效果会更好。 smail 语法介绍 一个 smail 文件对应一个 Java 的类,准确来说,是对应一个 .class 或者 .dex 文件。 内部类则会按照 ClassName$InnerClassA、ClassName$InnerClassB 的格式来命名。 smail 里面存在的基本类型,分别对应 Java 的基本类型,如下表所示: 类型关键字 Java 基本类型 V void Z boolean B byte S short C char I int J long F float D double smail一些常见的基本指令如下表: 指令 含义 .class 包名和类名 .super 父类 .source 源文件名称 .implements 接口实现 .field 变量 .method 方法 .end method 方法结束 .line 行数 .param 函数参数 .annotation 注解 .end annotation 注解结束 方法的调用也分为以下几种指令: 指令 含义 invoke-virtua 调用虚方法 invoke-static 调用静态方法 invoke-direct 调用没有被override的方法,例如private和构造方法 invoke-super 调用父类的方法 invoke-interface 调用接口方法 我们看一个示例 smali 文件的格式: .class public abstract Lcom/horcrux/svg/RenderableView; .super Lcom/horcrux/svg/VirtualView; .source "RenderableView.java" # static fields .field private static final CAP_BUTT:I = 0x0 .field static final CAP_ROUND:I = 0x1 # instance fields .field public fillOpacity:F .field public fillRule:Landroid/graphics/Path$FillType; .method static constructor <clinit>()V .registers 1 .line 97 invoke-static {v0}, Ljava/util/regex/Pattern;->compile(Ljava/lang/String;)Ljava/util/regex/Pattern; return-void .end method .method resetProperties()V .registers 4 .line 635 invoke-virtual {p0}, Ljava/lang/Object;->getClass()Ljava/lang/Class; invoke-virtual {v1, v2}, Ljava/lang/Class;->getField(Ljava/lang/String;)Ljava/lang/reflect/Field; return-void .end method smali 文件的开头会告诉类名、父类、源文件名。 这个文件的类名就是 com.horcrux.svg.RenderableView。父类是 com.horcrux.svg.VirtualView,源文件名为 RenderableView.java。 里面的变量和方法都有开头和结束的标记。 在 .method 里,我们可以看到 line 开头会标记行号 invoke- 开头会标记方法的调用 上面例子里包括两个方法: 构造方法。97行调了一个静态方法。 resetProperties 方法。在 635 行,调用了 getClass() 和 getField() 这两个虚函数。 对应的java代码则是: // line 635 Field field = this.getClass().getField((String)this.mLastMergedList.get(i)); 这里我们基本能定义出 smali 文件的扫描方式: 逐行读取一个smali文件,读到前面三行的时候,读取类的基本信息。 读取到 .method 和 .end method 的时候标记为读取到自己的方法。 读取到 .line 和下一个 .line 的时候,标记为读取到方法内的具体行号。 读取到 invoke- 开头的行,标记为读取到方法调用。如果此行末尾的方法签名满足我们的关键字匹配,就记录为扫描结果之一。 在实践中,我们可以使用开源的 baksmali.jar 进行dex转 smali的操作。使用上述规则直接扫描 smali 文件。避免了上面提到的缺陷。扫描时间也有很大的提升。基本上在半分钟左右都可以完成整个全量的扫描。省略了反编译jar包的巨长时间。 这个工具最终呈现为一个 jar 文件,通过命令行运行。在排查隐私合规可疑的 API 调用的时候,非常适用。 总结 通过这个工具, 在 APK 的隐私合规问题检查的时候,我们可以获取比较完整的可疑调用来辅助我们进行合规方面工作的处理。 这个工具的优势在于: APK 包是最终产物,扫描内容比较完整。 不在编译期进行扫描,不会降低开发效率。 但是这个工具还有一些不足之处,例如 不能精确定位到隐私函数调用具体归因在哪个模块或者 aar,难以集成在 CI/CD 进行归因处理。 比较难以获取完整的函数调用链。 所以我们还会继续进行编译期的合规检查工作。两者结合来完善相关的工作。   原文章地址:http://www.androidchina.net/11991.html
2 0 166天前
快速发帖 高级模式
关于薰豆 联系声明 友情链接 帮助中心 网站收录 MAP
QQ:85413540 湘ICP备14018790号-1
免责声明: 本网不承担任何由内容提供商提供的信息所引起的争议和法律责任。