求解,初学android,报错“语法错误检查,将“}”插入到完整 ClassBody 中”是哪里出问题了?

23:33 提问
在jsp页面中写java代码时括号匹配出现问题
下面是错误提示
在此行找到多个注释:
- 语法错误,将“;”插入到完整 Statement 中
- 标记上有语法错误,删除这些标记
- 语法错误,将“}”插入到完整 ClassBody 中
- 标记“{”上具有语法错误,AssignmentOperator 无效
- 标记“}”上有语法错误,删除此标记
- 语法错误,将“AssignmentOperator Expression”插入到完整
Assignment 中
- 标记上具有语法错误,错误放置了构造
然后这是运行后jsp页面的提示
![图片说明](https://img-ask.csdn.net/upload//_556264.png)
括号都是对应的,检查了好几遍
按时间排序
&%=rs.getString("content")%&缺少一个=
最后面应该这样
这代码写的溜,为什么不适用struts标签,或c标签。
友情提示:&%%&这个标签里面只能是java代码
不是不提倡jsp里写java代码了吗
准确详细的回答,更有利于被提问者采纳,从而获得C币。复制、灌水、广告等回答会被删除,是时候展现真正的技术了!
其他相关推荐eclipse提示Syntax error, insert “EnumBody” to complete EnumDeclaration - 为程序员服务
为程序员服务
eclipse提示Syntax error, insert “EnumBody” to complete EnumDeclaration
从网上复制了一段代码,语法没有问题,eclipse却提示了“Syntax error, insert “EnumBody” to complete EnumDeclaration”的错误。
产生这个问题可能是由于空格个数导致的,我复制的这段代码的缩进空格个数都是三个,我把所有缩进都删掉,然后重新格式化后,问题就解决了。
产生这个问题的原因是代码是从网上复制的,网上复制的代码英文半角的空格是 & 这个并非真正的空格,而是 Non-breaking space, xml或者java文件中如果复制了这个符号,就会出现这种奇怪的语法错误。
您可能的代码
相关聚客文章
荣誉:2088
相关专栏文章&figure&&img src=&https://pic2.zhimg.com/v2-c884175fea74c73c6c6e3a1_b.jpg& data-rawwidth=&515& data-rawheight=&330& class=&origin_image zh-lightbox-thumb& width=&515& data-original=&https://pic2.zhimg.com/v2-c884175fea74c73c6c6e3a1_r.jpg&&&/figure&&p&这期是 HenCoder 自定义绘制的第 1-5 期:绘制顺序&/p&&p&之前的内容在这里:&br&&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&HenCoder Android 开发进阶 自定义 View 1-1 绘制基础&/a& &/p&&p&&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&HenCoder Android 开发进阶 自定义 View 1-2 Paint 详解&/a& &/p&&p&&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&HenCoder Android 开发进阶 自定义 View 1-3 文字的绘制&/a& &/p&&p&&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&HenCoder Android 开发进阶 自定义 View 1-4 Canvas 对绘制的辅助&/a&&/p&&p&如果你没听说过 HenCoder,可以先看看这个:&br&&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&HenCoder:给高级 Android 工程师的进阶手册&/a&&/p&&h2&&b&简介&/b&&/h2&&p&前面几期讲的是「术」,是「用哪些 API 可以绘制什么内容」。到上一期为止,「术」已经讲完了,接下来要讲的是「道」,是「怎么去安排这些绘制」。&/p&&p&这期是「道」的第一期:绘制顺序。&/p&&p&Android 里面的绘制都是按顺序的,先绘制的内容会被后绘制的盖住。比如你在重叠的位置先画圆再画方,和先画方再画圆所呈现出来的结果肯定是不同的:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-b0f9ea2c26c8cab3e0b2fa_b.jpg& data-rawwidth=&542& data-rawheight=&267& class=&origin_image zh-lightbox-thumb& width=&542& data-original=&https://pic4.zhimg.com/v2-b0f9ea2c26c8cab3e0b2fa_r.jpg&&&/figure&&p&而在实际的项目中,绘制内容相互遮盖的情况是很普遍的,那么怎么实现自己需要的遮盖关系,就是这期要讲的内容。&/p&&h2&&b&1 super.onDraw() 前 or 后?&/b&&/h2&&p&前几期我写的自定义绘制,全都是直接继承 View 类,然后重写它的 onDraw() 方法,把绘制代码写在里面,就像这样:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class AppView extends View {
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
... // 自定义绘制代码
&/code&&/pre&&/div&&p&这是自定义绘制最基本的形态:继承 View 类,在 onDraw() 中完全自定义它的绘制。&/p&&p&在之前的样例中,我把绘制代码全都写在了 super.onDraw() 的下面。不过其实,绘制代码写在 super.onDraw() 的上面还是下面都无所谓,甚至,你把 super.onDraw() 这行代码删掉都没关系,效果都是一样的——因为在 View 这个类里,onDraw() 本来就是空实现:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// 在 View.java 的源码中,onDraw() 是空的
// 所以直接继承 View 的类,它们的 super.onDraw() 什么也不会做
public class View implements Drawable.Callback,
KeyEvent.Callback, AccessibilityEventSource {
* Implement this to do your drawing.
* @param canvas the canvas on which the background will be drawn
protected void onDraw(Canvas canvas) {
&/code&&/pre&&/div&&p&然而,除了继承 View 类,自定义绘制更为常见的情况是,继承一个具有某种功能的控件,去重写它的 onDraw() ,在里面&b&添加&/b&一些绘制代码,做出一个「进化版」的控件:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-5e863b7c40d2a3d85faf1b865b948c19_b.jpg& data-rawwidth=&716& data-rawheight=&257& class=&origin_image zh-lightbox-thumb& width=&716& data-original=&https://pic3.zhimg.com/v2-5e863b7c40d2a3d85faf1b865b948c19_r.jpg&&&/figure&&blockquote&基于 EditText,在它的基础上增加了顶部的 Hint Text 和底部的字符计数。&/blockquote&&p&而这种基于已有控件的自定义绘制,就不能不考虑 super.onDraw() 了:你需要根据自己的需求,判断出你绘制的内容需要盖住控件原有的内容还是需要被控件原有的内容盖住,从而确定你的绘制代码是应该写在 super.onDraw() 的上面还是下面。&/p&&h2&&b&1.1 写在 super.onDraw() 的下面&/b&&/h2&&p&把绘制代码写在 super.onDraw() 的下面,由于绘制代码会在原有内容绘制结束之后才执行,所以绘制内容就会盖住控件原来的内容。&/p&&p&这是最为常见的情况:为控件增加点缀性内容。比如,在 Debug 模式下绘制出 ImageView 的图像尺寸信息:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class AppImageView extends ImageView {
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (DEBUG) {
// 在 debug 模式下绘制出 drawable 的尺寸信息
&/code&&/pre&&/div&&figure&&img src=&https://pic4.zhimg.com/v2-4f4bf03669_b.jpg& data-rawwidth=&255& data-rawheight=&242& class=&content_image& width=&255&&&/figure&&blockquote&这招很好用的,试过吗?&/blockquote&&p&当然,除此之外还有其他的很多用法,具体怎么用就取决于你的需求、经验和想象力了。&/p&&h2&&b&1.2 写在 super.onDraw() 的上面&/b&&/h2&&p&如果把绘制代码写在 super.onDraw() 的上面,由于绘制代码会执行在原有内容的绘制之前,所以绘制的内容会被控件的原内容盖住。&/p&&p&相对来说,这种用法的场景就会少一些。不过只是少一些而不是没有,比如你可以通过在文字的下层绘制纯色矩形来作为「强调色」:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class AppTextView extends TextView {
protected void onDraw(Canvas canvas) {
... // 在 super.onDraw() 绘制文字之前,先绘制出被强调的文字的背景
super.onDraw(canvas);
&/code&&/pre&&/div&&figure&&img src=&https://pic4.zhimg.com/v2-93a00b37bd6_b.jpg& data-rawwidth=&560& data-rawheight=&371& class=&origin_image zh-lightbox-thumb& width=&560& data-original=&https://pic4.zhimg.com/v2-93a00b37bd6_r.jpg&&&/figure&&p&&br&&/p&&h2&&b&2 dispatchDraw():绘制子 View 的方法&/b&&/h2&&p&讲了这几期,到目前为止我只提到了 onDraw() 这一个绘制方法。但其实绘制方法不是只有一个的,而是有好几个,其中 onDraw() 只是负责自身主体内容绘制的。而有的时候,你想要的遮盖关系无法通过 onDraw() 来实现,而是需要通过别的绘制方法。&/p&&p&例如,你继承了一个 LinearLayout,重写了它的 onDraw() 方法,在 super.onDraw() 中插入了你自己的绘制代码,使它能够在内部绘制一些斑点作为点缀:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class SpottedLinearLayout extends LinearLayout {
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
... // 绘制斑点
&/code&&/pre&&/div&&figure&&img src=&https://pic4.zhimg.com/v2-def93c5a769c7aa16afd7b_b.jpg& data-rawwidth=&194& data-rawheight=&228& class=&content_image& width=&194&&&/figure&&p&看起来没问题对吧?&/p&&p&但是你会发现,当你添加了子 View 之后,你的斑点不见了:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&SpottedLinearLayout
android:orientation=&vertical&
&ImageView ... /&
&TextView ... /&
&/SpottedLinearLayout&
&/code&&/pre&&/div&&figure&&img src=&https://pic4.zhimg.com/v2-c1c148fc832a_b.jpg& data-rawwidth=&473& data-rawheight=&300& class=&origin_image zh-lightbox-thumb& width=&473& data-original=&https://pic4.zhimg.com/v2-c1c148fc832a_r.jpg&&&/figure&&p&造成这种情况的原因是 Android 的绘制顺序:在绘制过程中,每一个 ViewGroup 会先调用自己的 onDraw() 来绘制完自己的主体之后再去绘制它的子 View。对于上面这个例子来说,就是你的 LinearLayout 会在绘制完斑点后再去绘制它的子 View。那么在子 View 绘制完成之后,先前绘制的斑点就被子 View 盖住了。&/p&&p&具体来讲,这里说的「绘制子 View」是通过另一个绘制方法的调用来发生的,这个绘制方法叫做:dispatchDraw()。也就是说,在绘制过程中,每个 View 和 ViewGroup 都会先调用 onDraw() 方法来绘制主体,再调用 dispatchDraw() 方法来绘制子 View。&/p&&blockquote&注:虽然 View 和 ViewGroup 都有 dispatchDraw() 方法,不过由于 View 是没有子 View 的,所以一般来说 dispatchDraw() 这个方法只对 ViewGroup(以及它的子类)有意义。&/blockquote&&figure&&img src=&https://pic4.zhimg.com/v2-e757ad80a7bf857d77353e33_b.jpg& data-rawwidth=&507& data-rawheight=&170& class=&origin_image zh-lightbox-thumb& width=&507& data-original=&https://pic4.zhimg.com/v2-e757ad80a7bf857d77353e33_r.jpg&&&/figure&&p&回到刚才的问题:怎样才能让 LinearLayout 的绘制内容盖住子 View 呢?只要让它的绘制代码在子 View 的绘制之后再执行就好了。&/p&&h2&&b&2.1 写在 super.dispatchDraw() 的下面&/b&&/h2&&p&只要重写 dispatchDraw(),并在 super.dispatchDraw() 的下面写上你的绘制代码,这段绘制代码就会发生在子 View 的绘制之后,从而让绘制内容盖住子 View 了。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class SpottedLinearLayout extends LinearLayout {
// 把 onDraw() 换成了 dispatchDraw()
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
... // 绘制斑点
&/code&&/pre&&/div&&figure&&img src=&https://pic3.zhimg.com/v2-c398ebfaae2066_b.jpg& data-rawwidth=&201& data-rawheight=&254& class=&content_image& width=&201&&&/figure&&blockquote&好萌的蝙蝠侠啊&/blockquote&&h2&&b&2.2 写在 super.dispatchDraw() 的上面&/b&&/h2&&p&同理,把绘制代码写在 super.dispatchDraw() 的上面,这段绘制就会在 onDraw() 之后、 super.dispatchDraw() 之前发生,也就是绘制内容会出现在主体内容和子 View 之间。而这个……&/p&&p&其实和前面 1.1 讲的,重写 onDraw() 并把绘制代码写在 super.onDraw() 之后的做法,效果是一样的。&/p&&p&能想明白为什么吧?图就不上了。&/p&&h2&&b&3 绘制过程简述&/b&&/h2&&p&绘制过程中最典型的两个部分是上面讲到的主体和子 View,但它们并不是绘制过程的全部。除此之外,绘制过程还包含一些其他内容的绘制。具体来讲,一个完整的绘制过程会依次绘制以下几个内容:&/p&&ol&&li&背景&/li&&li&主体(onDraw())&/li&&li&子 View(dispatchDraw())&/li&&li&滑动边缘渐变和滑动条&/li&&li&前景&/li&&/ol&&p&一般来说,一个 View(或 ViewGroup)的绘制不会这几项全都包含,但必然逃不出这几项,并且一定会严格遵守这个顺序。例如通常一个 LinearLayout 只有背景和子 View,那么它会先绘制背景再绘制子 View;一个 ImageView 有主体,有可能会再加上一层半透明的前景作为遮罩,那么它的前景也会在主体之后进行绘制。需要注意,前景的支持是在 Android 6.0(也就是 API 23)才加入的;之前其实也有,不过只支持 FrameLayout,而直到 6.0 才把这个支持放进了 View 类里。&/p&&p&这其中的第 2、3 两步,前面已经讲过了;第 1 步——背景,它的绘制发生在一个叫 drawBackground() 的方法里,但这个方法是 private 的,不能重写,你如果要设置背景,只能用自带的 API 去设置(xml 布局文件的 android:background 属性以及 Java 代码的 View.setBackgroundXxx() 方法,这个每个人都用得很 6 了),而不能自定义绘制;而第 4、5 两步——滑动边缘渐变和滑动条以及前景,这两部分被合在一起放在了 onDrawForeground() 方法里,这个方法是可以重写的。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-3ecf3b4f2fe_b.jpg& data-rawwidth=&586& data-rawheight=&409& class=&origin_image zh-lightbox-thumb& width=&586& data-original=&https://pic2.zhimg.com/v2-3ecf3b4f2fe_r.jpg&&&/figure&&p&滑动边缘渐变和滑动条可以通过 xml 的 android:scrollbarXXX 系列属性或 Java 代码的 View.setXXXScrollbarXXX() 系列方法来设置;前景可以通过 xml 的 android:foreground属性或 Java 代码的 View.setForeground() 方法来设置。而重写 onDrawForeground() 方法,并在它的 super.onDrawForeground() 方法的上面或下面插入绘制代码,则可以控制绘制内容和滑动边缘渐变、滑动条以及前景的遮盖关系。&/p&&h2&&b&4 onDrawForeground()&/b&&/h2&&blockquote&首先,再说一遍,这个方法是 API 23 才引入的,所以在重写这个方法的时候要确认你的 minSdk 达到了 23,不然低版本的手机装上你的软件会没有效果。&/blockquote&&p&在 onDrawForeground() 中,会依次绘制滑动边缘渐变、滑动条和前景。所以如果你重写 onDrawForeground() :&/p&&h2&&b&4.1 写在 super.onDrawForeground() 的下面&/b&&/h2&&p&如果你把绘制代码写在了 super.onDrawForeground() 的下面,绘制代码会在滑动边缘渐变、滑动条和前景之后被执行,那么绘制内容将会盖住滑动边缘渐变、滑动条和前景。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class AppImageView extends ImageView {
public void onDrawForeground(Canvas canvas) {
super.onDrawForeground(canvas);
... // 绘制「New」标签
&!-- 使用半透明的黑色作为前景,这是一种很常见的处理 --&
&AppImageView
android:foreground=&#& /&
&/code&&/pre&&/div&&figure&&img src=&https://pic2.zhimg.com/v2-0e3cc2c43168bcf1b914de9df08b91af_b.jpg& data-rawwidth=&576& data-rawheight=&304& class=&origin_image zh-lightbox-thumb& width=&576& data-original=&https://pic2.zhimg.com/v2-0e3cc2c43168bcf1b914de9df08b91af_r.jpg&&&/figure&&blockquote&左上角的标签并没有被黑色遮罩盖住,而是保持了原有的颜色。&/blockquote&&h2&&b&4.2 写在 super.onDrawForeground() 的上面&/b&&/h2&&p&如果你把绘制代码写在了 super.onDrawForeground() 的上面,绘制内容就会在 dispatchDraw() 和 super.onDrawForeground() 之间执行,那么绘制内容会盖住子 View,但被滑动边缘渐变、滑动条以及前景盖住:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class AppImageView extends ImageView {
public void onDrawForeground(Canvas canvas) {
... // 绘制「New」标签
super.onDrawForeground(canvas);
&/code&&/pre&&/div&&figure&&img src=&https://pic3.zhimg.com/v2-ba27c864a76b7b9ef6c13ac_b.jpg& data-rawwidth=&563& data-rawheight=&292& class=&origin_image zh-lightbox-thumb& width=&563& data-original=&https://pic3.zhimg.com/v2-ba27c864a76b7b9ef6c13ac_r.jpg&&&/figure&&blockquote&由于被半透明黑色遮罩盖住,左上角的标签明显变暗了。&/blockquote&&p&这种写法,和前面 2.1 讲的,重写 dispatchDraw() 并把绘制代码写在 super.dispatchDraw() 的下面的效果是一样的:绘制内容都会盖住子 View,但被滑动边缘渐变、滑动条以及前景盖住。&/p&&h2&&b&4.3 想在滑动边缘渐变、滑动条和前景之间插入绘制代码?&/b&&/h2&&p&很简单:不行。&/p&&p&虽然这三部分是依次绘制的,但它们被一起写进了 onDrawForeground() 方法里,所以你要么把绘制内容插在它们之前,要么把绘制内容插在它们之后。而想往它们之间插入绘制,是做不到的。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-be9dcb0aac125fdc6408_b.jpg& data-rawwidth=&300& data-rawheight=&294& class=&content_image& width=&300&&&/figure&&p&&br&&/p&&h2&&b&5 draw() 总调度方法&/b&&/h2&&p&除了 onDraw() dispatchDraw() 和 onDrawForeground() 之外,还有一个可以用来实现自定义绘制的方法: draw()。&/p&&p&draw() 是绘制过程的总调度方法。一个 View 的整个绘制过程都发生在 draw() 方法里。前面讲到的背景、主体、子 View 、滑动相关以及前景的绘制,它们其实都是在 draw() 方法里的。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// View.java 的 draw() 方法的简化版大致结构(是大致结构,不是源码哦):
public void draw(Canvas canvas) {
drawBackground(Canvas); // 绘制背景(不能重写)
onDraw(Canvas); // 绘制主体
dispatchDraw(Canvas); // 绘制子 View
onDrawForeground(Canvas); // 绘制滑动相关和前景
&/code&&/pre&&/div&&p&从上面的代码可以看出,onDraw() dispatchDraw() onDrawForeground() 这三个方法在 draw() 中被依次调用,因此它们的遮盖关系也就像前面所说的——dispatchDraw() 绘制的内容盖住 onDraw() 绘制的内容;onDrawForeground() 绘制的内容盖住 dispatchDraw()绘制的内容。而在它们的外部,则是由 draw() 这个方法作为总的调度。所以,你也可以重写 draw() 方法来做自定义的绘制。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-521fb32d9ef8d12cb2839_b.jpg& data-rawwidth=&1002& data-rawheight=&440& class=&origin_image zh-lightbox-thumb& width=&1002& data-original=&https://pic4.zhimg.com/v2-521fb32d9ef8d12cb2839_r.jpg&&&/figure&&p&&br&&/p&&h2&&b&5.1 写在 super.draw() 的下面&/b&&/h2&&p&由于 draw() 是总调度方法,所以如果把绘制代码写在 super.draw() 的下面,那么这段代码会在其他所有绘制完成之后再执行,也就是说,它的绘制内容会盖住其他的所有绘制内容。&/p&&p&它的效果和重写 onDrawForeground(),并把绘制代码写在 super.onDrawForeground() 时的效果是一样的:都会盖住其他的所有内容。&/p&&blockquote&当然了,虽说它们效果一样,但如果你既重写 draw() 又重写 onDrawForeground() ,那么 draw() 里的内容还是会盖住 onDrawForeground() 里的内容的。所以严格来讲,它们的效果还是有一点点不一样的。&br&但这属于抬杠……&/blockquote&&h2&&b&5.2 写在 super.draw() 的上面&/b&&/h2&&p&同理,由于 draw() 是总调度方法,所以如果把绘制代码写在 super.draw() 的上面,那么这段代码会在其他所有绘制之前被执行,所以这部分绘制内容会被其他所有的内容盖住,包括背景。是的,背景也会盖住它。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-46bcf14dba_b.jpg& data-rawwidth=&222& data-rawheight=&227& class=&content_image& width=&222&&&/figure&&p&是不是觉得没用?觉得怎么可能会有谁想要在背景的下面绘制内容?别这么想,有的时候它还真的有用。&/p&&p&例如我有一个 EditText:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-ba26b1c9fcbc_b.jpg& data-rawwidth=&503& data-rawheight=&118& class=&origin_image zh-lightbox-thumb& width=&503& data-original=&https://pic4.zhimg.com/v2-ba26b1c9fcbc_r.jpg&&&/figure&&p&它下面的那条横线,是 EditText 的背景。所以如果我想给这个 EditText 加一个绿色的底,我不能使用给它设置绿色背景色的方式,因为这就相当于是把它的背景替换掉,从而会导致下面的那条横线消失:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&EditText
android:background=&#66BB6A& /&
&/code&&/pre&&/div&&figure&&img src=&https://pic4.zhimg.com/v2-9a92a7d6416eae35cd42_b.jpg& data-rawwidth=&366& data-rawheight=&92& class=&content_image& width=&366&&&/figure&&blockquote&EditText:我到底是个 EditText 还是个 TextView?傻傻分不清楚。&/blockquote&&p&在这种时候,你就可以重写它的 draw() 方法,然后在 super.draw() 的上方插入代码,以此来在所有内容的底部涂上一片绿色:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public AppEditText extends EditText {
public void draw(Canvas canvas) {
canvas.drawColor(Color.parseColor(&#66BB6A&)); // 涂上绿色
super.draw(canvas);
&/code&&/pre&&/div&&figure&&img src=&https://pic4.zhimg.com/v2-20b8b601de0da3cbff8877_b.jpg& data-rawwidth=&376& data-rawheight=&93& class=&content_image& width=&376&&&/figure&&p&当然,这种用法并不常见,事实上我也并没有在项目中写过这样的代码。但我想说的是,我们作为工程师,是无法预知将来会遇到怎样的需求的。我们能做的只能是尽量地去多学习一些、多掌握一些,尽量地了解我们能够做什么、怎么做,然后在需求到来的时候,就可以多一些自如,少一些束手无策。&/p&&h2&&b&注意&/b&&/h2&&p&关于绘制方法,有两点需要注意一下:&/p&&ol&&li&出于效率的考虑,ViewGroup 默认会绕过 draw() 方法,换而直接执行 dispatchDraw(),以此来简化绘制流程。所以如果你自定义了某个 ViewGroup 的子类(比如 LinearLayout)并且需要在它的除 dispatchDraw() 以外的任何一个绘制方法内绘制内容,你&b&可能&/b&会需要调用 View.setWillNotDraw(false) 这行代码来切换到完整的绘制流程(是「可能」而不是「必须」的原因是,有些 ViewGroup 是已经调用过 setWillNotDraw(false) 了的,例如 ScrollView)。&/li&&li&有的时候,一段绘制代码写在不同的绘制方法中效果是一样的,这时你可以选一个自己喜欢或者习惯的绘制方法来重写。但有一个例外:如果绘制代码既可以写在 onDraw() 里,也可以写在其他绘制方法里,那么优先写在 onDraw() ,因为 Android 有相关的优化,可以在不需要重绘的时候自动跳过 onDraw() 的重复执行,以提升开发效率。享受这种优化的只有 onDraw() 一个方法。&/li&&/ol&&h2&&b&总结&/b&&/h2&&p&今天的内容就是这些:使用不同的绘制方法,以及在重写的时候把绘制代码放在 super.绘制方法() 的上面或下面不同的位置,以此来实现需要的遮盖关系。下面用一张图和一个表格总结一下:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-521fb32d9ef8d12cb2839_b.jpg& data-rawwidth=&1002& data-rawheight=&440& class=&origin_image zh-lightbox-thumb& width=&1002& data-original=&https://pic4.zhimg.com/v2-521fb32d9ef8d12cb2839_r.jpg&&&/figure&&blockquote&嗯,上面这张图在前面已经贴过了,不用比较了完全一样的。&/blockquote&&figure&&img src=&https://pic4.zhimg.com/v2-4c1faebbdcb4fb5a04d1fe1b8f0a3bcb_b.jpg& data-rawwidth=&943& data-rawheight=&504& class=&origin_image zh-lightbox-thumb& width=&943& data-original=&https://pic4.zhimg.com/v2-4c1faebbdcb4fb5a04d1fe1b8f0a3bcb_r.jpg&&&/figure&&p&另外别忘了上面提到的那两个注意事项:&/p&&ol&&li&在 ViewGroup 的子类中重写除 dispatchDraw() 以外的绘制方法时,可能需要调用 setWillNotDraw(false);&/li&&li&在重写的方法有多个选择时,优先选择 onDraw()。&/li&&/ol&&h2&&b&练习项目&/b&&/h2&&p&为了避免转头就忘,强烈建议你趁热打铁,做一下这个练习项目:&a href=&https://link.zhihu.com/?target=https%3A//github.com/hencoder/PracticeDraw5& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&HenCoderPracticeDraw5&/a&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-1d8bd9dadead_b.jpg& data-rawwidth=&1009& data-rawheight=&526& class=&origin_image zh-lightbox-thumb& width=&1009& data-original=&https://pic2.zhimg.com/v2-1d8bd9dadead_r.jpg&&&/figure&&p&&br&&/p&&h2&&b&下期预告&/b&&/h2&&p&下期是「道」的第二期:动画。&/p&&p&本来没想讲动画的,因为动画其实不属于自定义 View 的范畴。不过最近从各个渠道的反馈里发现有很多人对动画的掌握都比较模糊,而动画如果掌握得不好,自定义 View 的开发肯定也会受到限制。所以好吧,增加一期动画详解。 &/p&&p&顺便说一下,「道」一共有三期。在这三期过后,自定义 View 的第一部分:自定义绘制就结束了。&/p&&blockquote&预告图?什么预告图?不存在的。&/blockquote&&h2&&b&觉得赞?&/b&&/h2&&p&如果你看完觉得有收获,把文章转发到你的微博、微信群、朋友圈、公众号,让其他需要的人也看到吧。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-2cc73ac0dcafc2cb9d44cd5_b.jpg& data-rawwidth=&900& data-rawheight=&429& class=&origin_image zh-lightbox-thumb& width=&900& data-original=&https://pic1.zhimg.com/v2-2cc73ac0dcafc2cb9d44cd5_r.jpg&&&/figure&&p&&/p&
这期是 HenCoder 自定义绘制的第 1-5 期:绘制顺序之前的内容在这里:
&figure&&img src=&https://pic1.zhimg.com/v2-a2fae6511e07_b.jpg& data-rawwidth=&1024& data-rawheight=&576& class=&origin_image zh-lightbox-thumb& width=&1024& data-original=&https://pic1.zhimg.com/v2-a2fae6511e07_r.jpg&&&/figure&&p&Hello,大家好,我是Clock。这是我开通知乎专栏后分享的第一篇技术文章,年前对负责开发的一款直播应用做了安装包瘦身,本文是小小的梳理总结,希望对同行有所帮助。&/p&&h2&开篇语&/h2&&p&前阵子老大交给了我一个任务,主要是帮我们开发的直播应用做 Android 端的安装包瘦身,花了大概一周的时间把安装包从 18MB 减小到了 12.5MB。原本完全可以优化到 10MB 之下,但由于其他原因的限制,所以目前阶段只到 12.5MB 为止。在此记录一下优化的思路和用到的工具,方便自己以后 Review ,有需要的童鞋也可供参考。&/p&&h2&瘦身的目的&/h2&&p&从目的导向来看,我们是不会无缘无故去做一件事情的,那我们对应用瘦身的目的是为了什么?答案是:提高下载转化率。什么是下载转化率?举个栗子:你的应用大小是 18MB ,有100个潜在用户想要去下载尝试使用,结果有20个用户嫌弃安装包太大直接扬长而去,有20个用户在等待下载的过程中取消下载,最终只有60个用户真正下载安装,那么应用的下载转化率就是 60/100 = 60% 。&/p&&p&简单的小结便是:安装包越小,用户下载等待的时间越短,对手机存储配置小的设备体验愈佳,应用的下载转化率也就越高。记得以前在腾讯大讲堂听微信大牛说过,微信第一个版本只有差不多 400KB ,瞬间膜拜。&/p&&h2&安装包的组成&/h2&&p&要对安装包做瘦身,首先需要了解安装包的组成结构,这里简单的梳理了一下组成各个部分及其作用:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-59f1d35d787_b.png& data-rawwidth=&1071& data-rawheight=&537& class=&origin_image zh-lightbox-thumb& width=&1071& data-original=&https://pic4.zhimg.com/v2-59f1d35d787_r.jpg&&&/figure&&figure&&img src=&https://pic2.zhimg.com/v2-39aa3eb55e3c8b3a4fda50e5_b.png& data-rawwidth=&1280& data-rawheight=&237& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic2.zhimg.com/v2-39aa3eb55e3c8b3a4fda50e5_r.jpg&&&/figure&&p&其中,在安装包中占比较大的包括:dex文件、res文件夹、assets文件夹、lib文件夹以及resource.arsc文件。所以,接下来的瘦身优化就是让这些文件变小,以此达到瘦身的目的。&/p&&p&在 Android Studio 2.2.3 开始,就加入了浏览 APK 结构的功能,我们直接把安装包拖入 IDE ,就可以直接浏览其组成和对应大小,这样能够很方便的对比分析出每一步优化后的结果。&/p&&h2&资源瘦身&/h2&&p&了解完 APK 的组成,我们可以开始着手优化的工作了,因为资源文件在 APK 中的占比最高,所以优先从资源瘦身开始着手。&/p&&p&&b&&i&尽量只保存一份图片资源&/i&&/b&&br&&/p&&p&开发目录下会有个 drawable 或者 mipmap 目录用于适配不同 dpi 的屏幕,下面是不同命名目录所适配的 dpi 范围&/p&&figure&&img src=&https://pic1.zhimg.com/v2-2d9d1c3a5ed3e7480a70_b.png& data-rawwidth=&382& data-rawheight=&245& class=&content_image& width=&382&&&/figure&&p&目前市面上绝大部分机型都处于 xxhdpi 的适配范围,所以可以考虑只保留 xxhdpi 目录下一份图片资源,具体保留哪个目录下的资源和保留几份资源还得依照应用自身的实际机型分布决定。&/p&&p&&b&&i&使用 Drawable XML、Color、.9 PNG 代替 PNG&/i&&/b&&br&&/p&&ul&&li&一些情况下,我们可以考虑使用 Drawable XML 来代替 PNG,如:渐变的背景图,用几行 XML 就可以描绘出来,何必使用几十到上百K的 PNG 文件;&/li&&li&用 Color 代替 PNG,如:纯色的背景;&/li&&li&从性能上看,比起使用图片资源需要先将其生成 Bitmap 再传到底层交由 GPU 渲染,用 Drawable XML 和 Color 则更加高效,它是直接将 Shape 信息传到底层由 GPU 进行渲染,CPU 和 内存的占用会更少;&/li&&li&用 .9 PNG 代替 PNG,场景很多,不举例了;&/li&&/ul&&p&&b&&i&使用 JPG 代替 PNG&/i&&/b&&br&&/p&&p&用 JPG 代替 PNG,由于 JPG 没有 Alpha 通道,所以文件更小,适用于不需要透明度的图片可以考虑。&/p&&p&&i&&b&谨慎使用 WebP 代替 PNG&/b&&/i&&/p&&p&由于 WebP 效果好,且相同效果下, WebP 文件比 PNG 文件要小得多 ,所以,网上很多人说使用 WebP 代替 PNG,对此,我保持异议。理由如下:&/p&&ul&&li&WebP 在 Android 端,最低只支持 4.0 ,要兼容 4.0 以下的环境需要额外引入兼容库,反而增大安装包体积;&br&&/li&&li&Android Studio 不支持预览 WebP 图片,引用 WebP 的布局文件也无法预览显示;&br&&/li&&li&解压了 BAT 们的应用,以及同类竞品,基本没有发现在资源文件中用 WebP 的;&br&&/li&&/ul&&br&&p&&i&&b&有损编码格式的音频文件代替无损格式的音频文件&/b&&/i&&/p&&p&从下面这篇官方文档&/p&&p&&a href=&http://link.zhihu.com/?target=https%3A//developer.android.com/guide/topics/media/media-formats.html& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&developer.android.com/g&/span&&span class=&invisible&&uide/topics/media/media-formats.html&/span&&span class=&ellipsis&&&/span&&/a&&/p&&p&可以看到 Android 平台支持的音视频格式,下面列出有损和无损常用的格式(不要认为有损编码就是音质很差):&/p&&ul&&li&无损格式:WAV,PCM,ALS,ALAC,TAK,FLAC,APE,WavPack(WV)&br&&/li&&li&有损格式:MP3,AAC,WMA,Ogg Vorbis&br&&/li&&/ul&&br&&p&实际开发中需要使用音频文件尽量采用 MP3、Ogg 这种有损格式,尽量不要用 WAV、PCM 这种无损音频。&/p&&p&&i&&b&移除无用的资源&/b&&/i&&/p&&p&这里的移除无用资源文件主要分为两个部分:&b&不打包没有使用的资源和删除没有使用的资源&/b&。&/p&&ul&&li&不打包没有使用的资源,在项目的 build.gradle 中配置 shrinkResources true 即可。&/li&&/ul&&figure&&img src=&https://pic4.zhimg.com/v2-c6ff26eac2ae682577fcb1250fcf0c3b_b.png& data-rawwidth=&796& data-rawheight=&218& class=&origin_image zh-lightbox-thumb& width=&796& data-original=&https://pic4.zhimg.com/v2-c6ff26eac2ae682577fcb1250fcf0c3b_r.jpg&&&/figure&&ul&&li&删除没有使用的资源,通过 Android Studio 选中项目右键 =& Analyze =& Run Inspection by Name =& 输入 Unused Resuroces&/li&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-6e8ca6468feaaa8b4b0a6_b.png& data-rawwidth=&982& data-rawheight=&471& class=&origin_image zh-lightbox-thumb& width=&982& data-original=&https://pic3.zhimg.com/v2-6e8ca6468feaaa8b4b0a6_r.jpg&&&/figure&&p&即可看到所有未使用的资源文件,建议定期清理掉这些没用的文件,一方面可以减小工程的大小,另一方面太多的资源文件会导致打包后 resources.arsc 文件变得越来越大,公司有一项目 resources.arsc 文件已经达到 2-3 MB 的程度,有点惊人。&/p&&p&综合以上几点,就可以有效的精简我们安装包中的&b&res文件夹、assets文件夹、resource.arsc文件&/b&大小,从而达到瘦身目的。&/p&&h2&工具&/h2&&p&上一章节提到的是优化的思路,本章节整理在优化过程中使用到的工具。&/p&&ul&&li&TinyPNG:&a href=&http://link.zhihu.com/?target=https%3A//tinypng.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&TinyPNG – Compress PNG images while preserving transparency&/a& ,支持对 PNG/JPEG 文件做压缩处理,效果不错。&/li&&li&pngquant:&a href=&http://link.zhihu.com/?target=https%3A//pngquant.org/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&pngquant - lossy PNG compressor&/a& , 支持 PNG 压缩,有时候 TinyPNG 处理过的图片噪点会稍多,可以考虑用 pngquant 来处理。&/li&&li&ImageOptim:&a href=&http://link.zhihu.com/?target=https%3A//imageoptim.com/mac& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&better Save for Web&/a& ,支持压缩 PNG/JPEG/GIF ,而且效果显著,可以看看这里 &a href=&http://link.zhihu.com/?target=https%3A//www.diycode.cc/topics/496& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&分享一个非常好用的免费 Mac 图片无损压缩工具 ImageOptim&/a& ,遗憾的是它只支持 Mac ,Windows 党很难过。&/li&&li&mozjpeg:&a href=&http://link.zhihu.com/?target=https%3A//imageoptim.com/mozjpeg& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ImageOptim online&/a& , 用于 PNG 转 JPEG、JPEG 压缩,效果很好。&/li&&li&Adobe Audition CC:&a href=&http://link.zhihu.com/?target=http%3A//www.adobe.com/cn/products/audition.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Adobe Audition CC&/a& ,Adobe 出品,支持对音频的采样率,分辨率和声道数目做更改,以此达到裁剪音频的目的(采样率,分辨率和声道数目是音频文件格式的关键参数,决定着音频文件的大小)。&/li&&/ul&&p&以上是我优化过程中用到的觉得不错的工具,有更好的推荐,欢迎补充。&/p&&p&另外,在对图片做压缩的时候,不要贪图方便直接将整个资源目录下的图片一次性压缩一趟。很多时候,前面做这个项目的人可能已经对一些资源文件做过压缩处理,很容易导致二次压缩而引起一些图片失真。这里我建议是,去到应用的资源目录下将资源文件从大到小排序,定一个标准,如超过 20KB 的图片要做压缩处理,则将这些符合条件的图片 Copy 一份出来做压缩处理,处理后确保没出现失真的情况下再替换对应优化前的图片资源。 音频文件的处理,同理。&/p&&h2&Native库瘦身&/h2&&p&Native 库瘦身主要是减小对 CPU 架构的支持,配置起来很简单,在 build.gradle 使用 abiFilters 配置需要用到的 CPU 架构,并将不需要兼容的 so 文件从项目中移除即可。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-84dbf6ce5e8f_b.png& data-rawwidth=&578& data-rawheight=&213& class=&origin_image zh-lightbox-thumb& width=&578& data-original=&https://pic3.zhimg.com/v2-84dbf6ce5e8f_r.jpg&&&/figure&&p&根据我们用户的机型分布,最终只保留了对 armeabi-v7a 支持。注意,这里需要根据自家产品的实际情况来决定。由于之前对 CPU 的架构分布不是很熟悉,感谢微信的张绍文、沪江的徐宜生以及虎牙的郑晓滨几位老司机给我科普了一发。&/p&&p&综上所述,就可以有效的精简我们安装包中的 lib 文件夹大小,从而达到瘦身目的。也有一种做法是通过在 build.gradle 配置 include 来针对每个 CPU 架构生成单独的安装包,虽然看起来很不错,但是很多国内应用市场上架的时候并不支持这种每个 CPU 配置一个包的做法,所以此做法较为鸡肋,不太建议去做,如果应用只上 Google Play ,那确实要比配置 abiFilters 好得多。&/p&&h2&代码瘦身&/h2&&p&这里可以做的事情也是很多,主要如下:&/p&&ul&&li&移除废弃功能的代码,反正有 VCS ,删了代码随时可以找回;&/li&&li&移除重复的代码,如:已经有了的功能代码,团队成员不知道自己又写了一套,只能靠代码 Review 解决了;&/li&&li&移除功能重叠的框架,如:项目中有几套网络访问框架 Volley、AsyncHttpClient、Retrofit 等,同样只能靠代码 Review 解决;&/li&&li&移除无用的 dependencies 或者 jar 包;&/li&&li&减小对 Support 兼容包的依赖,Support-V4 包非常大,项目引入无疑会增大 dex 文件的大小,Google 已经意识到这个问题,所以 Support-V7 一开始就做了拆分,并且开始对 Support-V4 做拆分,虽然目前成果还不明显,不过还是蛮值得期待的,特别是发现你少了 Support-V4 包后,可能就从2个 dex 变成1个 dex 了呢;&/li&&li&插件化,一种懒加载思想的体现,先让用户能够安装宿主包,对于一些功能模块做插件化,在特定的时机再下载安装;&/li&&/ul&&p&综上所述,就可以有效的精简我们安装包中的 dex 文件大小,从而达到瘦身目的。&/p&&h2&结束语&/h2&&p&整个优化过程我把项目从 18 MB 优化到了 12.5 MB,以上有些优化点受其他一些原因的影响,只能暂时作罢,可以考虑纳入下一次的优化排期。套路大概就是这么些,实践的时候请根据自身项目定夺,并优先优化性价比较高的部分(性价比=可优大小/所需时间)。&/p&&p&大致就是这么多了,如果你对我的文章感兴趣,可以关注我的知乎专栏,除此之外,还可以关注我的&/p&&ul&&li&个人博客:&a href=&http://link.zhihu.com/?target=http%3A//blog.coderclock.com/& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&blog.coderclock.com/&/span&&span class=&invisible&&&/span&&/a&&/li&&li&新浪微博: &a href=&http://link.zhihu.com/?target=http%3A//weibo.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&D_clock爱吃葱花&/a&&/li&&li&微信公众号:&b&技术视界&/b&&/li&&/ul&
Hello,大家好,我是Clock。这是我开通知乎专栏后分享的第一篇技术文章,年前对负责开发的一款直播应用做了安装包瘦身,本文是小小的梳理总结,希望对同行有所帮助。开篇语前阵子老大交给了我一个任务,主要是帮我们开发的直播应用做 Android 端的安装包瘦…
这个问题很多年前已经问过了。&br&&br&结果就是开源版本被开源社区维护,市场跟厂商都拒绝谷歌的闭源新版本,然后安卓将被固定在开源的那个版本,并且大家会在那个开源版本开始继续发展,长此下去,谷歌将丧失对安卓发展的控制权。&br&&br&类似的事情已经发生过,结果证实了上面的所有推测,后来谷歌只好把下个版本开源。&br&&br&补充:&br&指望谷歌不作恶就只能呵呵了,谷歌当然想闭源,想得要命。然而开源协议卡得死死的,闭源的后果是它不能承受的。永远不要指望别人发善心,确保制度的可靠性更重要。
这个问题很多年前已经问过了。 结果就是开源版本被开源社区维护,市场跟厂商都拒绝谷歌的闭源新版本,然后安卓将被固定在开源的那个版本,并且大家会在那个开源版本开始继续发展,长此下去,谷歌将丧失对安卓发展的控制权。 类似的事情已经发生过,结果证实…
在PC上,所谓的装系统,不管是Windows也好还是不同发行版的Linux也好,真正“装”的是什么?是装驱动。&br&&br&以PC上Windows为例,Windows系统在安装时需要判断是你CPU是单核还是多核(以此确定使用什么类型的内核),安装的过程中,可能还需要重写引导程序,安装各种驱动,包括并不限于CPU、主板、内存、显卡、声卡、键盘、鼠标、显示器……&br&&br&如果刨除驱动、各种运行库、各种应用软件的话,现代操作系统真正的内核都是很小的,基本上都在几MB到几十MB之间(对于Linux来说,需要需要裁剪内核)。&br&&br&为什么PC上操作系统需要“安装”?&br&&br&因为不同PC的硬件配置不同,你的键盘可能是USB的,但别人的可能是PS/2的,你的显卡用是A卡,别人的可能是N卡,甚至可能是CPU里集成的显卡。因此,PC上操作系统的安装程序需要识别你PC上的硬件配置,并加以安装对应的驱动。&br&&br&那么手机上为什么只是刷机?&br&&br&因为通常来说,手机的刷机包只能针对特定一款手机型号。也就是说,刷机包的驱动只能适用于特定的某一款手机。&br&&br&为什么厂商要把刷机包设计成只适合某一款设备?&br&&br&因为移动设备的存储空间不是无限大,且一般来说不好直接扩容,那么操作系统就应该遵循最简的原则:只适配于当前的硬件配置。而且,而且如果厂商在一个操作系统里包含所有驱动,其开发成本是很高的。微软用了几十年的时间才积累下载的驱动开发经验,不是一般厂商能够达到的。&br&&br&所以,厂商在发布设备的时候时,显示准备好一个适配于当前硬件的刷机包,因为驱动和内核都是完全适配于当前硬件的,那么刷机过程中,真正“装”的东西就很少了。&br&&br&而如果要移动设备能像PC一样随便更换系统,该怎么做?&br&&br&1、厂商要开放引导程序,使得第三方安装程序能顺利启动;&br&&br&2、主流操作系统要有适配于移动设备的硬件驱动,不要以为开发驱动很简单,对于移动设备来说,硬件的具体配置未必都是公开的,那么要对一个封闭信息的硬件开发驱动并不是一件简单的事情。&br&&br&但是对于硬件厂商来说,开放可能意味着售后的麻烦,比如用户会抱怨兼容性不好啦之类的。并不符合厂商的利益。所以,暂时就这样封闭就好了。&br&&br&如果自己想做,怎么做?&br&&br&1、了解bootloader的原理(对于非操作系统相关的开发人员来说,需要几个月的时间),然后能修改并加载自己的启动镜像。&br&&br&2、自己能生成新的内核镜像,以及,编译出相关配套的驱动。生成内核镜像并不难,尤其是有了第一步的基础,但驱动就未必是简单的事情了。有些新硬件,根本就没有第三方驱动。&br&&br&3、或者更简单的方法,淘宝上找找,通过花钱的方式解决问题。&br&&br&总结来说,因为移动设备和PC的安装过程不同,所以,不要把PC上的观念套用的移动设备上,目前来说,还不太适用(但未来就很难说了)。真要学习这方面的知识,需要对操作系统有一定程度的了解。如果想达到目的,又不像学这些知识,那么花钱是唯一的途经。
在PC上,所谓的装系统,不管是Windows也好还是不同发行版的Linux也好,真正“装”的是什么?是装驱动。 以PC上Windows为例,Windows系统在安装时需要判断是你CPU是单核还是多核(以此确定使用什么类型的内核),安装的过程中,可能还需要重写引导程序,安装…
这是个好问题,也曾经困扰过我。&br&&br&确实,省掉工厂方法也可以实现多态。比如像下面这样,直接调用Service实例作为参数:向上转型成Service接口,去耦合。&br&&div class=&highlight&&&pre&&code class=&language-text&&interface Service {
void method1();
void method2();
public static void serviceConsumer(Service s) {
s.method1();
s.method2();
&/code&&/pre&&/div&&br&但!工厂方法模式真正的精髓并不在于多态去耦合,而是关键在于“&b&工厂模式&/b&”!在Big4的Design Pattern书里,工厂方法模式是和“虚拟工厂”模式,“生成器”模式,以及“单例器”模式,放在一起来讲的,同属于“创建型模式”。&br&&br&创建型模式最大的一个共同特征就是,把类型的“&b&定义过程”&/b&和“&b&实例化过程”&/b&分离开。也就是在类自身构造器之外,附加一个经常被误认为没什么卵用的“工厂类”,比如下面的ServiceFactory。&br&&div class=&highlight&&&pre&&code class=&language-text&&class ServiceA {
void method1(){};
void method2(){};
ServiceA(){};
class ServiceFactory {
Service getService(){}
&/code&&/pre&&/div&&br&但是实际上,&b&实际工作中工厂类作用巨大&/b&!&br&&br&我们经常会说,当一个类有很多不同的变种,就需要工厂类来隔离实例化过程。比如下面代码,&br&&div class=&highlight&&&pre&&code class=&language-text&&class ServiceFacotry {
Service getService(){
ServiceA sA = new ServiceA();
} else (b) {
ServiceB sB = new ServiceB();
else (c) {
ServiceC sC = new ServiceC();
&/code&&/pre&&/div&&br&这确实有用,但实际工作中,这并不是逼迫我们决定分离出工厂类最主要的原因。真正主要的动机,基于下面这个事实:&b&相比于继承,我们更优先使用组合&/b&。&br&&blockquote&&b&&Creational patterns become important as systems evolve to depend more on object composition than class inheritance.(《Design Pattern》P-81)&&/b&&/blockquote&题主既然读《Think in Java》,肯定很熟悉作者反复提到的“&b&组合优先于继承&/b&”的设计风格。对于组合的好处,可以参考“状态模式(State Pattern)”。利用多态,状态模式可以在runtime改变内部组件的类型,从而完全改变类的行为,因此比继承更灵活。&br&&br&所以,真正迫使我们决定分离出工厂类的原因,是因为实体类使用了组合,而且组件又非常地多。之前的例子中并没有说Service类型具体的结构,看不出设置工厂类有什么好处。但如果这种Service是由很多组件构成的庞大的家伙呢?比如说把Service换成是一个迷宫就出事了。假设一个迷宫是由三种不同的小单元组成的,分别是房间,墙和门。&br&&div class=&highlight&&&pre&&code class=&language-text&&class Maze {
//成员字段
//按顺序摆放成员字段组件
&/code&&/pre&&/div&&br&但问题是要生成一个迷宫,可能需要成百上千个门,墙,房组件,而且还要遵循一定的逻辑规则,因此构造器就会很复杂。更要命的是,迷宫有无数种,如果我们给每个迷宫都创建一个由上百个组件构成的实体类,然后再给出具体构造流程,那就累死了。&br&&br&这种情况下,合理的办法是写一个随机迷宫生成器,能根据具体要求不同生成无数种千奇百怪的迷宫,而不是写个死板的迷宫放在那里。这就是创建型模式的意义所在。我们可以看到无论是后面的“虚拟工厂”模式,还是“生成器”模式,当然也包括我们的工厂方法模式,都是在具体生成的策略上做文章。但一个大前提不变,就是具体产出的产品“Product”都是由很多小的组件组合而成,而且组装的时候灵活度都非常高,甚至是runtime用户定制的,或者根本就是随机的。这就导致组合出来的产品Product单独作为一个类存在的意义很小,反而是构造器的作用被放大。直接导致索性把构造过程独立出来成为一个方法,把这个方法用到的参数作为成员字段一起封装起来,再来个构造器只负责初始化这些参数,这不就又是一个新的类了吗?这种类就叫做“工厂类”。&br&&div class=&highlight&&&pre&&code class=&language-text&&class MazeFactory {
//巨大的迷宫生成算法
Maze mazeGenerator(int roomNum, int wallNum, int doorNum){
//构造器:初始化生成迷宫的参数
MazeFactory(){
roomNum=100;
wallNum=1000;
doorNum=200;
//字段:生成迷宫的参数
&/code&&/pre&&/div&&br&至于原来的那个迷宫类,本身的构造器就不承担任何功能了,也就是被阉割了。迷宫类的存在,也仅仅是在于在生成之后,怎么来玩了。&br&&div class=&highlight&&&pre&&code class=&language-text&&class Maze {
void play(){
//构造器被阉割,只有白板数据结构
Maze(int roomNum, int wallNum, int doorNum){
Room[] roomSet=new Room[roomNum];
Wall[] wallSet=new Wall[wallNum];
Door[] doorSet=new Door[doorSet];
//迷宫由room, wall, door组成
Room[] roomS
Wall[] wallS
Door[] doorS
&/code&&/pre&&/div&&br&所以再回到工厂方法,书里说的所有的东西,都是基于这个前提,也就是我们说好了啊,迷宫这东西的类文件里是没有具体构造方法的,都是要用工厂类MazeFactory来生成的。至于后来,我们加入了方迷宫,和方迷宫生成器。又有了圆迷宫和圆迷宫生成器。有了一堆生成器复杂了之后,又想到用多态来解耦,这都是后话,是在确定了使用工厂类的前提下,利用多态解耦的优化方案。所以才有了最初的这段代码:&br&&div class=&highlight&&&pre&&code class=&language-text&&//两个接口
interface Maze {
void play();
interface MazeFactory {
Maze mazeGenerator();
//各自继承类
CircularMaze implements Maze {
SquareMaze implements Maze {
CircularMazeFactory implements MazeFactory {
SquareMazeFactory implements MazeFactory {
//多态,面向接口
public static void mazeGame(MazeFactory fact) {
Maze z = fact.mazeGenerator();
&/code&&/pre&&/div&&br&所以,用大白话来说的话,&b&工厂方法就是“工厂”模式的多态解耦加强版&/b&。&br&&br&&br&&br&-------------------------------------------------------&br&我的笔记栈
&a href=&//link.zhihu.com/?target=http%3A//ciaoshen.com& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&ciaoshen.com&/span&&span class=&invisible&&&/span&&/a& (笔记向,非教程)
这是个好问题,也曾经困扰过我。 确实,省掉工厂方法也可以实现多态。比如像下面这样,直接调用Service实例作为参数:向上转型成Service接口,去耦合。 interface Service {
void method1();
void method2();
public static void serviceConsumer(Servi…
简短答案:“鸡?蛋”问题通常都是通过一种叫“自举”(&b&bootstrap&/b&)的过程来解决的。&br&&br&其实“鸡蛋问题”的根本矛盾就在于假定了“鸡”或“蛋”的其中一个要先进入“完全可用”的状态。而许多现实中被简化为“鸡蛋问题”的情况实际可以在“混沌”中把“鸡”和“蛋”都初始化好,而不存在先后问题;在它们初始化的过程中,两者都不处于“完全可用”状态,而完成初始化后它们就同时都进入了可用状态。&br&&br&打个比方,番茄炒蛋。并不是要先把番茄完全炒好,然后把鸡蛋完全炒好,然后把它们混起来;而是先炒番茄炒到半熟,再炒鸡蛋炒到半熟,然后把两个半熟的部分混在一起同时炒熟。&br&&br&引用题主的问题:&br&&blockquote&Java的对象模型中:&br&&ol&&li&&b&所有的类都是Class类的实例,Object是类,那么Object也是Class类的一个实例。&/b&&br&&/li&&li&所有的类都最终继承自Object类,Class是类,那么Class也继承自Object。&/li&&/ol&&/blockquote&这个问题中,第1个假设是错的:java.lang.Object是一个Java类,但并不是java.lang.Class的一个实例。后者只是一个用于描述Java类与接口的、用于支持反射操作的类型。这点上Java跟其它一些更纯粹的面向对象语言(例如Python和Ruby)不同。&br&而第2个假设是对的:java.lang.Class是java.lang.Object的派生类,前者继承自后者。&br&&br&虽然第1个假设不对,但“鸡蛋问题”仍然存在:在一个已经启动完毕、可以使用的Java对象系统里,必须要有一个java.lang.Class实例对应java.lang.Object这个类;而java.lang.Class是java.lang.Object的派生类,按“一般思维”前者应该要在后者完成初始化之后才可以初始化…&br&&br&事实是:这些相互依赖的核心类型完全可以在“混沌”中一口气都初始化好,然后对象系统的状态才叫做完成了“bootstrap”,后面就可以按照Java对象系统的一般规则去运行。JVM、JavaScript、Python、Ruby等的运行时都有这样的bootstrap过程。&br&&br&在“混沌”(boostrap过程)里,&br&&ul&&li&JVM可以为对象系统中最重要的一些核心类型先分配好内存空间,让它们进入[&b&已分配空间&/b&]但[&b&尚未完全初始化&/b&]状态。此时这些对象虽然已经分配了空间,但因为状态还不完整所以尚不可使用。&br&&/li&&li&然后,通过这些分配好的空间把这些核心类型之间的引用关系串好。到此为止所有动作都由JVM完成,尚未执行任何Java字节码。&br&&/li&&li&然后这些核心类型就进入了[&b&完全初始化&/b&]状态,对象系统就可以开始自我运行下去,也就是可以开始执行Java字节码来进一步完成Java系统的初始化了。&br&&/li&&/ul&&br&在HotSpot VM里,有一个叫做“Universe”的C++类用于记录对象系统的总体状态。它有这么两个有趣的字段记录当前是处于bootstrapping阶段还是已经完全初始化好:&br&&a href=&//link.zhihu.com/?target=http%3A//hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/ade5be2b1758/src/share/vm/memory/universe.hpp%23l399& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&jdk8u/jdk8u/hotspot: ade5be2b1758 src/share/vm/memory/universe.hpp&/a&&br&&div class=&highlight&&&pre&&code class=&language-text&&
static bool is_bootstrapping()
{ return _ }
static bool is_fully_initialized()
{ return _fully_ }
&/code&&/pre&&/div&然后Universe::genesis()函数会在bootstrap阶段中创建核心类型的对象模型:&br&&a href=&//link.zhihu.com/?target=http%3A//hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/ade5be2b1758/src/share/vm/memory/universe.cpp%23l259& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&jdk8u/jdk8u/hotspot: ade5be2b1758 src/share/vm/memory/universe.cpp&/a&&br&(“genesis”是创世纪的意思,多么形象)&br&其中会调用SystemDictionary::initialize()来初始化对象系统的核心类型:&br&&a href=&//link.zhihu.com/?target=http%3A//hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/ade5be2b1758/src/share/vm/classfile/systemDictionary.cpp%23l1814& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&jdk8u/jdk8u/hotspot: ade5be2b1758 src/share/vm/classfile/systemDictionary.cpp&/a&&br&其中会进一步跑到SystemDictionary::initialize_preloaded_classes()来创建java.lang.Object、java.lang.Class等核心类型:&br&&a href=&//link.zhihu.com/?target=http%3A//hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/ade5be2b1758/src/share/vm/classfile/systemDictionary.cpp%23l1875& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&jdk8u/jdk8u/hotspot: ade5be2b1758 src/share/vm/classfile/systemDictionary.cpp&/a&&br&这个函数在加载了java.lang.Object、java.lang.Class等核心类型后会调用Universe::fixup_mirrors()来完成前面说的“把引用关系串起来”的动作:&br&&div class=&highlight&&&pre&&code class=&language-cpp&&
&span class=&c1&&// Fixup mirrors for classes loaded before java.lang.Class.&/span&
&span class=&c1&&// These calls iterate over the objects currently in the perm gen&/span&
&span class=&c1&&// so calling them at this point is matters (not before when there&/span&
&span class=&c1&&// are fewer objects and not later after there are more objects&/span&
&span class=&c1&&// in the perm gen.&/span&
&span class=&n&&Universe&/span&&span class=&o&&::&/span&&span class=&n&&initialize_basic_type_mirrors&/span&&span class=&p&&(&/span&&span class=&n&&CHECK&/span&&span class=&p&&);&/span&
&span class=&n&&Universe&/span&&span class=&o&&::&/span&&span class=&n&&fixup_mirrors&/span&&span class=&p&&(&/span&&span class=&n&&CHECK&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&a href=&//link.zhihu.com/?target=http%3A//hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/ade5be2b1758/src/share/vm/memory/universe.cpp%23l485& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&jdk8u/jdk8u/hotspot: ade5be2b1758 src/share/vm/memory/universe.cpp&/a&&br&&div class=&highlight&&&pre&&code class=&language-cpp&&&span class=&kt&&void&/span& &span class=&n&&Universe&/span&&span class=&o&&::&/span&&span class=&n&&fixup_mirrors&/span&&span class=&p&&(&/span&&span class=&n&&TRAPS&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// Bootstrap problem: all classes gets a mirror (java.lang.Class instance) assigned eagerly,&/span&
&span class=&c1&&// but we cannot do that for classes created before java.lang.Class is loaded. Here we simply&/span&
&span class=&c1&&// walk over permanent objects created so far (mostly classes) and fixup their mirrors. Note&/span&
&span class=&c1&&// that the number of objects allocated at this point is very small.&/span&
&span class=&c1&&// ...&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&就是这样。&br&&br&=======================================================&br&&br&Python里的对象系统里也有这种“鸡蛋问题”,也是通过一个bootstrap过程来解决。&br&“鸡蛋问题”在于:Python里的所有类型都确实用一个type object表示,而所有类型都是object类的子类。&br&换句话说,&type 'type'&类是&type 'object'&的子类;而&type 'object'&既是类又是个对象,是&type 'type'&的实例。这个情况就跟题主原本所想像的Java里的情况一样——虽然Java并非如此。&br&关于CPython 2.5的对象系统初始化的剖析,可以参考&a href=&//link.zhihu.com/?target=http%3A//book.douban.com/subject/3117898/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Python源码剖析》&/a&的第12章。&br&具体到CPython 2.7.x的代码,&a href=&//link.zhihu.com/?target=https%3A//github.com/python/cpython/blob/2.7/Python/pythonrun.c%23L140& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&pythonrun.c的Py_InitializeEx()&/a&会做Python运行时的初始化,其中会调用&a href=&//link.zhihu.com/?target=https%3A//github.com/python/cpython/blob/2.7/Objects/object.c%23L2068& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&object.c的_Py_ReadyTypes()&/a&来按照一个列表的顺序初始化核心类型的type对象,具体的初始化动作在&a href=&//link.zhihu.com/?target=https%3A//github.com/python/cpython/blob/2.7/Objects/typeobject.c%23L3968& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&typeobject.c的PyType_Ready()&/a&。&br&&br&这些核心类型的type对象在CPython里的C层面的类型是PyTypeObject,其结构是确定的;它们的存储空间通过静态变量分配,例如&type 'type'&就声明为在&a href=&//link.zhihu.com/?target=https%3A//github.com/python/cpython/blob/2.7/Include/object.h%23L441& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&object.h的PyTypeObject PyType_Type&/a&,对应的还有&type 'object'&的PyTypeObject PyBaseObject_Type。&br&所以在进行初始化动作之前它们的存储空间就已经有着落了,真正做初始化时只要把它们的相互引用串起来就好。&br&&br&=======================================================&br&&br&Ruby里的鸡蛋问题跟Python比较相似。Ruby里的所有类都是Class类的实例,而Class类是Object类的子类。&br&&br&以CRuby 2.2.1为例,核心类型的初始化在这里:&a href=&//link.zhihu.com/?target=https%3A//github.com/ruby/ruby/blob/v2_2_1/class.c%23L511& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&class.c的Init_class_hierarchy()&/a&,可以看到也是典型的bootstrap过程:先分配空间,再把相互引用关系串起来,然后完成bootstrap开始进入正常的对象系统运作。&br&&br&&div class=&highlight&&&pre&&code class=&language-c&&&span class=&kt&&void&/span&
&span class=&nf&&Init_class_hierarchy&/span&&span class=&p&&(&/span&&span class=&kt&&void&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&cm&&/* 给核心类型的Class对象实例分配空间并串上它们的继承关系 */&/span&
&span class=&n&&rb_cBasicObject&/span& &span class=&o&&=&/span& &span class=&n&&boot_defclass&/span&&span class=&p&&(&/span&&span class=&s&&&BasicObject&&/span&&span class=&p&&,&/span& &span class=&mi&&0&/span&&span class=&p&&);&/span&
&span class=&n&&rb_cObject&/span& &span class=&o&&=&/span& &span class=&n&&boot_defclass&/span&&span class=&p&&(&/span&&span class=&s&&&Object&&/span&&span class=&p&&,&/span& &span class=&n&&rb_cBasicObject&/span&&span class=&p&&);&/span&
&span class=&n&&rb_cModule&/span& &span class=&o&&=&/span& &span class=&n&&boot_defclass&/span&&span class=&p&&(&/span&&span class=&s&&&Module&&/span&&span class=&p&&,&/span& &span class=&n&&rb_cObject&/span&&span class=&p&&);&/span&
&span class=&n&&rb_cClass&/span& &span class=&o&&=&/span&
&span class=&n&&boot_defclass&/span&&span class=&p&&(&/span&&span class=&s&&&Class&&/span&&span class=&p&&,&/span&
&span class=&n&&rb_cModule&/span&&span class=&p&&);&/span&
&span class=&n&&rb_const_set&/span&&span class=&p&&(&/span&&span class=&n&&rb_cObject&/span&&span class=&p&&,&/span& &span class=&n&&rb_intern_const&/span&&span class=&p&&(&/span&&span class=&s&&&BasicObject&&/span&&span class=&p&&),&/span& &span class=&n&&rb_cBasicObject&/span&&span class=&p&&);&/span&
&span class=&cm&&/* 让上面创建的Class对象实例的类型信息(klass字段)指向Class对象 */&/span&
&span class=&n&&RBASIC_SET_CLASS&/span&&span class=&p&&(&/span&&span class=&n&&rb_cClass&/span&&span class=&p&&,&/span& &span class=&n&&rb_cClass&/span&&span class=&p&&);&/span&
&span class=&n&&RBASIC_SET_CLASS&/span&&span class=&p&&(&/span&&span class=&n&&rb_cModule&/span&&span class=&p&&,&/span& &span class=&n&&rb_cClass&/span&&span class=&p&&);&/span&
&span class=&n&&RBASIC_SET_CLASS&/span&&span class=&p&&(&/span&&span class=&n&&rb_cObject&/span&&span class=&p&&,&/span& &span class=&n&&rb_cClass&/span&&span class=&p&&);&/span&
&span class=&n&&RBASIC_SET_CLASS&/span&&span class=&p&&(&/span&&span class=&n&&rb_cBasicObject&/span&&span class=&p&&,&/span& &span class=&n&&rb_cClass&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&然后看调用它的上层函数:&br&&div class=&highlight&&&pre&&code class=&language-c&&&span class=&cm&&/*!&/span&
&span class=&cm&& * Initializes the world of objects and classes.&/span&
&span class=&cm&& *&/span&
&span class=&cm&& * At first, the function bootstraps the class hierarchy.&/span&
&span class=&cm&& * It initializes the most fundamental classes and their metaclasses.&/span&
&span class=&cm&& * - \c BasicObject&/span&
&span class=&cm&& * - \c Object&/span&
&span class=&cm&& * - \c Module&/span&
&span class=&cm&& * - \c Class&/span&
&span class=&cm&& * After the bootstrap step, the class hierarchy becomes as the following&/span&
&span class=&cm&& * diagram.&/span&
&span class=&cm&& *&/span&
&span class=&cm&& * \image html boottime-classes.png&/span&
&span class=&cm&& *&/span&
&span class=&cm&& * Then, the function defines classes, modules and methods as usual.&/span&
&span class=&cm&& * \ingroup class&/span&
&span class=&cm&& */&/span&
&span class=&cm&&/* ... */&/span&
&span class=&kt&&void&/span&
&span class=&nf&&Init_Object&/span&&span class=&p&&(&/span&&span class=&kt&&void&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&n&&Init_class_hierarchy&/span&&span class=&p&&();&/span&
&span class=&cm&&/* ... */&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&
简短答案:“鸡?蛋”问题通常都是通过一种叫“自举”(bootstrap)的过程来解决的。 其实“鸡蛋问题”的根本矛盾就在于假定了“鸡”或“蛋”的其中一个要先进入“完全可用”的状态。而许多现实中被简化为“鸡蛋问题”的情况实际可以在“混沌”中把“鸡”…
&p&前段时间面试,自己以及小伙伴们简要的汇总的一些面试问题,可以对照的参考一下吧~&/p&&p&建议就是在面一家公司之前了解好这个公司的app是以什么为驱动的,例如电商的话肯定要了解hybrid 开发的一些知识点,图像处理的公司要好好复习下bitmap canvas相关的知识点,祝好运!&/p&&p&&br&&/p&&p&&br&&/p&&p&UI:&/p&&p&1.View的绘制&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//blog.csdn.net/guolin_blog/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android视图绘制流程完全解析,带你一步步深入了解View(二)&/a&&/p&&p&2)&a href=&//link.zhihu.com/?target=http%3A//blog.csdn.net/guolin_blog/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&& Android自定义View的实现方法&/a&&/p&&p&3)canvas&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//blog.csdn.net/qinjuning/article/details/6936783& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Andriod中绘(画)图----Canvas的使用详解&/a&&/p&&p&Activity:&/p&&p&1.onPause和onStop的区别
// &u&&b&这里有错误需要修改,修改在下方&/b&&/u&&/p&&p&onPause:可以看到这个activity (比如dialog) onStop:看不见这个activity&/p&&p&&br&&/p&&p&2.Activit用的启动方式&/p&&ul&&li&standard: 标准模式,一调用startActivity()方法就会产生一个新的实例。&/li&&li&singleTop: 如果已经有一个实例位于Activity栈的顶部时,就不产生新的实例,而只是调用Activity中的newInstance()方法。如果不位于栈顶,会产生一个新的实例。&/li&&li&singleTask: 会在一个新的task中产生这个实例,以后每次调用都会使用这个,不会去产生新的实例了。&/li&&li&singleInstance: 这个跟singleTask基本上是一样,只有一个区别:在这个模式下的Activity实例所处的task中,只能有这个activity实例,不能有其他的实例。&/li&&/ul&&p&3.ActivityManagerService&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//wiki.jikexueyuan.com/project/deep-android-v2/activity.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&第6章 深入理解ActivityManagerService&/a&&/p&&p&4.activity的启动过程:&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//www.cloudchou.com/android/post-788.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&深入理解Activity启动流程(一)&/a&&/p&&p&5.Activity之间的协作当一个activity A启动了另外一个activity B,它们的生命周期是有交叉的;&/p&&ul&&li&首先A的onPause()被调用;&/li&&li&e之后B的onCrate(), onStart()及onResume() 方法会被调用(此时B拥有用户焦点);&/li&&li&最后,如果A在屏幕上不可见,onStop()方法被调用;&/li&&li&因此,我们在两个activities中传递数据,或者共享资源时(如数据库连接),需要在前一个activity的onPause()方法而不是onStop()方法中进行;&/li&&/ul&&p&Service&/p&&p&1)service解析 &/p&&p&&a href=&//link.zhihu.com/?target=http%3A//www.cnblogs.com/newcj/archive//2061370.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android 中的 Service 全面总结&/a&&/p&&p&&br&&/p&&p&2)两种启动方式&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//android.tgbus.com/Android/tutorial/990.shtml& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Service 两种启动方式&/a&&/p&&p&&br&&/p&&p&Fragment&/p&&p&1)生命周期:&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//www.cnblogs.com/purediy/p/3276545.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&[整] Android Fragment 生命周期图&/a&&/p&&p&&br&&/p&&p&数据存储:&/p&&p&1)contentprovider&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//www.cnblogs.com/linjiqin/archive//2061396.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&十二、ContentProvider和Uri详解&/a&&/p&&p&2)sqlite&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//keeponmoving.iteye.com/blog/1487773& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android SQLite数据库操作实例&/a&&/p&&p&内存泄露:&/p&&p&&br&&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//my.oschina.net/rengwuxian/blog/181449& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android中使用Handler造成内存泄露的分析和解决&/a&&/p&&p&&br&&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//spencer-dev.lofter.com/post/d7b9e_6faf120& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&android开发中,可能会导致内存泄露的问题&/a&&/p&&p&1,不要让生命周期长于Activity的对象持有到Activity的引用&/p&&p& 2,尽量使用Application的Context而不是Activity的Context&/p&&p& 3,尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用(具体可以查看细话Java:”失效”的private修饰符了解)。如果使用静态内部类,将外部实例引用作为弱引用持有。&/p&&p& 4,垃圾回收不能解决内存泄露,了解Android中垃圾回收机制&/p&&p&网络&/p&&p&1)volley&/p&&p&&a href=&//link.zhihu.com/?target=https%3A//bxbxbai.github.io//android-working-with-volley/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android库Volley的使用介绍&/a&&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//blog.csdn.net/guolin_blog/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android Volley完全解析(四),带你从源码的角度理解Volley&/a&&/p&&p&2)如何控制TCP连接时的拥塞&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//blog.csdn.net/yechaodechuntian/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&TCP的流量控制和拥塞控制&/a&&/p&&p&3)三次握手&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//blog.csdn.net/whuslei/article/details/6667471& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&TCP协议中的三次握手和四次挥手(图解)&/a&&/p&&p&4)Android客户端和服务端如何使用Token和Session&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//wyong.blog.51cto.com/3352& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android客户端和服务端如何使用Token和Session&/a&&/p&&p&5) 移动端获取网络数据优化的几个点&/p&&p&
0. 连接复用 :&/p&&p&
节省连接建立时间,如开启 keep-alive。&/p&&p&
对于 Android 来说默认情况下 HttpURLConnection 和 HttpClient 都开启了
keep-alive。只是 2.2 之前 HttpURLConnection 存在影响连接池的 Bug,具体可见:&a href=&//link.zhihu.com/?target=http%3A//www.trinea.cn/android/android-http-api-compare/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android HttpURLConnection 及 HttpClient 选择&/a&&/p&&ol&&li&请求合并:&/li&&/ol&&p& 即将多个请求合并为一个进行请求,比较常见的就是网页中的 CSS Image Sprites。如果某个页面内请求过多,也可以考虑做一定的请求合并。&/p&&p&
2. 减少请求数据的大小:&/p&&p& 对于post请求,body可以做gzip压缩的,header也可以作数据压缩(不过只支持http 2.0)。&/p&&p&
3. 返回的数据的body也可以作gzip压缩,body数据体积可以缩小到原来的30%左右。(也可以考虑压缩返回的json数据的key数据的体积,尤其是针对返回数据格式变化不大的情况,支付宝聊天返回的数据用到了)&/p&&p&
4. 根据用户的当前的网络质量来判断下载什么质量的图片(电商用的比较多)&/p&&p&图像处理:&/p&&p&1.裁剪&/p&&p&2.Fresco:&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//fresco-cn.org/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Fresco | Fresco 中文说明&/a&&/p&&p&android缓存&/p&&p&1.LRUCache&/p&&p&GC:&/p&&p&1.GC过程:&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//chenchendefeng.iteye.com/blog/455883& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&JVM垃圾回收(GC)原理&/a&&/p&&p&内存:&/p&&p&1)内存分配:&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//www.cnblogs.com/zuoxiaolong/p/jvm1.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&JVM内存管理------JAVA语言的内存管理概述&/a&&/p&&p&多线程&/p&&p&1)线程池:&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//www.cnblogs.com/dolphin0520/p/3932921.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Java并发编程:线程池的使用&/a&&/p&&p&事件分发机制&/p&&p&1)Touch&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//www.cnblogs.com/sunzn/archive//3064129.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android 编程下 Touch 事件的分发和消费机制&/a&&/p&&p&算法:&/p&&p&1.TopK堆解决&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//xidajiancun.iteye.com/blog/2004940& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Java最小堆解决TopK问题&/a&&/p&&p&优化&/p&&p&1)Listview的优化&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//stackoverflow.com/questions//android-what-is-the-meaning-of-stableids& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&listview - Android&/a&&/p&&p&2)convertView是如何传递到adapter的getView方法的&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//heisedeyueya.iteye.com/blog/1689613& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ListView性能优化系列之三&/a&&/p&&p&Hybrid&/p&&p&1)java和JS的交互&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//droidyue.com/blog//interaction-between-java-and-javascript-in-android/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android中Java和JavaScript交互&/a&&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//rensanning.iteye.com/blog/2043049& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android中JavaScript和Native之间的Bridge&/a&&/p&&ul&&li&WebView开启JavaScript脚本执行&/li&&li&WebView设置供JavaScript调用的交互接口。&/li&&li&客户端和网页端编写调用对方的代码。&/li&&/ul&&p&-----------------------------------------------------------------------------------------------&/p&&p&感谢@ Jaeger 的修正,之前一直以为一个dialog弹出会触发当前的activity的onPause函数,其实并没有,onPause()触发的条件为当前的actiivty即将要退出activityStack的top,然而dialog并不是一个activity所以无法让activity退出的。写了个demo的确是没有弹出来onPause里面的toast,详细解释stackoverflow也有一个比较好的答案,上链接大家更正一下,不要影响到面试最重要!&/p&&a href=&//link.zhihu.com/?target=http%3A//stackoverflow.com/questions/7240916/& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic1.zhimg.com/v2-2d47e939feed796bcfc88_ipico.jpg& data-image-width=&316& data-image-height=&316& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android: Under what circumstances would a Dialog appearing cause onPause() to be called?&/a&&p&&br&&/p&&hr&&p&借帖子发一条头条研发招人帖子:&/p&&p&头条目前在北京/上海/深圳/武汉都有研发基地了, 年后有跳槽意愿的旁友们可以找我来给你们内推一下哟, 通过的话我会给你发个红包的哈哈哈, email : zhangxiang.&/p&
前段时间面试,自己以及小伙伴们简要的汇总的一些面试问题,可以对照的参考一下吧~建议就是在面一家公司之前了解好这个公司的app是以什么为驱动的,例如电商的话肯定要了解hybrid 开发的一些知识点,图像处理的公司要好好复习下bitmap canvas相关的知识点…
________________________________________________________________________________&br&&b&个人声明&/b&:&br&讲实话,本来这个声明不想写的,也感谢一些给我发私信的网友好心的催促。分享在于可以帮助到更多的同仁,也就仅此而已,不想文章部分引用了作者的某些内容(实际上我已经跟原文作者要求授权过,他已经应许我放上去,但还是受到了某些哗众取宠之人抹黑,以致挺好的答案被知乎管理员折叠,叔可忍,婶不能忍!)好吧,感觉知乎已经沦为某些挑梁小丑争权夺利的名利场了。为了其不知其所为的原创,必搅的人连分享的心情都没有了!倘若是这样的话,我倒是奉劝一句:希望保持下去,不过也希望你在每次开发的过程中,调用每个接口,每个功能的时候,也去征求原作者的同意,否则不是打自己的脸吗?(不@ 你啦!我又没有你下限那么低)&br&&b&文章的分享部分我会去用自己的话重新改一遍,只为重见天日,回馈一下支持我的知友!&/b&另外请不要动不动诋毁人家举报你什么的!能让你的评论赤裸裸的展出,也足矣证明我的坦荡荡吧!(别把知乎的人都当傻子,杀马特都去了帝吧,理性之人留在了知乎,再说我没事举报你干嘛?难道你不知道评论有直接删除的功能吗?【大笑】)&br&
&b&知乎管理员&/b&,麻烦下次折叠之前用心求证一下,就酱!&br&———————————————————————————————————————————&br&俗话说,轮子只造一遍,但是就是当今互联网发达到一塌糊涂的地步,信息的闭塞仍然是个问题,轮子被不同的地方,不同的程序猿造了一遍一遍又一遍!其实在}

我要回帖

更多关于 语法错误 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信