授人以鱼不如授人以渔,当向别人请教了问题且被解答了疑惑后,或许也想知道对方是如何思考,如何按照一定的的逻辑得出最终的答案。故想分享一下我这6年Android开发中是如何解决问题的,一家之言,请同学们多多指教。
由于Android是开源系统,所以我们可以很方便地查看到源码,如果遇到代码运行不符合预期,或者是代码崩溃的问题,都可以定位到相应的代码位置,往上阅读代码梳理其逻辑然后分析原因,找到解决方案。
网上的解决方案始终是别人的,自己查看源码得到的方案才是印象最深刻的,也是收获最多的。
当Android Studio上无法往下继续追踪源码时,可去此网址阅读:AOSPXRef
一个Android开发者需要熟记的网站:Android 开发者 | Android Developers (google.cn)
关于Android的知识点都可以在这个网站上找到,比如说你想了解Android四大组件中Activity的生命周期,那么可以在这里看到生命周期的最详细解释:了解 Activity 生命周期 | Android 开发者 | Android Developers (google.cn),相信里边的那张生命周期流程图在很多文章中都出现过。
代码示例:示例 | Android 开发者 | Android Developers (google.cn),或者直接查看Github上的代码示例仓库:Android · GitHub
官方文档永远是第一手信息的来源处,开发Android的人写的文档会将所有的知识点都列罗上去,并且还有一些注意事项,而这些注意事项中描述的内容往往就是开发中容易遇到的问题。
人类之所以能够不断进步成为食物链顶端的物种,靠的就是知识的不断传承以及在此基础上的创新。
编程开发亦是如此,开发者应当维护两个知识库,一是新学习的内容及技巧库,二是问题库。
每次新学习到的知识或者是技巧都记录到内容库中,当下一次遇到该知识点时在内容库中查找的效率肯定比你在百度中查找更快。
问题库则是记录何时遇到了什么问题,是如何解决的,关键代码段是什么,查阅了什么资料,最好把资料链接也记录下来。
古希腊哲学家赫拉克利特说过:人不能两次踏进同一条河流。而对于程序员则是,程序员尽量不要写两次一模一样的Bug.
如上述3点都没有办法帮你解决问题,那只能是用搜索引擎查找答案了。由于众所周知的原因,使用google搜索是一件比较麻烦的事情。从个人经验来说,大多数的问题使用百度搜索也能解决。比如遇到报错问题,直接将报错异常粘贴到百度,大部分异常都有人遇到过并有相应的对策。
这里推荐一个可以访问的国外编程问答网站Stack Overflow ,有一些疑难杂症可以在这里找到答案,在百度上搜索小众问题时一般都是在这个网站上得到正确的解决方案。
不用过分迷恋google搜索,一是中文社区也有很多资料,能解决大部分问题;二是google搜索要将问题转换为英文才能得到精确的答案;三是找梯子始终是一件需要成本的事情,金钱或者时间。把它当成最后一个选项,是一个理智的选择。
某日程序员小Q完成某项功能调试时,程序崩溃报如下错误。
2022-09-18 22:47:06.493 24957-24957/cn.pigdreams.ampmdemo E/dreams.ampmdem: Invalid ID 0x00000003.
2022-09-18 22:47:06.493 24957-24957/cn.pigdreams.ampmdemo D/AndroidRuntime: Shutting down VM
2022-09-18 22:47:06.494 24957-24957/cn.pigdreams.ampmdemo E/AndroidRuntime: FATAL EXCEPTION: main
Process: cn.pigdreams.ampmdemo, PID: 24957
android.content.res.Resources$NotFoundException: String resource ID #0x3
at android.content.res.Resources.getText(Resources.java:453)
at android.widget.TextView.setText(TextView.java:6479)
at cn.pigdreams.ampmdemo.MainActivity$3.onClick(MainActivity.java:72)
at android.view.View.performClick(View.java:7570)
at android.view.View.performClickInternal(View.java:7525)
at android.view.View.access$3900(View.java:836)
at android.view.View$PerformClick.run(View.java:28680)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:263)
at android.app.ActivityThread.main(ActivityThread.java:8273)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:612)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1006)
2022-09-18 22:47:06.561 24957-24957/cn.pigdreams.ampmdemo I/Process: Sending signal. PID: 24957 SIG: 9
//报错处代码如下
int i = 3;
tvHello.setText(i);
依赖清晰的记忆和积累的众多经验迅速解决了,耗费时间15秒。
此乃宗师境界,非一朝一夕可达到,日积月累,反复记忆,终将达成。
此乃略有小成之程序员,会遇到2个分支。
入门不久的小Q,因经验较少,遇到了这个报错,手足无措。
大部分程序员会直接使用指导性原则中的第四点:面向百度编程。
搜索一番,发现原来是TextView.setText()中设置了一个数字,导致了程序崩溃,解决方案就是将数字转换成字符串TextView.setText()String.valueOf(i)
。
而后心想:好的,问题解决了,快点继续开发下一个任务。
这种方式耗时大概在5分钟左右,但一周过后又忘了这茬了,然后就是重复重复又重复。
如果按照指导性原则走呢?
因之前无问题集,无法从过往经验中找答案,那只能先阅读源码。
从报错的位置往上追溯,一步步找到报错的地方。
追溯到报错位置,发现setText()有好几个重载函数,一个接收字符参数,一个接收int参数(resid),出错的就是这个TextView中setText(@StringRes int resid)
函数。
/**
* Sets the text to be displayed using a string resource identifier.
*
* @param resid the resource identifier of the string resource to be displayed
*
* @see #setText(CharSequence)
*
* @attr ref android.R.styleable#TextView_text
*/
@android.view.RemotableViewMethod
public final void setText(@StringRes int resid) {
setText(getContext().getResources().getText(resid));
mTextSetFromXmlOrResourceId = true;
mTextId = resid;
}
//往下追踪到Resources类
/**
* Return the string value associated with a particular resource ID. The
* returned object will be a String if this is a plain string; it will be
* some other type of CharSequence if it is styled.
* {@more}
*
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
* entry. The value 0 is an invalid identifier.
*
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*
* @return CharSequence The string data associated with the resource, plus
* possibly styled text information.
*/
@NonNull public CharSequence getText(@StringRes int id) throws NotFoundException {
CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
if (res != null) {
return res;
}
throw new NotFoundException("String resource ID #0x"
+ Integer.toHexString(id));
}
定位到了抛异常的位置,原因就是Resources#getText()
这个方法中res变量没获取到值为空。为什么会为空呢,其实注释已经说的比较清楚了。我们把注释放到Google 翻译一下
返回与特定资源 ID 关联的字符串值。 如果这是一个纯字符串,则返回的对象将是一个字符串; 如果设置了样式,它将是其他类型的 CharSequence。
@param id 所需的资源标识符,由 aapt 工具生成。 此整数对包、类型和资源条目进行编码。 值 0 是无效标识符。
@throws NotFoundException 如果给定的 ID 不存在,则抛出 NotFoundException。
@return CharSequence 与资源关联的字符串数据,以及可能带有样式的文本信息。
从注释中可以得到关键信息,就是传入的resid(资源ID)不存在,所以抛出了NotFoundException
。
这里又引入了一个新知识点,资源ID,其实参数(@StringRes int resid)
中有个注解@StringRes
也可以大概猜到这个参数需要传入一个字符串资源。
搜索一下这个注解的含义,这里这个注解可能是标红的不能往下追,按下全局搜索@StringRes
,在androidx.annotation包中发现了这个注解。
/**
* Denotes that an integer parameter, field or method return value is expected
* to be a String resource reference (e.g. {@code android.R.string.ok}).
*/
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface StringRes {
}
//继续翻译注解
表示整数参数、字段或方法返回值应为字符串资源引用(例如 android.R.string.ok)。
从例如android.R.string.ok可以联想到我们在项目中一直在使用的R.id.tv_hello等资源ID。
至此一条完整的知识链路就清晰了。因为设置了一个无效的资源ID导致程序崩溃->随意设置的数字并不是一个有效的资源ID->资源ID是像R.string.ok这样的样式。
来验证一下猜想,我们找到R文件,看看里边资源ID的格式.资源ID在R文件中,路径为\app\build\intermediates\runtime_symbol_list\debug\R.txt
nt string abc_shareactionprovider_share_with_application 0x7f0e0019
int string abc_toolbar_collapse_description 0x7f0e001a
int string app_name 0x7f0e001b
int string appbar_scrolling_view_behavior 0x7f0e001c
int string bottom_sheet_behavior 0x7f0e001d
可见string类型映射的资源ID数值是以0x7f0e开头的一个整数,而小Q传入的是3,肯定在这个映射表里找不到。验证了3是无效的资源ID,所以程序崩溃了。
把问题记录下来,包含发生的时间、解决方案、关键代码、涉及到的知识点等信息
这些步骤走完,至少需要30分钟,虽然有些源代码会无法继续追下去只能通过阅读注释来得到答案。但是在这个过程中,把程序崩溃的根本原因搞清楚了,也把涉及到的知识点都搞清楚了,以后再遇到这种崩溃问题脑海中就会浮现这一链条式的知识点。就算暂时忘了,看一下问题集又马上回想起来了。
这30分钟积累下来的就是精髓知识点,会慢慢地刻在脑海,变成条件反映般的肌肉记忆,是非常值得的。在重复过个学习的过程中,Level1就会不知不觉的升级到了Level3,解决问题会是极快的速度。
小Q某天接到了一个使用Widget显示应用简要功能按钮的需求,关于这个Widget完全没有概念,平时也很少接触到。
小Q依旧上述原则,先找官方文档。找到了Widget的如下信息,知识了Widget的含义和类型。
然后再找代码示例,在上述指导文档中已经有部分代码贴出来了。完整的代码示例StackWidget会提示无法访问,其实这个示例就是在其源码中的Sample目录中,我们从源码网站中去找AOSPXRef。StackWidget所在的目录为development/samples/StackWidget/
,网页路径为StackWidget。如遇到点不开的代码示例链接,都可去此链接的sample目录中找着。
按照示例代码写一个widget的工程demo进行测试,如遇到问题再查看文档与示例代码。
通过官方文档学习到的新知识是最完整最系统的,会比个人博客的介绍会更加全面一些。如想让知识点更加熟练,可搜索相关的教程查看。
网站 | 网站说明 |
---|---|
AOSPXRef | 查看Android的源代码 |
Android 开发 | Android开发者中文官网 |
Android代码示例 | Android代码示例集合页 |
Android · GitHub | Android Github示例代码仓库 |
Stack Overflow | 编程问答网站 |
Google 翻译 | 用来翻译源码中的注释 |
sample | 开发者官网中不能点开的链接代码可在此找到 |