├── Android 4.4 中 WebView 使用注意事项.md ├── Android AIDL 原理解析.md ├── Android Ant 批量多渠道打包实例.md ├── Android平台上常用的定时器选择.md ├── Android应用正确使用扩展SD卡.md ├── README.md ├── 为RecyclerView增加Header和Footer.MD ├── 关于Kindle.md ├── 引用 android-support-v4.jar 时候冲突问题解决.md ├── 转:Android系统源码目录 system-framework 下各个jar包的用途.md ├── 通过Android源代码分析startActivity()过程(上).md ├── 通过Android源代码分析startActivity()过程(下).MD ├── 通过浏览器直接打开Android应用程序.md └── 通过源码分析Android 的消息处理机制.md /Android 4.4 中 WebView 使用注意事项.md: -------------------------------------------------------------------------------- 1 | # Android 4.4 中 WebView 使用注意事项 2 | 3 | ------ 4 | 5 | 自Android 4.4起,Android中的WebView开始基于Chromium( 这大概是因为Android部门负责人从Andy Rubin变成了Chrome部门的主管Sundar Pichai了吧,^_^)。 6 | 7 | 这个改变使得WebView的性能大幅度提升,并且对HTML5, CSS3, and JavaScript有了更好的支持。 8 | 9 | 那么,作为一个客户端开发者,我们写代码的时候需要注意哪些呢? 10 | 11 | # 1.多线程 12 | 13 | 如果你在子线程中调用WebView的相关方法,而不在UI线程,则可能会出现无法预料的错误。 14 | 15 | 所以,当你的程序中需要用到多线程时候,也请使用 runOnUiThread()方法来保证你关于WebView的操作是在UI线程中进行的: 16 | 17 | ```java 18 | 19 | runOnUiThread(newRunnable(){ 20 | @Override 21 | publicvoid run(){ 22 | // Code for WebView goes here 23 | } 24 | }); 25 | 26 | ``` 27 | 28 | # 2.线程阻塞 29 | 30 | 永远不要阻塞UI线程,这是开发Android程序的一个真理。虽然是真理,我们却往往不自觉的犯一些错误违背它,一个开发中常犯的错误就是:在UI线程中去等待JavaScript 的回调。 31 | 32 | 例如: 33 | 34 | ```java 35 | // This code is BAD and will block the UI thread 36 | webView.loadUrl("javascript:fn()"); while(result ==null){ 37 | Thread.sleep(100); } 38 | ``` 39 | 40 | 千万不要这样做,Android 4.4中,提供了新的Api来做这件事情。 41 | evaluateJavascript() 就是专门来异步执行JavaScript代码的。 42 | 43 | # 3.evaluateJavascript() 方法 44 | 45 | 专门用于异步调用JavaScript方法,并且能够得到一个回调结果。 46 | 47 | 示例: 48 | 49 | ```java 50 | mWebView.evaluateJavascript(script, new ValueCallback() { 51 | @Override 52 | public void onReceiveValue(String value) { 53 | //TODO 54 | } 55 | }); 56 | ``` 57 | 58 | # 4.处理 WebView 中 url 跳转 59 | 60 | 新版WebView对于自定义scheme的url跳转,新增了更为严格的限制条件。 61 | 当你实现了 shouldOverrideUrlLoading() 或 shouldInterceptRequest() 回调,WebView 也只会在跳转url是合法Url时才会跳转。 62 | 63 | 例如,如果你使用这样一个url : 64 | 65 | ``` java 66 | Show Profile 67 | ``` 68 | 69 | shouldOverrideUrlLoading() 将不会被调用。 70 | 71 | 正确的使用方式是: 72 | 73 | ``` java 74 | Show Profile 75 | ``` 76 | 77 | 对应的检测Url跳转的方式: 78 | 79 | ```java 80 | // The URL scheme should be non-hierarchical (no trailing slashes) 81 | privatestaticfinalString APP_SCHEME ="example-app:"; 82 | 83 | @Override publicboolean shouldOverrideUrlLoading(WebView view,String 84 | url){ 85 | if(url.startsWith(APP_SCHEME)){ 86 | urlData =URLDecoder.decode(url.substring(APP_SCHEME.length()),"UTF-8"); 87 | respondToData(urlData); 88 | returntrue; 89 | } 90 | returnfalse; } 91 | ``` 92 | 93 | 当然,也可以这样使用: 94 | 95 | ```java 96 | webView.loadDataWithBaseURL("example-app://example.co.uk/", HTML_DATA, 97 | null,"UTF-8",null); 98 | ``` 99 | 100 | # 5.UserAgent 变化 101 | 102 | 如果你的App对应的服务端程序,会根据客户端传来的UserAgent来做不同的事情,那么你需要注意的是,新版本的WebView中,UserAgent有了些微妙的改变: 103 | 104 | ```java 105 | Mozilla/5.0 (Linux; Android 4.4; Nexus 4 Build/KRT16H) 106 | AppleWebKit/537.36(KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 107 | Mobile Safari/537.36 108 | ``` 109 | 110 | 使用 getDefaultUserAgent()方法可以获取默认的UserAgent,也可以通过: 111 | 112 | ```java 113 | mWebView.getSettings().setUserAgentString(ua); 114 | mWebView.getSettings().getUserAgentString(); 115 | ``` 116 | 117 | 来设置和获取自定义的UserAgent。 118 | 119 | # 6.使用addJavascriptInterface()的注意事项 120 | 121 | 从Android4.2开始。 122 | 只有添加 @JavascriptInterface 声明的Java方法才可以被JavaScript调用,例如: 123 | 124 | ```java 125 | class JsObject { 126 | @JavascriptInterface 127 | public String toString() { return "injectedObject"; } 128 | } 129 | webView.addJavascriptInterface(new JsObject(), "injectedObject"); 130 | webView.loadData("", "text/html", null); 131 | webView.loadUrl("javascript:alert(injectedObject.toString())"); 132 | ``` 133 | 134 | # 7.Remote Debugging 135 | 136 | 新版的WebView还提供了一个很厉害的功能:使用Chrome来调试你运行在WebView中的程序。 137 | 138 | 具体可以看: 139 | [remote-debugging][1] 140 | ![remote-debugging][2] 141 | 142 | [1]: https://developers.google.com/chrome-developer-tools/docs/remote-debugging 143 | [2]: https://developer.chrome.com/devtools/docs/remote-debugging/remote-debug-banner.png 144 | -------------------------------------------------------------------------------- /Android AIDL 原理解析.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundong/blog/94c3b598141210a428796a8cc4078b4d66cbe619/Android AIDL 原理解析.md -------------------------------------------------------------------------------- /Android Ant 批量多渠道打包实例.md: -------------------------------------------------------------------------------- 1 | # Android Ant 批量多渠道打包实例 2 | 3 | ------ 4 | 5 | 关于批量打包,无需多言,这是每个国内Android开发者必须面对的一个问题。 6 | 7 | 下面,我就以开源项目「[知乎小报][1]」为例,详细说明如何使用ANT实现批量打渠道包。 8 | 9 | ## 1 Ant 安装 10 | 11 | * 下载ANT 12 | 13 | 请前往 [http://ant.apache.org][2] 下载。 14 | 15 | * 配置环境变量 16 | 17 | 设置环境变量后,在命令行下测试ant命令,如果出现以下内容,则说明配置成功: 18 | 19 | ``` shell 20 | cundongdeMacBook-Pro:~ cundong$ ant 21 | Buildfile: build.xml does not exist! 22 | Build failed 23 | ``` 24 | * ant-contrib-1.0b3.jar下载 25 | 26 | 由于ant本身不支持迭代,因此我们需要用到一个第三方的库 ant-contrib来实现迭代功能。 27 | 28 | 下载ant-contrib,并将ant-contrib-1.0b3.jar文件拷贝至ANT安装目录。 29 | 30 | 下载地址:[http://ant-contrib.sourceforge.net/][3] 31 | 32 | ## 2 生成local.properties、build.xml文件 33 | 34 | 先介绍一下iZhihuPaper的工程依赖情况。 35 | 36 | * iZhihuPaper 依赖 actionbarpulltorefresh.extras.actionbarsherlock、Crouton、PhotoView 37 | 38 | * actionbarpulltorefresh.extras.actionbarsherlock 依赖 ActionBarPullToRefresh 39 | 40 | * ActionBarPullToRefresh 依赖 actionbarsherlock、SmoothProgressBar 41 | 42 | * Crouton、actionbarsherlock 依赖 SupportLib 43 | 44 | * PhotoView、SmoothProgressBar、SupportLib 无任何依赖 45 | 46 | ###生成方式 47 | 48 | 我们需要为iZhihuPaper工程和他直接或者间接引用的所有工程(一共7个)都生成local.properties、build.xml文件。 49 | 50 | 命令格式: 51 | 52 | android update project --target {target版本} --name {工程名字} --path {工程目录} 53 | 54 | 依次执行以下命令: 55 | 56 | 1. 57 | ``` shell 58 | android update project --target android-20 --name SupportLib --path /Users/cundong/Documents/github/SupportLib 59 | ``` 60 | 61 | 2. 62 | ``` shell 63 | android update project --target android-20 --name PhotoView --path /Users/cundong/Documents/github/PhotoView 64 | ``` 65 | 66 | 3. 67 | ``` shell 68 | android update project --target android-20 --name SmoothProgressBar --path /Users/cundong/Documents/github/SmoothProgressBar 69 | ``` 70 | 71 | 4. 72 | ``` shell 73 | android update project --target android-20 --name Crouton --path /Users/cundong/Documents/github/Crouton 74 | ``` 75 | 76 | 5. 77 | ``` shell 78 | android update project --target android-20 --name actionbarsherlock --path /Users/cundong/Documents/github/actionbarsherlock 79 | ``` 80 | 81 | 6. 82 | ``` shell 83 | android update project --target android-20 --name ActionBarPullToRefresh --path /Users/cundong/Documents/github/ActionBarPullToRefresh 84 | ``` 85 | 86 | 7. 87 | ``` shell 88 | android update project --target android-20 --name actionbarpulltorefresh.extras.actionbarsherlock --path /Users/cundong/Documents/github/actionbarpulltorefresh.extras.actionbarsherlock 89 | ``` 90 | 91 | 8. 92 | ``` shell 93 | android update project --target android-20 --name iZhihuPaper --path /Users/cundong/Documents/github/iZhihuPaper 94 | ``` 95 | 96 | ###注意事项 97 | 98 | * BUILD SCUUCESS 99 | 如果执行命令后,出现如下所示: 100 | ``` shell 101 | cundongdeMacBook-Pro:~ cundong$ android update project --target android-20 --name SupportLib --path /Users/cundong/Documents/github/SupportLib 102 | Updated project.properties 103 | Updated project.properties 104 | Added file /Users/cundong/Documents/github/SupportLib/build.xml 105 | Updated file /Users/cundong/Documents/github/SupportLib/proguard-project.txt 106 | It seems that there are sub-projects. If you want to update them 107 | please use the --subprojects parameter. 108 | ``` 109 | 110 | 则说明执行成功。 111 | 112 | * 常见BUILD FAILED问题 113 | 114 | 如果执行后,出现如下提示: 115 | ``` shell 116 | BUILD FAILED 117 | /Users/cundong/Documents/github/iZhihuPaper/build.xml:44: The following error occurred while executing this line: 118 | /Users/cundong/Documents/github/iZhihuPaper/build.xml:59: The following error occurred while executing this line: 119 | /Applications/adt-bundle-mac-x86_64/sdk/tools/ant/build.xml:470: Invalid file: /Users/cundong/Documents/github/SmoothProgressBar/build.xml 120 | ``` 121 | 122 | 则说明它所依赖的工程缺少project.properties、project.properties文件,请先参照步骤1,为其依赖的工程生成project.properties、project.properties文件。 123 | 124 | 如果遇到以下问题: 125 | 126 | ``` shell 127 | BUILD FAILED 128 | /Applications/adt-bundle-mac-x86_64-20140624/sdk/tools/ant/build.xml:601: The following error occurred while executing this line: 129 | /Applications/adt-bundle-mac-x86_64-20140624/sdk/tools/ant/build.xml:653: The following error occurred while executing this line: 130 | /Applications/adt-bundle-mac-x86_64-20140624/sdk/tools/ant/build.xml:698: null returned: 1 131 | ``` 132 | 133 | 则需要手动删除该工程的gen、bin目录。 134 | 135 | ## 配置 local.properties 136 | 137 | 配置local.properties文件,增加ant.dir、target.dir: 138 | 139 | ``` shell 140 | sdk.dir=/Applications/adt-bundle-mac-x86_64/sdk 141 | ant.dir=/Applications/apache-ant-1.9.4 142 | target.dir=/Users/cundong/Documents/ZhihuPaperRelease 143 | ``` 144 | ant.dir为ant安装目录,target.dir为批量打包的apk存储目录。 145 | 146 | 详细例子可参考:[ZhihuPaper/local.properties][4] 147 | 148 | ## 3 添加 ant.properties文件 149 | 150 | 1.将签名文件(*.keystore)拷贝到工程的目录。 151 | 152 | 2.在根目录下新建ant.properties文件。 153 | 154 | ``` shell 155 | key.store=android.keystore 156 | key.alias=android 157 | key.store.password=Cundong123456!@# 158 | key.alias.password=Cundong123456!@# 159 | market_channels=Wandoujia,360 160 | app_name=ZhihuPaper 161 | app_version=2.1 162 | ``` 163 | 164 | 说明: 165 | key.store为签名文件; 166 | key.alias为签名文件别名; 167 | key.store.password、key.alias.password为密码; 168 | market_channels为我们需要生成的所有渠道列表,使用“,”分开;app_name为生成apk的文件名; 169 | app_version为生成apk的版本号; 170 | 171 | 详细例子可参考:[ZhihuPaper/ant.properties][5] 172 | 173 | ##配置build.xml 174 | 175 | 为了实现批量打出多个渠道包,我们必须手动对刚刚生成的build.xml文件进行修改。 176 | 177 | * 引入ant.properties文件。 178 | 179 | ```xml 180 | 181 | ``` 182 | 183 | * 支持循环执行 184 | 185 | ```xml 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | Run ant-contrib-1.0b3.jar ok 194 | ``` 195 | * 配置循环打包代码 196 | 197 | ```xml 198 | 199 | 200 | 201 | 202 | 203 | 204 | Run '${channel}' apk 205 | 206 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | ``` 219 | 配置后,会读取ant.properties中market_channels中配置项,得到一个渠道号数组,对这个数据进行迭代,替换AndroidMainfext.xml文件中的android:name="UMENG_CHANNEL"。 220 | 221 | 每替换好一个,将输出到"out.final.file"。 222 | 223 | ${target.dir},即为local.properties文件中配置的target.dir=/Users/cundong/Documents/ZhihuPaperRelease 224 | ${app_name},即为ant.properties文件中配置的app_name=ZhihuPaper 225 | ${app_version},即为ant.properties文件中配置的app_version=2.1 226 | ${channel},即为当前循环的渠道号 227 | 228 | 请务必保证${target.dir}/${app_version}目录真是存在并且有写权限。 229 | 230 | 当前例子中为:/Users/cundong/Documents/ZhihuPaperRelease/2.1,如果这么目录不存在,则会提示报错信息。 231 | 232 | 详细例子可参考:[ZhihuPaper/build.xml][6] 233 | 234 | ## 4 配置proguard-project.txt文件 235 | 236 | proguard-project.txt,即混淆时的配置文件。 237 | 238 | * 引用的第三方jar包,不要混淆; 239 | * 自己写的控件,即需要配置在layout文件中的Widget,不要混淆; 240 | * Android的基础组件,不要混淆。 241 | 242 | * 需要在project.properties中配置:proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 243 | 244 | 详细例子可参考: [ZhihuPaper/proguard-project.txt][7] 245 | 246 | ## 5 打包 247 | 在iZhihuPaper中创建一个批处理文件,Mac为*.sh文件,Window为*.bat文件: 248 | 249 | ```shell 250 | 251 | cd /Users/cundong/Documents/github/iZhihuPaper 252 | ant deploy 253 | pause 254 | 255 | ``` 256 | 257 | 调用这个批处理文件,即可进行批量打混淆后的渠道包。 258 | 259 | [1]: https://github.com/cundong/ZhihuPaper 260 | [2]: http://ant.apache.org 261 | [3]: http://ant-contrib.sourceforge.net/ 262 | [4]: https://github.com/cundong/ZhihuPaper/blob/master/local.properties 263 | [5]: https://github.com/cundong/ZhihuPaper/blob/master/ant.properties 264 | [6]: https://github.com/cundong/ZhihuPaper/blob/master/build.xml 265 | [7]: https://github.com/cundong/ZhihuPaper/blob/master/proguard-project.txt 266 | -------------------------------------------------------------------------------- /Android平台上常用的定时器选择.md: -------------------------------------------------------------------------------- 1 | # Android平台上常用的定时器选择 2 | 3 | Android平台定时器有两个: 4 | 5 | >* Java原生的Timer 6 | >* Android新引入的AlarmManager 7 | 8 | 9 | ##Java原生的Timer 10 | 11 | ```java 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_main); 17 | 18 | Timer timer = new Timer(); 19 | timer.schedule(new MyTimerTask(), 2000); 20 | } 21 | 22 | static class MyTimerTask extends TimerTask { 23 | 24 | @Override 25 | public void run() { 26 | 27 | /* */ 28 | } 29 | } 30 | ``` 31 | 32 | Timer实际上就是装装了一个Thread、一个TimerTask队列,这个TimerTask队列按照一定的方式排队执行。 33 | 34 | 但是,如果此时CUP进入了休眠状态,这个Thread就会因为失去了CPU而阻塞,导致我们的定时任务失效。 35 | 36 | 比如:我们设置一个任务5分钟后执行,可手机不到一分钟就锁屏进入休眠了,这时候我们这个任务就会执行失败。 37 | 38 | ##AlarmManager 39 | AlarmManager是Android用来管理时钟的类,可以在手机休眠的时候正常运行,到达预设时间时,唤醒CPU来执行任务。 40 | 41 | 所以,如果我们用 AlarmManager 来定时执行任务,CPU 可以正常的休眠,只有在需要运行任务时醒来一段很短的时间。 42 | 43 | ##如何选择 44 | 45 | 我们需要根据当前场景来选择使用哪一个。 46 | 47 | 短时间的任务,可以通过Timer来实现,比如延时几百毫秒,几秒后执行某个任务。 48 | 49 | 对于长时间的定时任务,考虑到手机休眠导致的任务失效,改用AlarmManager来实现。例如,现在很多App的推送功能,实际上都是客户端定时发起查询,使用AlarmManager来每隔一小时、两小时执行一次的任务。 50 | 51 | -------------------------------------------------------------------------------- /Android应用正确使用扩展SD卡.md: -------------------------------------------------------------------------------- 1 | # Android应用正确使用扩展SD卡 2 | 3 | ------ 4 | 5 | ## 先介绍一下Android的存储 6 | 7 | 在 2.x 版本中,Android设备都是单存储,第三方App写文件,必须申请 WRITE_EXTERNAL_STORAGE 权限; 8 | 9 | 在4.0之后,Android设备开始有了内置闪存,即 primary storage,并且可以外置SD卡,即 secondary external storage device; 10 | 11 | WRITE_EXTERNAL_STORAGE 权限变成了仅仅控制 primary storage,同时引入了 WRITE_MEDIA_STORAGE 权限来控制secondary external storage device的操作。 12 | 13 | 到了Android 4.4 KitKat,WRITE_MEDIA_STORAGE 权限仅提供给系统应用,不再授予第三方App。 14 | 15 | 关于 secondary external storage device 的写操作也有了新规定。 16 | 17 | Android的文档是这么写的: 18 | 19 | Link: [http://source.android.com/devices/tech/storage/index.html:][1] 20 | 21 | > The WRITE_EXTERNAL_STORAGE permission must only grant write access to 22 | > the primary external storage on a device. Apps must not be allowed to 23 | > write to secondary external storage devices, except in their 24 | > package-specific directories as allowed by synthesized permissions. 25 | > Restricting writes in this way ensures the system can clean up files 26 | > when applications are uninstalled. 27 | 28 | 翻译: 29 | WRITE_EXTERNAL_STORAGE 权限,仅仅用于授权用户写 primary external storage,除了与自己包名相关的文件夹之外,应用程序不允许写secondary external storage devices。 30 | 31 | 举例来说,如果应用的包名是com.example.foo,那么外部存储上的Android/data/com.example.foo/文件夹就可随意访问,其他任何地方都不允许写,并且,存储在自己包名相关的文件夹的文件,当该应用被卸载时候也会随之被清除。 32 | 33 | 分情况来说: 34 | 35 | > * 只有外部存储的设备 36 | 这种设备一般是android4.0之前的,只有一个存储,不受这个规则限制,还是可以随便读写,但如果你刷了4.4系统,那么就只能写自己包名相关的文件夹了。 37 | 38 | > * 只有内部存储的设备 39 | 比如Nexus系列,sony L系列,不受这个规则限制,但是建议在自己的包名相关的文件夹写数据。 40 | 41 | > * 既有内部存储又有外部存储 42 | 需要遵守这个规定,不能在外部存储乱写了,需要在自己的包名相关的文件夹写数据。 43 | 44 | Google做了这个限制后解决了这个问题: 45 | 46 | 随便一个App,都会在/sdcard、/sdcard1 上建一个目录,删了也会重新建,即使被卸载,也会留下一些垃圾文件。 47 | 48 | 但是,也产生了一个问题: 49 | 50 | 类似于视频、图像处理这种想在外部存储缓存大量音视频文件,并且App被卸载后还想保留的,就没办法了。 51 | 52 | ## 开发中应该怎么使用? 53 | 54 | 作为一个程序员,想必你也很讨厌App在SD卡根目录乱建目录吧,那就从我做起,来遵守Google的这一规定吧。 55 | 56 | 通过Context.getExternalFilesDir()方法可以获取到 SDCard/Android/data/{package_name}/files/ ,储存一些长时间保存的数据; 57 | 58 | 通过Context.getExternalCacheDir()方法可以获取到 SDCard/Android/data/{package_name}/cache/,储存临时缓存数据; 59 | 60 | 这两个目录分别对应 设置->应用->应用详情里面的”清除数据“与”清除缓存“选项。 61 | 62 | 一个获取外部存储Cache的例子: 63 | 64 | > /** 65 | > * 获取拓展存储Cache的绝对路径 66 | > * 67 | > * @param context 68 | > */ 69 | > public static String getExternalCacheDir(Context context) { 70 | > 71 | > if (!isMounted()) 72 | > return null; 73 | > 74 | > StringBuilder sb = new StringBuilder(); 75 | > 76 | > File file = context.getExternalCacheDir(); 77 | > 78 | > // In some case, even the sd card is mounted, 79 | > // getExternalCacheDir will return null 80 | > // may be it is nearly full. 81 | > 82 | > if (file != null) { 83 | > sb.append(file.getAbsolutePath()).append(File.separator); 84 | > } else { 85 | > sb.append(Environment.getExternalStorageDirectory().getPath()).append("/Android/data/").append(context.getPackageName()) 86 | > .append("/cache/").append(File.separator).toString(); 87 | > } 88 | > 89 | > return sb.toString(); 90 | > } 91 | 92 | 93 | 参考:
94 | [Google Plus](https://plus.google.com/+TodLiebeck/posts/gjnmuaDM8sn)
95 | [Android doc](http://source.android.com/devices/tech/storage/index.html) 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundong/blog/94c3b598141210a428796a8cc4078b4d66cbe619/README.md -------------------------------------------------------------------------------- /为RecyclerView增加Header和Footer.MD: -------------------------------------------------------------------------------- 1 | # 为RecyclerView增加Header和Footer 2 | 3 | 最近在开发新产品,为了跟上Google的脚步,项目中使用到ListView的地方都换成RecyclerView了。 4 | 5 | 换完之后,遇到最紧急的一个问题就是:ListView是自带addHeaderView()、addFooterView()的,RecyclerView不知出于什么原因的考虑,没有自带这两个方法。 6 | 7 | 我们只能自己想办法去自己加上去。 8 | 9 | ## 把Header、Footer当做RecyclerView的一个ViewType 10 | 11 | 比较通用的做法是把Header、Footer当做RecyclerView的一个ViewType来处理,诸如: 12 | 13 | ```java 14 | @Override 15 | public int getItemViewType(int position) { 16 | if (isHeader(position)) { 17 | return TYPE_HEADER_VIEW; 18 | } 19 | 20 | if (isFooter(position)) { 21 | return TYPE_FOOTER_VIEW; 22 | } 23 | 24 | int thePosition = position - (hasHeader() ? 1 : 0); 25 | return getItemViewType2(thePosition) + TYPE_DATA_OFFSET; 26 | } 27 | ``` 28 | 29 | 原理就是,向原先的dataList中插入两个元素,一个头一个尾,然后用户去定义这个头尾的布局,这样做的坏处是会改变元素的真实位置。当你在dataList中插入了一个Header的时,相当于整个dataList中元素都后移了,因此我们想要获取dataList中的元素,必须要重新计算一下: 30 | 31 | int thePosition = position - (hasHeader() ? 1 : 0); 32 | 33 | 为了做的通用一点,需要自己封装一个抽象类出来,继承 RecyclerView.Adapter,并且实现几个主要的方法: 34 | onBindViewHolder()、onCreateViewHolder()、getItemCount()等。 35 | ```java 36 | @Override 37 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 38 | 39 | if (viewType == TYPE_HEADER_VIEW) { 40 | return mHeaderViewHolder; 41 | } else if (viewType == TYPE_FOOTER_VIEW) { 42 | return mFooterViewHolder; 43 | } 44 | 45 | return onCreateViewHolder2(parent, viewType - TYPE_DATA_OFFSET); 46 | } 47 | 48 | @Override 49 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 50 | 51 | int thePosition = position - (hasHeader() ? 1 : 0); 52 | 53 | if (!isHeader(position) && !isFooter(position)) { 54 | 55 | holder.itemView.setTag(mDataList.get(thePosition)); 56 | 57 | onBindViewHolder2((VH) holder, thePosition); 58 | } else { 59 | ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams(); 60 | if(layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) { 61 | ((StaggeredGridLayoutManager.LayoutParams) layoutParams).setFullSpan(true); 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * 用于替代RecyclerView.Adapter.onCreateViewHolder()方法 68 | * 69 | * @param parent 70 | * @param viewType 71 | * @return 72 | */ 73 | public abstract VH onCreateViewHolder2(ViewGroup parent, int viewType); 74 | 75 | /** 76 | * 用于替代RecyclerView.Adapter.onBindViewHolder()方法 77 | * 78 | * @param holder 79 | * @param position 80 | */ 81 | public abstract void onBindViewHolder2(VH holder, int position); 82 | ``` 83 | 我们在自己的业务Adapter中,需要实现onCreateViewHolder2()这个抽象方法,并且用它来代替原生的onCreateViewHolder()方法。 84 | 85 | ## 自己写一个OuterAdapter,包裹真实的业务逻辑Adapter 86 | 87 | 另一种方法是自己写一个OuterAdapter,继承自RecyclerView.Adapter,把自己写的innerAdapter传进来,我们在OuterAdapter中插入头和尾,这样就不会影响用户自己写的innerAdapter中数据位置了。 88 | 89 | 并且,在OuterAdapter中重新为innerAdapter设置监听器,当数据变化时,计算插入Header、Footer之后的新位置,调用notifyXXXX系列方法来通知数据的改变。 90 | 91 | ```java 92 | /** 93 | * 设置adapter 94 | * @param adapter 95 | */ 96 | public void setAdapter(RecyclerView.Adapter adapter) { 97 | 98 | if (adapter != null) { 99 | if (!(adapter instanceof RecyclerView.Adapter)) 100 | throw new IllegalArgumentException("your adapter must be a RecyclerView.Adapter"); 101 | } 102 | 103 | if (mInnerAdapter != null) { 104 | notifyItemRangeRemoved(getHeaderViewsCount(), mInnerAdapter.getItemCount()); 105 | mInnerAdapter.unregisterAdapterDataObserver(mDataObserver); 106 | } 107 | 108 | this.mInnerAdapter = adapter; 109 | mInnerAdapter.registerAdapterDataObserver(mDataObserver); 110 | notifyItemRangeInserted(getHeaderViewsCount(), mInnerAdapter.getItemCount()); 111 | } 112 | 113 | private RecyclerView.AdapterDataObserver mDataObserver = new RecyclerView.AdapterDataObserver() { 114 | 115 | @Override 116 | public void onChanged() { 117 | super.onChanged(); 118 | notifyDataSetChanged(); 119 | } 120 | 121 | @Override 122 | public void onItemRangeChanged(int positionStart, int itemCount) { 123 | super.onItemRangeChanged(positionStart, itemCount); 124 | notifyItemRangeChanged(positionStart + getHeaderViewsCount(), itemCount); 125 | } 126 | 127 | @Override 128 | public void onItemRangeInserted(int positionStart, int itemCount) { 129 | super.onItemRangeInserted(positionStart, itemCount); 130 | notifyItemRangeInserted(positionStart + getHeaderViewsCount(), itemCount); 131 | } 132 | 133 | @Override 134 | public void onItemRangeRemoved(int positionStart, int itemCount) { 135 | super.onItemRangeRemoved(positionStart, itemCount); 136 | notifyItemRangeRemoved(positionStart + getHeaderViewsCount(), itemCount); 137 | } 138 | 139 | @Override 140 | public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 141 | super.onItemRangeMoved(fromPosition, toPosition, itemCount); 142 | int headerViewsCountCount = getHeaderViewsCount(); 143 | notifyItemRangeChanged(fromPosition + headerViewsCountCount, toPosition + headerViewsCountCount + itemCount); 144 | } 145 | }; 146 | ``` 147 | 148 | ## 两层布局嵌套 149 | 150 | 还有一种比较巧妙的方式,如果你只需要为RecyclerView增加一个Header,不需要Footer,可以使用。 151 | 152 | ### 设置第一个元素的top padding 153 | 通过RecyclerView.ItemDecoration,将RecyclerView第一个item的top值设置的很大(Header多高就设置多大),然后在一个FrameLayout中放置RecyclerView,再把Header盖上去,这样给用户的视觉感受就是RecyclerView有了一个Header 154 | 155 | ### 滑动的时候联动 156 | 然后,让Header随着RecyclerView的滑动而滑动,并且在滑动Header的时候,带动RecyclerView的滑动 157 | 158 | ### 一些限制 159 | 如果你增加的Header中有能消费事件的控件,比如Button,EditText,则会中断事件的传递,导致滑动的时候Header和RecyclerView不联动,因此如果你想增加一个简单的HeaderView(比如只有简单的TextView展示基本信息之类),可以使用这种方式,否则,请使用以上两种方式。 160 | 161 | 我使用的是第2种方式,最简单。 162 | 163 | ##代码 164 | 代码整理完毕后会push到GitHub上。 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /关于Kindle.md: -------------------------------------------------------------------------------- 1 | # 关于Kindle,我所知道的 2 | 3 | ------ 4 | 5 | 大约在两年前,Kindle5(为了书写方便,以下用它的中文译名「啃刀」代替)刚刚发布的时候,我从淘宝代购了一个,从此就开始了在啃刀(这名字实在是凶残了点,我还是换回Kindle吧,下同)上的阅读生涯,刚才登录了一下amazon的 Content and Devices 页面,发现大约在Kindle上阅读了60本左右的书,外加从网页上推过来的文章若干。 6 | 7 | 在此期间, Kindle 升级了两次,从Kindle5到 Kindle Paper White,再到现在的Kindle Paper White2,从按键变成了触屏,从无背光变成了有背光,从只能欧美日本代购到合法入华。。共有9个小伙伴受我影响,通过我代购的那家店,或是amazon官网购买了Kindle Paper White/Kindle Paper White2,真替他们感到高兴,也替这家店感到高兴。 8 | 9 | 在这里,我把小伙伴经常问我的一些问题写下来,希望能帮助到刚刚购买的朋友。 10 | 11 | ## 到哪里去发现图书? 12 | 13 | > readfree:[GO][1] 14 | > readfar: [GO][2] 15 | > douban小站: [GO][3] 16 | 17 | ## 如何把图书装进去? 18 | 19 | 1.直接用数据线拷进去; 20 | 2.可以使用Amazon的官方PC应用「Send to Kindle」,把电脑里的文件,推送到kindle; 21 | 22 | ## 如何把网页上的文章装进去? 23 | 1.可以通过 Chrome 浏览器的插件 「Send to Kindle」 将网页推送到 Kindle,格式和排版基本上不影响阅读。 24 | 2.也可以用 Chrome 浏览器的 「印象笔记-剪藏」 插件,将文字保存到印象笔记,整理好后导出为 HTML文件,再使用 Calibre 做成电子书推送到 Kindle 。 25 | 26 | ## 需要刷多看吗? 27 | 我觉得原生系统已经很优秀了,完全可以满足我对阅读的需要,懒得折腾,就没有刷,并且,最近Kindle商城入华之后,多看的所谓正版书源也没有太大优势了,除非你想从多看书城购买图书。 28 | 29 | ## 为什么要买Kindle? 30 | 也许你要说,我用手机装个「多看」不一样能看书吗?为什么非要花钱单买一个设备呢。 31 | 答案很简单: 32 | 1.Kindle提供了无异于纸书的体验,屏幕不刺眼,并且省电,不开WiFi的情况,充一次电坚持半个月没问题,我上次把Kindle忘在一个朋友家,快一个月后去拿,电量基本没变,书还是翻到原来那一页,待机时长比诺基亚还逆天。 33 | 34 | 2.仪(zhuang)式(bi)感,也就是江湖上说的「逼格」,正如古人读书之前要焚香沐手一般。 35 | 36 | 3.它只能用于看书,假设你用手机上的App看书,恰好你又和我一样,意志力不是那么的坚定,很容易一会看看看微信一会刷刷微博,而使用Kindle就避免了这一点,没有任何打扰,让你完全沉浸其中。 37 | 38 | 4.能看到很多因种种原因未在国内出版的书,比如敏感词写的《 敏感词回忆录 》、《敏感词与敏感词》等等(此处不能说的太细,你懂的)。 39 | 40 | 5.Kindle几乎是在所有电子产品中最保值的产品。我两年前购买的Kindle5,550块,现在买一个全新的Kindle5,还需要480元,两年时间才降价70块,这是多么的不可思议。想想两年前的苹果or三星的电子产品,贬值至少一半起了吧。这是因为手机会受摩尔定律影响,每18个月硬件就更新换代一次,硬件的升级带动了软件的升级,你那一个三年前的设备根本无法承载当前的操作系统和常用软件,作为一个阅读设备,Kindle的更新则不受其影响,不会因为是三年前的硬件就看不了今年的书。 41 | 42 | 6.便携,它只有七寸,薄且轻便,实在是公交地铁,火车大客,卧室沙发之必备神器。 43 | 44 | 最后,它唯一的缺点:Elink屏易碎,不过这个缺点对我们中国人来说也真的是——没什么,我们是那么喜欢给数码产品加个外壳。 45 | 46 | 47 | [1]: http://readfree.me 48 | [2]: http://readfar.com 49 | [3]: http://site.douban.com/150757/room/1603912 50 | -------------------------------------------------------------------------------- /引用 android-support-v4.jar 时候冲突问题解决.md: -------------------------------------------------------------------------------- 1 | # 引用 android-support-v4.jar 时候冲突问题解决 2 | 3 | ------ 4 | 5 | 在开发应用的时候,难以避免的会用到很多第三方的开源项目,这些项目中都会使用android-support-v4.jar包,而我的项目也使用它。 6 | 7 | 再加上这些开源项目之间还存在各种复杂的引用关系。 8 | 9 | 就这样引用来、引用去,就可能会出现android-support-v4.jar的冲突问题,类似于: 10 | 11 | > Jar mismatch! Fix your dependencies 12 | 13 | > Found 2 versions of android-support-v4.jar in the dependency list,but not all the versions are identical 14 | 15 | 我的解决方法是: 16 | 17 | 1. 18 | 新建一个空的工程,里面仅仅在libs/下放置一个版本正确的android-support-v4.jar包。 19 | 20 | 2. 21 | 删除你的项目,以及你引用项目中的所有android-support-v4.jar。 22 | 23 | 3. 24 | 为每一个需要用到android-support-v4.jar的工程,增加对步骤1中创建的空工程的依赖。 25 | Properties > Android> Library box > Add 26 | 27 | 这样以来,可以保证整个工程中用到的v4.jar是单例的。 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /转:Android系统源码目录 system-framework 下各个jar包的用途.md: -------------------------------------------------------------------------------- 1 | 转载: 2 | http://my.oschina.net/DragonWang/blog/62082 3 | 4 | Android系统源码目录 system/framework 下各个jar包的用途 5 | 6 | - am.jar:终端下执行am命令时所需的java库。源码目录:framework/base/cmds/am 7 | - android.policy.jar:锁屏界面需要用到的jar包,该包引用了android.test.runner.jar,源码目录:framework/base/policy 8 | - android.test.runner.jar:测试应用所需的jar包,该包引用了core.jar,core-junit.ajr以及framework.jar,源码目录:framework/base/test-runner 9 | - bmgr.jar:adb shell命令下对Android Device所有package备份和恢复的操作时所需的java库。官方文档:http://developer.android.com/guide/developing/tools/bmgr.html。不过这个android服务默认是Disabled,而且要backup的应用必须实现BackupAgent,在AndroidManifest.xml的application标签中加入android:backupAgent属性。源码目录:framework/base/cmds/bmgr 10 | - bouncycastle.jar: java三方的密匙库,网上资料说用来apk签名、https链接之类,官网 :http://www.bouncycastle.org/java.html 11 | - com.android.future.usb.accessory.jar:用于管理USB的上层java库,在系统编译时hardware层会调用到。源码目录:frameworks/base/libs/usb 12 | - com.android.location.provider.jar: 13 | - com.android.nfc_extras.jar:NFC外部库。android/nfc/NfcAdapter.java会调用到包中的NfcAdapterExtras.java。源码目录:frameworks/base/nfc-extras 14 | - core-junit.jar :junit核心库,在运行*Test.apk时被调用。 15 | - core-junitrunner.jar:未知,公司话机上有。 16 | - core-tests*.jar:framework下的一系列测试jar包,不做测试时可删除。 17 | - core.jar:核心库,启动桌面时首先加载这个。源码目录: 18 | - ext.jar:android外部三方扩展包,源码主要是external/nist-sip(java下的sip三方库)、external/apache-http(apache的java三方库)、external/tagsoup(符合SAX标准的HTML解析器)。其实这个jar包可以添加外部扩展jar包,只需在framework/base/Android.mk中的ext-dir添加src目录即可。 19 | - framework-res.apk:android系统资源库。 20 | - framework.jar:android的sdk中核心代码。 21 | - ime.jar:ime命令所需jar包,用于查看当前话机输入法列表、设置输入法。源码目录:framework/base/cmds/ime 22 | - input.jar:input命令所需的jar包,用于模拟按键输入。源码目录:framework/baes/cmds/input 23 | - javax.obex.jar:java蓝牙API,用于对象交换协议。源码目录:framework/base/obex 24 | - monkey.jar:执行monkey命令所需jar包。源码目录:framework/base/cmds/monkey 25 | - pm.jar:执行pm命令所需的jar包,pm详情见adb shell pm,源码目录:framework/base/cmds/pm 26 | - services.jar:话机框架层服务端的编译后jar包,配合libandroid_servers.so在话机启动时通过SystemServer以循环闭合管理的方式将各个service添加到ServiceManager中。源码目录:framework/base/service 27 | - sqlite-jdbc.jar: sqlite的Java DataBase Connextivity jar包。 28 | - svc.jar:svc命令所需jar包,可硬用来管理wifi,power和data。源码目录:framework/base/cmds/svc,详情见:http://madgoat.cn/2011/02/android_svc/ -------------------------------------------------------------------------------- /通过Android源代码分析startActivity()过程(上).md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundong/blog/94c3b598141210a428796a8cc4078b4d66cbe619/通过Android源代码分析startActivity()过程(上).md -------------------------------------------------------------------------------- /通过Android源代码分析startActivity()过程(下).MD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundong/blog/94c3b598141210a428796a8cc4078b4d66cbe619/通过Android源代码分析startActivity()过程(下).MD -------------------------------------------------------------------------------- /通过浏览器直接打开Android应用程序.md: -------------------------------------------------------------------------------- 1 | # 通过浏览器直接打开Android应用程序 2 | ------ 3 | 4 | 之前写过[一篇blog][1],介绍如何通过点击浏览器中的链接,直接打开本地Android App。 5 | 6 | 实现方式不太完美,最近看了微博、京东的手机版网页,感觉他们的实现方式很不错,研究了一下,实现以下效果: 7 | 8 | 如果本地已经安装了指定Android应用,就直接打开它;如果没有安装,则直接下载该应用的安装文件(也可以跳转到下载页面)。 9 | 10 | ## 实现方式 11 | 12 | 1.为Android应用的启动Activity设置一个Schema,如下: 13 | 14 | ```xml 15 | 16 | ``` 17 | 18 | 2.用户点击浏览器中的链接时,在动态创建一个不可见的iframe,并且让这个iframe去加载步骤1中的Schema,如下: 19 | 20 | ```javascript 21 | var ifr = document.createElement('iframe'); 22 | ifr.src="cundong://splash" 23 | ``` 24 | 25 | 3,如果在指定的时间内没有跳转成功,则当前页跳转到apk的下载地址(或下载页),如下: 26 | 27 | ```javascript 28 | window.location = download_url; 29 | ``` 30 | 31 | ## HTML代码 32 | 33 | ```html 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | this's a demo 43 | 44 | 45 | 46 |
47 | 立即打开>> 48 | 49 |
50 | 51 | 93 | 94 | 95 | ``` 96 | 97 | ## AndroidMainfext.xml 98 | 99 | ``` xml 100 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | ``` 117 | 118 | [1]: http://my.oschina.net/liucundong/blog/168612 119 | -------------------------------------------------------------------------------- /通过源码分析Android 的消息处理机制.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundong/blog/94c3b598141210a428796a8cc4078b4d66cbe619/通过源码分析Android 的消息处理机制.md --------------------------------------------------------------------------------