-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 115 KB
/
content.json
1
{"meta":{"title":"caiyonglong'blog","subtitle":"Talk is cheap. Show me the code.","description":"这就是一个简单的描述","author":"caiyonglong","url":"https://blog.musiclake.cn"},"pages":[{"title":"","date":"2022-05-05T09:56:06.612Z","updated":"2022-05-05T09:56:06.612Z","comments":true,"path":"resume/index.html","permalink":"https://blog.musiclake.cn/resume/index.html","excerpt":"","text":""},{"title":"404 Not Found:该页无法显示","date":"2022-05-05T09:56:06.441Z","updated":"2022-05-05T09:56:06.441Z","comments":false,"path":"/404.html","permalink":"https://blog.musiclake.cn/404.html","excerpt":"","text":".article-title { font-size: 2.1em; } strong a { color: #747474; } .share { display: none; } .player { margin-left: -10px; } .sign { text-align: right; font-style: italic; } #page-visit { display: none; } .center { text-align: center; height: 2.5em; font-weight: bold; } .search2 { height: 2.2em; font-size: 1em; width: 50%; margin: auto 24%; color: #727272; opacity: .6; border: 2px solid lightgray; } .search2:hover { opacity: 1; box-shadow: 0 0 10px rgba(0, 0, 0, 0.3) }; .article-entry hr { margin: 0; } .pic { text-align: center; margin: 0; } .pic br { display: none; } 很抱歉,您所访问的地址并不存在: 回主页 · 所有文章 · 留言板 可在边栏搜索框中对本站进行检索,以获取相关信息。 以下是博主喜欢的一些歌曲,可以听听,稍作休息~"},{"title":"关于我","date":"2021-03-28T10:10:26.000Z","updated":"2022-05-05T09:56:06.453Z","comments":true,"path":"about/index.html","permalink":"https://blog.musiclake.cn/about/index.html","excerpt":"","text":"关于我94年,Android 开发工程师,现在深圳工作,工作四年 关于本站主要用于记录日常学习,随笔,欢迎与我交流,共同学习进步。 博客16年基于Hexo搭建,平时基本都是写代码,很少总结,写博客。工作几年后才发现,除了项目代码,什么也没有留下。 后面发现这个博客很久没有维护更新,基本都废了,所以想重新维护起来,记录一下当前的学习,也回顾以前的项目,为博客添加一些内容。 联系方式邮箱:caiyonglong@live.comQQ:643872807 主要项目有 音乐湖-MusicLake:开源项目,用于学习研究,欢迎issue、PR。 暴走P图:已在各应用商店上线,如需体验,可前往应用商店搜索下载,欢迎提意见。"},{"title":"categories","date":"2016-06-04T17:24:39.000Z","updated":"2022-05-05T09:56:06.453Z","comments":false,"path":"categories/index.html","permalink":"https://blog.musiclake.cn/categories/index.html","excerpt":"","text":""},{"title":"tags","date":"2018-04-23T16:39:22.000Z","updated":"2022-05-05T09:56:06.613Z","comments":false,"path":"tags/index.html","permalink":"https://blog.musiclake.cn/tags/index.html","excerpt":"","text":""},{"title":"相册","slug":"instagram","date":"2022-05-05T09:56:06.612Z","updated":"2022-05-05T09:56:06.612Z","comments":true,"path":"instagram/index.html","permalink":"https://blog.musiclake.cn/instagram/index.html","excerpt":"","text":"图片来自instagram,正在加载中…"},{"title":"读书","date":"2022-05-05T09:56:06.612Z","updated":"2022-05-05T09:56:06.612Z","comments":true,"path":"reading/index.html","permalink":"https://blog.musiclake.cn/reading/index.html","excerpt":"","text":""}],"posts":[{"title":"","slug":"uml/UML介绍","date":"2022-05-05T09:56:06.448Z","updated":"2022-05-05T09:56:06.448Z","comments":true,"path":"posts/0.html","link":"","permalink":"https://blog.musiclake.cn/posts/0.html","excerpt":"","text":"","categories":[],"tags":[],"keywords":[]},{"title":"ReactNative源码分析","slug":"源码分析/ReactNative源码分析","date":"2021-05-06T10:15:00.000Z","updated":"2022-05-05T09:56:06.450Z","comments":true,"path":"posts/aa74f4e0.html","link":"","permalink":"https://blog.musiclake.cn/posts/aa74f4e0.html","excerpt":"","text":"前言最近一个月都在看RN的问题,因为线上RN包出现一个偶现的崩溃问题,在react-native github上也没人回复。搞得头都大了,每天都是查埋点,抓日志。想找出一个复现的具体步骤,但是根据埋点和日志信息都没能复现定位到线上崩溃问题。所以想通过源码集成的方式,能在RN Android源码层面加一些日志来辅助定位。随便看看RN源码是怎么实现JS和Android之间的交互和通信的 ReactNative源码集成1、下载React-Native源代码 git clone https://github.com/facebook/react-native git tag v0.64.0 切换到对应版本,react-native是通过git tag来区分版本的。所以代码拉取完成后需要通过git tag来切换到对应的版本上。本文是基于0.64版本 2、npm安装环境 在react-native根目录下,通过npm安装依赖 npm install 3、通过Android Studio打开react-native/ReactAndroid。编译即可 react-native官网上具体的集成方式:从源代码编译React Native React-Native源码分析主要分析ReactNative的启动流程、Android端的渲染原理、通信机制等方面 ReactNative的启动流程 渲染原理 通信机制","categories":[],"tags":[{"name":"ReactNative","slug":"ReactNative","permalink":"https://blog.musiclake.cn/tags/ReactNative/"}],"keywords":[]},{"title":"自动化打包","slug":"自动化打包/jenkins","date":"2021-03-31T15:35:00.000Z","updated":"2022-05-05T09:56:06.450Z","comments":true,"path":"posts/aa1fa5ba.html","link":"","permalink":"https://blog.musiclake.cn/posts/aa1fa5ba.html","excerpt":"","text":"前言在日常开发过程中,编译打包是必不可少的流程,特别是开发转测试阶段。","categories":[],"tags":[],"keywords":[]},{"title":"LeakCanary 集成和原理分析","slug":"LeakCanary/LeakCanary","date":"2021-03-30T14:35:00.000Z","updated":"2022-05-05T09:56:06.445Z","comments":true,"path":"posts/LeakCanary.html","link":"","permalink":"https://blog.musiclake.cn/posts/LeakCanary.html","excerpt":"","text":"最近看到项目中用到的LeakCanary版本还是1.6.3版本,所以想升级一下。看到LeakCanary的官方文档,最新版已经是2.7了,所以想记录一下以及分析一下新版的原理 介绍LeakCanary是适用于Android的内存泄漏检测库。 官方解释: LeakCanary’s knowledge of the internals of the Android Framework gives it a unique ability to narrow down the cause of each leak, helping developers dramatically reduce OutOfMemoryError crashes. LeakCanary可以帮忙定位内存泄漏问题,从而帮助开发人员大大减少OutOfMemoryError崩溃。 集成到项目中后,一般是测试包和开发包会用到,生产包不需要依赖。 集成在app/build.gradle 添加一下依赖 dependencies { // debugImplementation because LeakCanary should only run in debug builds. debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' } 可以在 Logcat 里面通过过滤搜索 LeakCanary,来判断LeakCanary是否运行 LeakCanary: LeakCanary is running and ready to detect leaks 1.6.版本和2.0版本差别对比对比老版本1.6.3和新版本 2.7。主要的更新点如下(官方文档比较多,这里只写一下主要的点): 集成更加方便,原来需要一次集成多个依赖库,新版只需要集成一个依赖 内存泄漏支持分组,相同的内存泄漏会自动分为一类,方便查看 使用Kotlin重写 新的堆解析器,比旧版减少了90%内存,速度快了6倍。个人体验确实比1.6.3版本快了很多 原理分析未完待续…","categories":[{"name":"内存优化","slug":"内存优化","permalink":"https://blog.musiclake.cn/categories/%E5%86%85%E5%AD%98%E4%BC%98%E5%8C%96/"}],"tags":[],"keywords":[{"name":"内存优化","slug":"内存优化","permalink":"https://blog.musiclake.cn/categories/%E5%86%85%E5%AD%98%E4%BC%98%E5%8C%96/"}]},{"title":"音乐湖环境部署","slug":"project/musiclake_build","date":"2021-03-29T10:21:16.000Z","updated":"2022-05-05T09:56:06.447Z","comments":true,"path":"posts/musicLakeBuild.html","link":"","permalink":"https://blog.musiclake.cn/posts/musicLakeBuild.html","excerpt":"","text":"音乐湖环境部署音乐湖项目主要包含了Android端,PC端,微信小程序端,歌单服务器等几个项目,具体项目地址如下: Android客户端 MusicLakePC端 music云歌单服务器 play-be音乐解析 Api 前言因为涉及音乐版权问题,被官方警告了。所以我们就不再提供可用的应用以及接口API了,目前主要偏向于学习研究。在群里也有人反馈看不懂README,部署比较困难,看到别人的部署博客,非常详细,非常好,所以作为开发者的我们也应该写一篇来记录记录。如果只是想用单纯的使用音乐湖软件听歌,不建议搭建这一套环境体系。 项目介绍本项目采用CS架构,客户端-服务器模式,搭建部署整个音乐播放服务。 客户端:MusicLake、PC客户端服务器:play-be(登录、云歌单)、NeteaseMusicApi(网易云音乐api) 云歌单服务器部署云歌单服务器 play-be 开发cp config/default.js config/local.js修改config/local.js相应配置npm installnpm run start 部署 cp config/default.js config/local.js 修改config/local.js相应配置 以下三种方式任选一docker-compose up -d # Docker Compose(Recommended) pm2 start pm2.production.json # PM2, daemon run npm run start # Just run it PC客户端部署如果想直接使用体验,github 支持workflow部署,release下有打包好的生成包,可以直接选择对应的客户端下载安装即可。 也可以自己打包 打包环境要求 nodejs v12.1.0 版本及以上 yarn 打包yarn run build 更多请见 github Android客户端部署未完待续… Github登录服务配置未完待续… QQ登录配置这个需要备案的域名,才能使用。建议推荐Github登录配置","categories":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}],"tags":[{"name":"MusicLake","slug":"MusicLake","permalink":"https://blog.musiclake.cn/tags/MusicLake/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}]},{"title":"音乐湖(MusicLake)项目介绍","slug":"project/musiclake_introduce","date":"2021-03-29T10:21:16.000Z","updated":"2022-05-05T09:56:06.448Z","comments":true,"path":"posts/musicLake.html","link":"","permalink":"https://blog.musiclake.cn/posts/musicLake.html","excerpt":"","text":"MusicLake MediaPlayer、ExoPlayer音乐库封装 编译&服务器部署 build.md 功能版本 Android 音乐播放器(本地/在线播放) (最低支持Android版本5.0) 支持多平台音乐源,百度音乐,虾米音乐、网易云音乐、Youtube(需要翻墙) 歌词播放、桌面歌词、桌面小控件 通知栏控制、线控播放、音频焦点控制 QQ登录、微博登陆、在线歌单同步 网易云热门歌手,百度电台列表,网易云mv排行榜,mv播放评论 酷狗歌词搜索、修改歌词样式、歌词翻译 PC端 开源不易,有兴趣可给个star,支持一下哦! 群聊已经解散,有什么问题可以提issues!,有想法可以提PR。感谢关注🙏 相关项目|音乐API 客户端音乐API 客户端/PC音乐共用API 后台/云歌单API 三方/NeteaseCloudMusicApi 第三方库 rxjava retrofit dagger2 Glide LitePal DSBridge BaseRecyclerViewAdapterHelper More.. 截图 主要功能截图 夜间模式相关截图","categories":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}],"tags":[{"name":"MusicLake","slug":"MusicLake","permalink":"https://blog.musiclake.cn/tags/MusicLake/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}]},{"title":"常用设计模式","slug":"设计模式/designModel","date":"2021-03-25T13:55:03.000Z","updated":"2022-05-05T09:56:06.451Z","comments":true,"path":"posts/1e80ad29.html","link":"","permalink":"https://blog.musiclake.cn/posts/1e80ad29.html","excerpt":"","text":"为什么学习设计模式设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。 以上都是官方套话,在平时开发中,我们会遇到各种代码设计问题,比如抱怨别人写的代码可读性,可维护性很差,虽然最终都实现了功能,但是代码质量不高,随着新需求的不断调整,只能继续旧代码基础上写代码,最后越写越难受,复杂的代码,自己都看不懂。所以如果有个什么方式能够让我们写出可维护、可复用、可扩展及灵活的代码。 设计模式有什么作用有助于项目架构设计,提高项目代码的可读性和可维护性 设计模式六大原则 开闭原则 开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,需要使用接口和抽象类。 里式代换原则 里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。 依赖倒转原则 这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。 接口隔离原则 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。 迪米特法则,又称最少知道远着 最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。 合成复用原则 合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。 设计模式有多少种总共有23种设计模式,这些设计模式可分为三类,创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns) 创建型模式这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。 工厂模式 抽象工厂模式 建造者模式 单例模式 原型模式 结构型模式这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。 适配器模式 桥接模式 组合模式 装饰器模式 外观模式 享元模式 代理模式 行为型模式这些设计模式特别关注对象之间的通信。 责任链模式 迭代器模式 备忘录模式 命令模式 中介者模式 观察者模式 状态模式 策略模式","categories":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"keywords":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}]},{"title":"【RN】RN工程化架构-redux","slug":"react-native-test","date":"2020-09-08T01:57:17.000Z","updated":"2022-05-05T09:56:06.448Z","comments":true,"path":"posts/854d3cbf.html","link":"","permalink":"https://blog.musiclake.cn/posts/854d3cbf.html","excerpt":"","text":"前言对于RN官网上介绍的RN代码示例,都是一些单界面的示例,但是在实际项目开发过程中,这些远远不能满足负责的业务逻辑,跟不上项目迭代速度 Redux介绍Redux在React Native中的使用项目级架构","categories":[],"tags":[{"name":"RN","slug":"RN","permalink":"https://blog.musiclake.cn/tags/RN/"},{"name":"跨平台","slug":"跨平台","permalink":"https://blog.musiclake.cn/tags/%E8%B7%A8%E5%B9%B3%E5%8F%B0/"}],"keywords":[]},{"title":"Android事件分发机制","slug":"interview/事件分发机制","date":"2019-09-22T04:12:01.000Z","updated":"2022-05-05T09:56:06.447Z","comments":true,"path":"posts/7bbb2ea5.html","link":"","permalink":"https://blog.musiclake.cn/posts/7bbb2ea5.html","excerpt":"","text":"前言这是很常见的面试题,几乎每次面试都会问到,每次问的区别就是问的深度不一样。虽然网上有很多面经,很多总结,但是自己还是想花点时间来总结一下,加深印象。 常见面试问题 说一下Android事件分发的流程 分发、拦截方法和Touch_Down、Touch_Move、Touch_Up 的执行顺序 事件分发基本概念Activity -> ViewGroup ->View dispatchTouchEvent 分发 onInterceptTouchEvent 拦截 View 没有拦截事件","categories":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}],"tags":[{"name":"面试","slug":"面试","permalink":"https://blog.musiclake.cn/tags/%E9%9D%A2%E8%AF%95/"},{"name":"dasd","slug":"dasd","permalink":"https://blog.musiclake.cn/tags/dasd/"}],"keywords":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}]},{"title":"Activity的启动流程","slug":"源码分析/Activity的启动流程","date":"2019-09-21T16:00:00.000Z","updated":"2022-05-05T09:56:06.449Z","comments":true,"path":"posts/3f706ac0.html","link":"","permalink":"https://blog.musiclake.cn/posts/3f706ac0.html","excerpt":"","text":"前言Ativity的启动流程一般可以分为两部分 从桌面Launcher点击小图标,启动应打开主Activity 应用内startActivity启动其他Activity","categories":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}],"tags":[],"keywords":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}]},{"title":"常用第三方库源码分析——EventBus","slug":"源码分析/EventBus","date":"2019-09-21T16:00:00.000Z","updated":"2022-05-05T09:56:06.449Z","comments":true,"path":"posts/6ba9cd28.html","link":"","permalink":"https://blog.musiclake.cn/posts/6ba9cd28.html","excerpt":"","text":"前言EventBus是适用于Android和Java的发布/订阅事件总线。 1、定义事件2、准备订阅者,声明并注释订阅方法,也可以指定线程模式注册和注销订阅者3、发送事件 源码分析注册方法,Eventbus实例注册订阅者事件 public void register(Object subscriber) { //获取注册的对象的类型 Class<?> subscriberClass = subscriber.getClass(); //查找并获取注册的对象的订阅方法 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); //加锁,在同步锁中订阅方法 synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { //对订阅方法进行注册 subscribe(subscriber, subscriberMethod); } } } 查找订阅者的订阅方法,METHOD_CACHE方法缓存,使用ConcurrentHashMap存储 List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { //从缓存中获取订阅方法 List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } //如果缓存中没有,则是否强制使用反射还是使用索引获取订阅方法 if (ignoreGeneratedIndex) { subscriberMethods = findUsingReflection(subscriberClass); } else { subscriberMethods = findUsingInfo(subscriberClass); } if (subscriberMethods.isEmpty()) { throw new EventBusException("Subscriber " + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation"); } else { METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; } }","categories":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}],"tags":[{"name":"Eventbus","slug":"Eventbus","permalink":"https://blog.musiclake.cn/tags/Eventbus/"}],"keywords":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}]},{"title":"常用第三方库源码分析——Glide","slug":"源码分析/Glide源码分析","date":"2019-09-21T16:00:00.000Z","updated":"2022-05-05T09:56:06.449Z","comments":true,"path":"posts/a7a0829e.html","link":"","permalink":"https://blog.musiclake.cn/posts/a7a0829e.html","excerpt":"","text":"","categories":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}],"tags":[{"name":"Glide","slug":"Glide","permalink":"https://blog.musiclake.cn/tags/Glide/"}],"keywords":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}]},{"title":"Handler消息机制","slug":"源码分析/Hander消息机制","date":"2019-09-21T16:00:00.000Z","updated":"2022-05-05T09:56:06.449Z","comments":true,"path":"posts/c6dbe339.html","link":"","permalink":"https://blog.musiclake.cn/posts/c6dbe339.html","excerpt":"","text":"前言平时工作中缺乏总结,写博客不是为别人,而是为自己,虽然网上很多类似的,但是自己还是得写一遍加深印象 基本原理消息机制包含 Handler、MessageQueue、Message、Looper 拖欠图 Handler 发送消息,消息存入到消息队列中,然后Looper 循环执行消息队列。 问题Handler 为什么会造成内存泄漏因为在Activity中,handler内部类引用了它,而该handler实例可能被MessageQueue引用着。比如发送了一个延时消息到队列中,那么就可能在队列中存在很长时间,而消息队列(MessageQueue)的生命周期等于它所在的线程。当大到Activity被finish()了后还在队列中时,就满足了上面的短生命周期引用长生命周期的条件。根据Java GC的规则,Activity的引用计数不为0,故不会回收 解决办法 1、保证Activity被finish()时该线程的消息队列没有这个Activity的handler内部类的引用 2、要么让这个handler不持有Activity等外部组件实例,让该Handler成为静态内部类。(静态内部类是不持有外部类的实例的,因而也就调用不了外部的实例方法了) 3、在2方法的基础上,为了能调用外部的实例方法,传递一个外部的弱引用进来 为什么消息Handler能实现跨线程通信为什么Looper循环不会导致ANRANR的基本概念 1:5s内无法响应用户输入事件(例如键盘输入, 触摸屏幕等) 2:BroadcastReceiver在10s内无法结束 3:ServiceTimeout(20s) --小概率类型,Service在特定的时间内无法处理完成 Android是事件驱动的,在Loop.loop()中不断接收事件、处理事件,而Activity的生命周期都依靠于主线程的Loop.loop()来调度,所以可想而知它的存活周期和Activity也是一致的。当没有事件需要处理时,主线程就会阻塞;当子线程往消息队列发送消息,并且往管道文件写数据时,主线程就被唤醒。 主线程在没有事件需要处理的时候就是处于阻塞的状态。想让主线程活动起来一般有两种方式: 第一种是系统唤醒主线程,并且将点击事件传递给主线程; 第二种是其他线程使用Handler向MessageQueue中存放了一条消息,导致loop被唤醒继续执行。 主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。 总结: Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。 主线程Looper在哪里初始化 ActivityThread 类里面 public static void main(String[] args) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain"); // CloseGuard defaults to true and can be quite spammy. We // disable it here, but selectively enable it later (via // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false); Environment.initForCurrentUser(); // Set the reporter for event logging in libcore EventLogger.setReporter(new EventLoggingReporter()); // Make sure TrustedCertificateStore looks in the right place for CA certificates final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId()); TrustedCertificateStore.setDefaultUserDirectory(configDir); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line. // It will be in the format "seq=114" long startSeq = 0; if (args != null) { for (int i = args.length - 1; i >= 0; --i) { if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) { startSeq = Long.parseLong( args[i].substring(PROC_START_SEQ_IDENT.length())); } } } ActivityThread thread = new ActivityThread(); thread.attach(false, startSeq); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }","categories":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}],"tags":[{"name":"Handler","slug":"Handler","permalink":"https://blog.musiclake.cn/tags/Handler/"}],"keywords":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}]},{"title":"常用第三方库源码分析——Okhttp","slug":"源码分析/Okhttp源码分析","date":"2019-09-21T16:00:00.000Z","updated":"2022-05-05T09:56:06.450Z","comments":true,"path":"posts/3c273908.html","link":"","permalink":"https://blog.musiclake.cn/posts/3c273908.html","excerpt":"","text":"以前总想分析一下一些开源框架的源码,看看被人的设计思想和功能","categories":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}],"tags":[{"name":"Okhttp","slug":"Okhttp","permalink":"https://blog.musiclake.cn/tags/Okhttp/"}],"keywords":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}]},{"title":"常用网络请求库对比——Volley、Okhttp、Retrofit对比","slug":"源码分析/volley、okhttp、retrofit分析","date":"2019-09-21T16:00:00.000Z","updated":"2022-05-05T09:56:06.450Z","comments":true,"path":"posts/b0301fc2.html","link":"","permalink":"https://blog.musiclake.cn/posts/b0301fc2.html","excerpt":"","text":"Volley、Okhttp、Retrofit三大网络请求库分析 1. 原生Android 原生使用HttpUrlConnection 2. VolleyVolley 是 Google 官方出的一套小而巧的异步请求库,该框架封装的扩展性很强,支持 HttpClient、HttpUrlConnection,甚至支持 OkHttp,具体方法可以看 Jake 大神的这个 Gist 文件 优点缺点在BasicNetwork中判断了statusCode(statusCode < 200 || statusCode > 299),如何符合条件直接抛出IOException(),不够合理 导致401等其他状态抛出IOException解决方案 :http://blog.csdn.net/kufeiyun/article/details/44646145http://stackoverflow.com/questions/30476584/android-volley-strange-error-with-http-code-401-java-io-ioexception-no-authe 图片加载性能一般 对大资源下载支持不够","categories":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}],"tags":[],"keywords":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}]},{"title":"建造者模式","slug":"设计模式/建造者模式","date":"2019-09-13T16:00:16.000Z","updated":"2022-05-05T09:56:06.451Z","comments":true,"path":"posts/fe816c3c.html","link":"","permalink":"https://blog.musiclake.cn/posts/fe816c3c.html","excerpt":"","text":"简介建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。 实例Android 中AlertDialog ```public class AlertDialog extends AppCompatDialog implements DialogInterface { //只展示部分代码 ... public static class Builder { private final AlertParams P; private final int mTheme; public Builder(@NonNull Context context) { this(context, AlertDialog.resolveDialogTheme(context, 0)); } public Builder(@NonNull Context context, @StyleRes int themeResId) { this.P = new AlertParams(new ContextThemeWrapper(context, AlertDialog.resolveDialogTheme(context, themeResId))); this.mTheme = themeResId; } //只展示部分代码 .... } }","categories":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"keywords":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}]},{"title":"责任链模式","slug":"设计模式/责任链模式","date":"2019-09-13T06:00:16.000Z","updated":"2022-05-05T09:56:06.452Z","comments":true,"path":"posts/6208627e.html","link":"","permalink":"https://blog.musiclake.cn/posts/6208627e.html","excerpt":"","text":"简介责任链模式 (Chain of Responsibility Pattern)是一种常见的行为模式。责任链模式为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。 典型实例okhttp 拦截器(Interceptor)就使用了责任链模式","categories":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"keywords":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}]},{"title":"暴走P图项目重构","slug":"Android/代码重构经验","date":"2019-09-11T02:04:02.000Z","updated":"2022-05-05T09:56:06.445Z","comments":true,"path":"posts/db3d6b05.html","link":"","permalink":"https://blog.musiclake.cn/posts/db3d6b05.html","excerpt":"","text":"将MVP 代码重构 MVVM 总结 1、首先将整个工程 迁移到AndroidX(可做可不做,为了跟紧最新技术)AndroidX 是 Google 推出了的,目的是为了统一 android.support.xxx 兼容包主要迁移是利用Android Studio 的 Refacetor -> Migrate to AndroidX 操作可以通过查看 gradle.properties 中 android.useAndroidX=true 来确定是否迁移了AndroidX最后查看app/build.gradle 中是否导入了相关的三方库如 implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2' implementation "androidx.lifecycle:lifecycle-livedata-ktx:$rootProject.lifecycleVersion" implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.lifecycleVersion" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.lifecycleVersion" 2、将MVP 转成 MVVM根据项目架构(如包结构),可以对包结构做一个具体分析,根据业务功能,划分数据包、功能包、适配器包、工具包等然后在功能包内再 定义各个功能的viewmodel。 初始想法是 先将 XXXPresenter 重命名为 XXXViewModel,然后再根据具体的业务,使用LiveData观察会改变的数据类","categories":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}],"tags":[{"name":"重构","slug":"重构","permalink":"https://blog.musiclake.cn/tags/%E9%87%8D%E6%9E%84/"},{"name":"想法","slug":"想法","permalink":"https://blog.musiclake.cn/tags/%E6%83%B3%E6%B3%95/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}]},{"title":"Android 系统适配","slug":"Android/系统适配","date":"2019-09-09T02:04:02.000Z","updated":"2022-05-05T09:56:06.445Z","comments":true,"path":"posts/adb2609a.html","link":"","permalink":"https://blog.musiclake.cn/posts/adb2609a.html","excerpt":"","text":"Android系统适配主要介绍一下适配各个版本系统的特性 Android 5.0 5.1 通知栏适配Android 6.0 6.1 动态权限申请 Android 7.0 7.1 应用间共享文件Fileprovider 7.2 org.apache不支持问题 7.3 SharedPreferences闪退SharedPreferences read = getSharedPreferences(RELEASE_POOL_DATA, MODE_WORLD_READABLE);//MODE_WORLD_READABLE :7.0以后不能使用这个获取,会闪退,修改成MODE_PRIVATE Android 8.0 8.1 通知适配.增加渠道ID安装APK.允许安装未知来源 8.2 通知适配安卓8.0中,为了更好的管制通知的提醒,不想一些不重要的通知打扰用户,新增了通知渠道,用户可以根据渠道来屏蔽一些不想要的通知 8.3 安装APK 首先在AndroidManifest文件中添加安装未知来源应用的权限: <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> 8.4 SecurityException的闪退 8.5 静态广播无法正常接收 问题原因: Android 8.0 引入了新的广播接收器限制,因此您应该移除所有为隐式广播 Intent 注册的广播接收器 8.6 Caused by: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation问题原因: Android 8.0 非全屏透明页面不允许设置方向(后面8.1系统谷歌就去掉了这个限制,可能很多人真的不习惯吧) 解决方案: (1)android:windowIsTranslucent设置为false (2)如果还是想用的话,就去掉清单文件中Activity中的android:screenOrientation=”portrait”, (3)就是使用透明的dialog或者PopupWindow来代替,也可以用DialogFragment,看自己的需求和喜好 Android 9.0 9.1 CLEARTEXT communication to life.115.com not permitted by network security policy http明文网络请求问题 9.2 其他Api的修改 Android 10Android 10版本对于用户的隐私权限要求更严了,所以需要对于权限这块做好适配,如 ime 参考参考1","categories":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}],"tags":[{"name":"适配","slug":"适配","permalink":"https://blog.musiclake.cn/tags/%E9%80%82%E9%85%8D/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}]},{"title":"MVC ,MVP,MVVM 的理解","slug":"Android/MVP、MVVM的理解","date":"2019-08-30T16:00:00.000Z","updated":"2022-05-05T09:56:06.444Z","comments":true,"path":"posts/234507b1.html","link":"","permalink":"https://blog.musiclake.cn/posts/234507b1.html","excerpt":"","text":"一切都是为了解耦合,开发更加方便 MVC简单介绍Model、View、ControllerModel 处理网络数据,数据库数据等View xmlController activity MVPModel View Presenter Model 处理网络数据,数据库数据等 Presenter 主要把model 和 view 隔离,减少耦合,拆分Controller层 MVVM基于MVP模式的基础上,将P变成ViewModel,使用databinding 实现数据的双向绑定。 Viewmodel DatabindingGoogle 官方介绍官方解释:数据绑定库是一个支持库,允许您使用声明性格式而不是以编程方式将布局中的UI组件绑定到应用程序中的数据源。 布局通常在具有调用UI框架方法的代码的活动中定义。 例如,下面的代码调用findViewById()来查找TextView小部件并将其绑定到viewModel变量的userName属性: DataBinding 是谷歌官方发布的一个框架,顾名思义即为数据绑定,是 MVVM 模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter 层替换成了 ViewModel 层。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常 启用 DataBinding 的方法是在对应 Model 的 build.gradle 文件里加入以下代码,同步后就能引入对 DataBinding 的支持 android { dataBinding { enabled = true } } LiveDataLiveData是一个数据持有类,它可以通过添加观察者被其他组件观察其变更。不同于普通的观察者,它最重要的特性就是遵从应用程序的生命周期,如在Activity中如果数据更新了但Activity已经是destroy状态,LivaeData就不会通知Activity(observer)。当然。LiveData的优点还有很多,如不会造成内存泄漏等。LiveData通常会配合ViewModel来使用,ViewModel负责触发数据的更新,更新会通知到LiveData,然后LiveData再通知活跃状态的观察者。","categories":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}],"tags":[],"keywords":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}]},{"title":"常用第三方库源码分析——Dagger2","slug":"源码分析/Dagger2","date":"2019-08-15T02:04:02.000Z","updated":"2022-05-05T09:56:06.449Z","comments":true,"path":"posts/5880a2ff.html","link":"","permalink":"https://blog.musiclake.cn/posts/5880a2ff.html","excerpt":"","text":"Dagger2 是什么?Dagger2是Dagger的升级版,是一个依赖注入框架,第一代由大名鼎鼎的Square公司共享出来,第二代则是由谷歌接手后推出的,现在由Google接手维护. Google dagger Github 地址 Dagger是依赖注入的编译时框架。 它不使用反射或运行时字节码生成,在编译时进行所有分析,并生成纯Java源代码。 Gladle引入 // Add Dagger dependencies dependencies { api 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x' }","categories":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}],"tags":[],"keywords":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/categories/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"}]},{"title":"help","slug":"help","date":"2019-03-23T07:30:40.000Z","updated":"2022-05-05T09:56:06.446Z","comments":true,"path":"posts/8875cac.html","link":"","permalink":"https://blog.musiclake.cn/posts/8875cac.html","excerpt":"","text":"","categories":[],"tags":[],"keywords":[]},{"title":"接入Google、FaceBook第三方登录","slug":"login-sdk","date":"2019-01-16T15:16:43.000Z","updated":"2022-05-05T09:56:06.447Z","comments":true,"path":"posts/baa940ea.html","link":"","permalink":"https://blog.musiclake.cn/posts/baa940ea.html","excerpt":"","text":"通过Firebase接入Google、FaceBook等第三方登录准备 科学上网 google 账号,facebook账号 接入指南主要分为三部分。 Google登录 Facebook登录 Firebase集成接入google,facebook登录 Google登录推荐只接入Google登录,不接入Firebase 接入指南 主要步骤1、新建应用,或者已有的选择应用2、\b创建应用的凭借,即token 核心代码 // Configure sign-in to request the user's ID, email address, and basic // profile. ID and basic profile are included in DEFAULT_SIGN_IN. GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) .requestEmail() .build(); // Build a GoogleSignInClient with the options specified by gso. mGoogleSignInClient = GoogleSignIn.getClient(this, gso); private void signIn() { Intent signInIntent = mGoogleSignInClient.getSignInIntent(); startActivityForResult(signInIntent, RC_SIGN_IN); } Facebook登录推荐只接入Facebook登录,不接入Firebase Facebook应用管理 主要流程 1、首先进入facebook应用开发面板,新建应用,并添加Facebook 登录 2、跟着步骤一步步完成所有配置(注意点:生成秘钥序列) 3、然后一步步点击完成 4、测试OK Firebase集成接入google,facebook登录如果项目中已经集成了Firebase,则推荐使用这种方式 Firebase项目管理界面 创建项目 1、进入项目设置 开发->Authentication->登录方法。启用登录提供方中的Google,facebook。 2、\b关联google登录,只需要更新项目设置,添加应用,然后根据要求添加sha1指纹,填写包名,下载google-service.json文件引入到项目中。 3、启动facebook登录,按facebook登录步骤,启动facebook登录,然后配置应用token。","categories":[{"name":"接口","slug":"接口","permalink":"https://blog.musiclake.cn/categories/%E6%8E%A5%E5%8F%A3/"}],"tags":[{"name":"api","slug":"api","permalink":"https://blog.musiclake.cn/tags/api/"}],"keywords":[{"name":"接口","slug":"接口","permalink":"https://blog.musiclake.cn/categories/%E6%8E%A5%E5%8F%A3/"}]},{"title":"Flutter 学习","slug":"flutter-study","date":"2019-01-13T16:00:51.000Z","updated":"2022-05-05T09:56:06.446Z","comments":true,"path":"posts/2d016662.html","link":"","permalink":"https://blog.musiclake.cn/posts/2d016662.html","excerpt":"","text":"Flutter简介Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。 具备多个特点 快速开发:主要是毫秒级的热重载,能够在代码修改之后能立即刷新界面 富有表现力和灵活的UI:快速发布聚焦于原生体验的功能。分层的架构允许您完全自定义,从而实现难以置信的快速渲染和富有表现力、灵活的设计。 原生性能:Flutter包含了许多核心的widget,如滚动、导航、图标和字体等,这些都可以在iOS和Android上达到原生应用一样的性能。 使用编程语言:Dart 入门踩坑注意点 项目结构划分,android 或 ios 底层源码编辑 命名规范,主要包括文件名命名,变量命名和方法命名 多语言支持 待续… Dart简介及基本命名规范原文:dart官方文档 总结几点1.类名使用大驼峰式2.变量名使用小驼峰3.文件名使用小写字母加下划线4.不要使用前缀字母5.注意导入包的排序6.对于所有的控制流结构使用花括号 具体样例 其他资源Flutter 官方网站Flutter 中文网Flutter 三方库搜索","categories":[],"tags":[],"keywords":[]},{"title":"音频焦点管理","slug":"Android/AudioFocus","date":"2018-04-24T02:04:02.000Z","updated":"2022-05-05T09:56:06.443Z","comments":true,"path":"posts/68e283a1.html","link":"","permalink":"https://blog.musiclake.cn/posts/68e283a1.html","excerpt":"","text":"#前言随着Android版本的升级,以前用的一些api都提过时,项目中使用AudioFocusRequest 顾名思义是一个音频焦点请求类。 一个封装音频焦点请求信息的类,AudioFocusRequest通过Builder实例化,有两个方法requestAudioFocus和abandonAudioFocusRequest。 #什么是焦点请求? 音频焦点是API 8中引入的一个概念。它主要作用于用户在同一个时刻只能关注单个音频流,例如,不能在同一时间听音乐或广播。在某些情况下,可以同时播放多个音频流,但只有一个用户会真正听到(关注),而其他的是背景音。例如在驾驶时正在说话,低音量播放音乐。(又称为低音)。 当应用程序请求音频焦点时,它表示它有意“拥有”音频焦点播放音频。我们来回顾一下不同类型的焦点请求,请求后的返回值,以及对损失的回应。 注意:在授予焦点之前,应用程序不应播放任何内容。 不同类型的焦点请求 AUDIOFOCUS_GAIN:用于指示音频焦点的增益或未知持续时间的音频焦点请求。 AUDIOFOCUS_GAIN_TRANSIENT:用于指示临时增益或音频焦点请求,预计持续时间短 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:用于指示音频焦点的临时请求,预计持续时间较短时间量,以及其他音频应用程序可以继续播放的位置降低产出水平后(也称为“回避”)。临时改变的例子是回放音乐播放的行车路线在后台是可以接受的。 AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:用于指示音频焦点的临时请求,预计持续时间较短,没有其他应用程序或系统组件应该播放的时间量、任何东西。 独占和瞬态音频焦点请求的例子是语音、备忘记录和语音识别,在此期间系统不应该播放任何内容通知,媒体播放应暂停。 AUDIOFOCUS_LOSS:用于指示未知持续时间的音频焦点丢失。 AUDIOFOCUS_LOSS_TRANSIENT:用于指示音频焦点的瞬时丢失。 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:用于指示音频焦点的失败者可能发生的音频焦点的瞬时丢失,如果它想继续播放(也称为“躲避”),则降低其输出音量,新的焦点所有者不需要其他人保持沉默。 /** * Used to indicate no audio focus has been gained or lost, or requested. */ public static final int AUDIOFOCUS_NONE = 0; /** * Used to indicate a gain of audio focus, or a request of audio focus, of unknown duration. * @see OnAudioFocusChangeListener#onAudioFocusChange(int) * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int) */ public static final int AUDIOFOCUS_GAIN = 1; /** * Used to indicate a temporary gain or request of audio focus, anticipated to last a short * amount of time. Examples of temporary changes are the playback of driving directions, or an * event notification. * @see OnAudioFocusChangeListener#onAudioFocusChange(int) * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int) */ public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; /** * Used to indicate a temporary request of audio focus, anticipated to last a short * amount of time, and where it is acceptable for other audio applications to keep playing * after having lowered their output level (also referred to as "ducking"). * Examples of temporary changes are the playback of driving directions where playback of music * in the background is acceptable. * @see OnAudioFocusChangeListener#onAudioFocusChange(int) * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int) */ public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3; /** * Used to indicate a temporary request of audio focus, anticipated to last a short * amount of time, during which no other applications, or system components, should play * anything. Examples of exclusive and transient audio focus requests are voice * memo recording and speech recognition, during which the system shouldn't play any * notifications, and media playback should have paused. * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int) */ public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4; /** * Used to indicate a loss of audio focus of unknown duration. * @see OnAudioFocusChangeListener#onAudioFocusChange(int) */ public static final int AUDIOFOCUS_LOSS = -1 * AUDIOFOCUS_GAIN; /** * Used to indicate a transient loss of audio focus. * @see OnAudioFocusChangeListener#onAudioFocusChange(int) */ public static final int AUDIOFOCUS_LOSS_TRANSIENT = -1 * AUDIOFOCUS_GAIN_TRANSIENT; /** * Used to indicate a transient loss of audio focus where the loser of the audio focus can * lower its output volume if it wants to continue playing (also referred to as "ducking"), as * the new focus owner doesn't require others to be silent. * @see OnAudioFocusChangeListener#onAudioFocusChange(int) */ public static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK = -1 * AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK; 例子初始化audio focus request mAudioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE); mPlaybackAttributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .build(); mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) .setAudioAttributes(mPlaybackAttributes) .setAcceptsDelayedFocusGain(true) .setWillPauseWhenDucked(true) .setOnAudioFocusChangeListener(this, mMyHandler) .build(); mMediaPlayer = new MediaPlayer(); mMediaPlayer.setAudioAttributes(mPlaybackAttributes); final Object mFocusLock = new Object(); boolean mPlaybackDelayed = false; // requesting audio focus int res = mAudioManager.requestAudioFocus(mFocusRequest); synchronized (mFocusLock) { if (res == AUDIOFOCUS_REQUEST_FAILED) { mPlaybackDelayed = false; } else if (res == AUDIOFOCUS_REQUEST_GRANTED) { mPlaybackDelayed = false; playbackNow(); } else if (res == AUDIOFOCUS_REQUEST_DELAYED) { mPlaybackDelayed = true; } } 实现音频焦点监听,OnAudioFocusChangeListener &#64;Override public void onAudioFocusChange(int focusChange) { switch (focusChange) { case AudioManager.AUDIOFOCUS_GAIN: if (mPlaybackDelayed || mResumeOnFocusGain) { synchronized (mFocusLock) { mPlaybackDelayed = false; mResumeOnFocusGain = false; } playbackNow(); } break; case AudioManager.AUDIOFOCUS_LOSS: synchronized (mFocusLock) { // 长久失去音频焦点,停止播放 mResumeOnFocusGain = false; mPlaybackDelayed = false; } pausePlayback(); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: // 我们以同样的方式处理所有短暂的损失,因为我们从不聆听有声书 synchronized (mFocusLock) { // we should only resume if playback was interrupted mResumeOnFocusGain = mMediaPlayer.isPlaying(); mPlaybackDelayed = false; } pausePlayback(); break; } }","categories":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}],"tags":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/tags/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"},{"name":"AudioFocus","slug":"AudioFocus","permalink":"https://blog.musiclake.cn/tags/AudioFocus/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}]},{"title":"总结","slug":"interview/interview-android","date":"2018-04-23T16:55:00.000Z","updated":"2022-05-05T09:56:06.447Z","comments":true,"path":"posts/c8d0539d.html","link":"","permalink":"https://blog.musiclake.cn/posts/c8d0539d.html","excerpt":"","text":"总结一下 基础知识点1、View的绘制2、View的事件分发机制3、StringBuffer 和StringBuilder区别4、常用的设计模式5、Android activity的启动流程6、动画、属性动画7、MVP模式 项目中的问题1、做这个项目有没有遇到什么问题2、项目中使用的一些技术问题3、这个效果是怎么实现的 工作内容1、平时工作内容是什么2、完成了什么任务","categories":[{"name":"杂谈","slug":"杂谈","permalink":"https://blog.musiclake.cn/categories/%E6%9D%82%E8%B0%88/"}],"tags":[{"name":"杂谈","slug":"杂谈","permalink":"https://blog.musiclake.cn/tags/%E6%9D%82%E8%B0%88/"}],"keywords":[{"name":"杂谈","slug":"杂谈","permalink":"https://blog.musiclake.cn/categories/%E6%9D%82%E8%B0%88/"}]},{"title":"音乐湖项目遇到的问题","slug":"project/musiclake-buglist","date":"2018-04-23T10:21:16.000Z","updated":"2022-05-05T09:56:06.447Z","comments":true,"path":"posts/255a7cdf.html","link":"","permalink":"https://blog.musiclake.cn/posts/255a7cdf.html","excerpt":"","text":"1、ViewPager中fragment A,点击跳转切换到另外一个Fragment B(addFragment 添加B),然后返回到A(回退栈顶的A)。A中的数据不刷新。尝试了在onResume中,添加更新,但是效果还是一样。 解决办法,引入事件总线RxBus. 2、适配Android 8.0 出现java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation 只有全屏不透明的活动才能请求方向.主要是manifest中定义了android:screenOrientation=”portrait” 和设置半透明主题导致。 3、使用Rxbus时,退出Activity。然后从通知栏进入程序崩溃。 4、Retrofit gson解析数据QQ音乐歌词数据时,抛出异常,因为返回的数据不是一个完整的json数据。 io.reactivex.exceptions.OnErrorNotImplementedException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $ .... 返回数据格式jsonp格式:MusicJsonCallback({…})。通过下面的解决方法,不会抛出异常。但是获得的String 数据只有MusicJsonCallback( .这部分重要部分不知道去哪了… Gson gson = new GsonBuilder() .setLenient() .create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); 不得已,最后尝试重写GSON转换器。遇到一个大坑那就是ResponseBody //ResponseData中的流只能使用一次,我们先将流中的数据读出在byte数组中。这个方法中已经关闭了ResponseBody,所以不需要再关闭了 5、Observable 转换成 Observable //lamba表达式 Observable.flatMap(T -> { return Observable.fromArray(S); } //Java表达式 Observable.flatMap(new Function<T, ObservableSource<?>>() { @Override public ObservableSource<?> apply(T tt) throws Exception { return null; } }); 6、Retrofit 多个网络请求合并使用 Observable.merge(QQApiServiceImpl.search(key, limit, page), XiamiServiceImpl.search(key, limit, page)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<List<Music>>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(List<Music> results) { mView.showSearchResult(results); } @Override public void onError(Throwable e) { mView.showEmptyView(); mView.hideLoading(); } @Override public void onComplete() { mView.hideLoading(); } }); 7、封装MediaPlayer,prepareAsync()装载异常,还有getDuration会出现数据异常 //异步装载数据 player.prepareAsync(); //设置异步装载完毕监听事件 player.setOnPreparedListener(this); //设置异步装载进度 player.setOnBufferingUpdateListener(this); 8、ViewPager中 多点触碰导致 java.lang.IllegalArgumentException: pointerIndex out of range at android.view.MotionEvent.nativeGetAxisValue(Native Method) 原因分析:在LrcView中,因为调用了requestDisallowInterceptTouchEvent来区分左右滑动和上下滑动,所以会导致父类的view多点触摸有些情况下会出现数组溢出的情况.解决办法 利用try{}catch(){}抛出异常 重写ViewPager中的onTouchEvent 和onInterceptTouchEvent。public class MultiTouchViewPager extends ViewPager { public MultiTouchViewPager(Context context) { super(context); } public MultiTouchViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onTouchEvent(MotionEvent ev) { try { return super.onTouchEvent(ev); } catch (IllegalArgumentException ex) { ex.printStackTrace(); } return false; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { try { return super.onInterceptTouchEvent(ev); } catch (IllegalArgumentException ex) { ex.printStackTrace(); } return false; } } 9、InputEventReceiver: Attempted to finish an input event but the input event receiver has already been disposed. 10、重构数据库,当插入十几条数据或更多时,其中重复的数据更新数据库,不重复的添加进去,能不能用一条语句实现? 11、再次设计数据库,播放队列,播放历史设计成一种特殊的歌单数据。 12、增加setNextMediaPlayer方法时,抛出异常E/MediaPlayer: next player is not prepared 13、Observable.flatmap 操作符的使用 14、Attempt to invoke interface method 接口下载服务接口更新下载状态。 15、AudioManager控制播放 16、播放暂停恢复,音量变化 17、线控 18、对于ViewPager+Fragment+Tablayout中 懒加载优化。 19、getwidth()和getheight()已经过时使用point//屏幕宽高 Displaydisplay = getWindowManager().getDefaultDisplay(); Pointsize = newPoint(); display.getSize(size); width = size.x; height = size.y;","categories":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}],"tags":[{"name":"MusicLake","slug":"MusicLake","permalink":"https://blog.musiclake.cn/tags/MusicLake/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}]},{"title":"git使用---修改历史提交记录","slug":"tools/git","date":"2018-02-02T13:34:25.000Z","updated":"2022-05-05T09:56:06.448Z","comments":true,"path":"posts/be0c7f93.html","link":"","permalink":"https://blog.musiclake.cn/posts/be0c7f93.html","excerpt":"","text":"git使用 总结@(总结)[git] 总结一下当前工作中经常使用的git命令 ##日常使用 git初始化和拉取git分支git init git clone ... 修改&提交对于只有修改前先pull 一下代码,修改完成后再push git add -u / -A git commit git push 分支管理git branch git branch newbranch git checkout newbranch 查看状态&历史记录git status git log 移植git cherry-pick git commit --amend 常用命令git log git log 查看 当前分支 的提交记录 git log –stat git log -5 –pretty –oneline 显示过去5次提交 git log –follow 显示某个文件的版本历史,包括文件改名 git status显示git状态主要分为工作区和暂存区两个,其中工作区又分为git 监控的和未被监控的两个区域。 git add git add filename git add -A/. 添加当前目录 all tracked and untracked files 到暂存区 git add -u 添加当前目录的tracked files 到暂存区 git commit将暂存区的提交当分支上 git commit -m “the commit message” 提交 git commit –amend 增补提交. 会使用与当前提交节点相同的父节点进行一次新的提交,旧的提交将会被取消. git checkout可以移出暂存区的文件、可以切换分支也可以切换到tag节点。 git checkout branch 检出分支 git checkout tag_name 检出当前tag git checkout file_name 放弃对文件的修改 git branch可以用来创建、删除、查看分支,不管是远程的还是本地的都可以。 git branch 显示本地仓库分支 git branch -a 查看本地远程仓库所有分支 git branch -r 显示远程仓库分支 git branch -D master 删除master分支 git branch master 新建master分支 git reset用来回退版本,例如从当前commit 回退到历史提交中 git reset –hard 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致 git reset –soft 不会改变暂存区,仅仅将commit回退到了指定的提交 git reset –mixed 重置当前分支的HEAD为指定commit,工作区commit之后的全部放入暂存区 git clean可以用来清理工作区中 Untracked files。 git clean -n 显示 将要 删除的 文件 和 目录 git clean -f 删除 文件 git clean -df 删除 文件 和 目录 git pull从服务器拉取代码,也有些说法不推荐使用pull拉取代码,因为拉取的代码有可能会污染本地的提交记录。 git push将本地分支推送到服务器上 git push origin master 将本地分支推送到master远程分支 git cherry-pick将其他分支的提交修改剪切到当前分支,cherry-pick 后面带上需要剪切移植的commit ID。当遇到cherry-pick冲突后,需要解决冲突,然后使用 git commit –amend 修改提交信息即可。 git cherry-pick 剪切过来提交过来后,直接提交到当前分支 git cherry-pick –no-commit 不直接提交当前分支,在暂存区可见修改记录的文件。 git merge git merge master 当前分支合并 master分支 git diff显示暂存区和工作区的差异 git tag git tag 新建一个tag在当前commit git tag 新建一个tag在指定commit git tag -d 删除本地tag git push origin :refs/tags/ 删除远程tag git rebasegit stash git stash save -a “message” 将当前改动保存到一个 git stash list 显示所有临时区的stash id git stash pop git stash drop gitk 图形界面gitk 是一个图形界面,可用用来查看提交记录和修改记录。也可以使用其他的git 管理工具,例如source tree。感觉挺好用的。","categories":[{"name":"开发工具","slug":"开发工具","permalink":"https://blog.musiclake.cn/categories/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"}],"tags":[{"name":"git","slug":"git","permalink":"https://blog.musiclake.cn/tags/git/"}],"keywords":[{"name":"开发工具","slug":"开发工具","permalink":"https://blog.musiclake.cn/categories/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"}]},{"title":"git使用---修改历史提交记录","slug":"tools/git_rebase","date":"2018-02-02T13:34:25.000Z","updated":"2022-05-05T09:56:06.448Z","comments":true,"path":"posts/be0c7f93.html","link":"","permalink":"https://blog.musiclake.cn/posts/be0c7f93.html","excerpt":"","text":"git cherry-pick所遇到的问题使用git 也有很长一段时间了,所以想总结一下git 的使用。其中,在日常工作中copy代码(搬砖)是常有的事情。对于git 管理的分支来说,git cherry-pick就是另类的copy。说的正式点就是 移植。 而cherry-pick 就是移植命令。 –对于git cherry-pick过来的提交记录,有些是因为一点小错误导致重复提交的,还有一些是一个功能分了好几次提交,对于这些记录,如果全部按原来分支的提交记录提交,就感觉很麻烦,到底有没有一种方法能够合并几条提交记录呢? 还有就是当cherry-pick多条记录后,突然想修改其中一条修改记录时。发现很难办。这时 git rebase 就很轻松了解决掉了这些难处。 当然网上也有很多类似的介绍,也可以去借阅一下。 1. 合并多条修改初始时,通过git log 查看 提交记录,记录一下commit hash值。 @ubuntu:~/work/git$ git log commit e916213e3c4cc9c977ec20902ae27ae45352eef3 Date: Tue Feb 6 20:05:32 2018 +0800 Third commit commit 358371a2b2b15d847bf89e44e94e7b926fed2da0 Date: Tue Feb 6 20:04:48 2018 +0800 Second commit commit ba82e7007559afab14ff124ea51e19772dedfe2c Date: Tue Feb 6 20:03:32 2018 +0800 First commit 然后通过 git rebase -i commit-id 命令编辑提交记录。 git rebase -i commit-id 表示,修改commit-id以后的提交记录, 也可以用git rebase -i HEAD ~n. n表示倒数第几条记录。 pick 358371a Second commit pick e916213 Third commit # Rebase ba82e70..e916213 onto ba82e70 # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. # 其中 pick 使用这条提交(保持不变) reword 使用这条提交,可以编辑当前commit信息 edit 使用这条提交,放弃当前commit信息 squash 使用这条提交,合并前一条提交 fixup 和squash一样,但会放弃当前这个提交的信息 exec 合并修改使用,将 e916213 Third commit 前的pick 改成squash 即可,然后保存退出。 pick 358371a Second commit squash e916213 Third commit 修改完成后再次查看git log,已经合并到一起了。因为使用squash,所以两条记录得message都会合在一起。如果 使用fixup,则会放弃Third commit 的commit 信息。 commit 940fec9ea88c69a195c546d48e5209c4cdb6abec Date: Tue Feb 6 20:04:48 2018 +0800 Second commit Third commit commit ba82e7007559afab14ff124ea51e19772dedfe2c Date: Tue Feb 6 20:03:32 2018 +0800 First commit 2. 修改提交记录中的commit 信息同上面,只要在需要修改的记录前 将pick 换成reword,即可进入编辑界面,编辑commit message,然后保存退出。这样就修改完成了。 当使用edit时,虽然也可以修改, Stopped at 3af42b9... reword Second commit You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continue 再使用git commit –amend 进入编辑界面修改。 保存,使用git rebase –continue 返回到最新节点。 可以实际操作一下,如果怕玩坏工作分支,可以自己新建一个分支玩一玩。这个git rebase 真的很神奇。对于rebase的其他功能也很强大。","categories":[{"name":"开发工具","slug":"开发工具","permalink":"https://blog.musiclake.cn/categories/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"}],"tags":[{"name":"git","slug":"git","permalink":"https://blog.musiclake.cn/tags/git/"}],"keywords":[{"name":"开发工具","slug":"开发工具","permalink":"https://blog.musiclake.cn/categories/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"}]},{"title":"WallpaperPicker分析","slug":"源码分析/WallpaperPicker分析","date":"2017-12-25T00:21:16.000Z","updated":"2022-05-05T09:56:06.450Z","comments":true,"path":"/post","link":"","permalink":"https://blog.musiclake.cn/post","excerpt":"","text":"分析点1.主要类介绍2.WallpaperPicker中主要使用的设计模式——模板方法模式3.图片的处理 原生Android系统中的WallpaperPicker主要Activity有WallpaperPickerActivity、WallpaperCropActivity 两个 初始化流程根据源码,壁纸Activity加载主要分为以下步骤:创建 HandlerThread mLoaderThread = new HandlerThread("wallpaper_loader"); mLoaderThread.start(); mLoaderHandler = new Handler(mLoaderThread.getLooper(), this); 调用 init(),加载布局、控件初始化、初始化一些操作 -> mCropView初始化触摸监听 -> 初始化壁纸偏移量 mWallpaperParallaxOffset -> 初始化壁纸数据库 加载填充存储壁纸 -> 加载填充Resources壁纸 -> 异步加载填充live壁纸 -> 加载填充第三方壁纸 -> Gallery增加tile -> 初始化mCropView布局改变监听事件 -> 初始化资源壁纸列表 -> 为删除items时创建过渡动画 -> 设置actionBar监听事件 概念介绍BitmapRegionTileSource:图片区域平铺资源 壁纸预览流程-> mCropView.addOnLayoutChangeListener-> onClick(mWallpapersView.getChildAt(mSelectedIndex))-> 根据当前壁纸信息Tag 获取 壁纸来源-> 然后根据不同的壁纸来源调用 onClick-> 以ResourceWallpaperInfo为例。-> 创建图片输入流BitmapRegionTileSource.InputStreamSource-> 裁剪图片-> 创建 LoadRequest,初始化数据-> 移除 MSG_LOAD_IMAGE 加载图片的消息-> 发送加载图片的消息->> 加载图片 流程->> 判断bitmapSource 是否为空->>> 如果为空 返回默认壁纸的DrawableTileSourcereq.src不为空,调用BitmapSource的 loadInBackground->>>> loadInBackground 数据流程—–新建对象 InBitmapProvider—–获取图片旋转角度getExifRotation—–初始化图片大小解码器(主要获取宽高)—–图片解码BitmapFactory.Options/**BitmapFactory.Options 详解 inPreferredConfig:这个值是设置色彩模式,默认值是ARGB_8888,在这个模式下,一个像素点占用4bytes空间,一般对透明度不做要求的话,一般采用RGB_565模式,这个模式下一个像素点占用2bytes。inJustDecodeBounds:如果将这个值置为true,那么在解码的时候将不会返回bitmap,只会返回这个bitmap的尺寸。这个属性的目的是,如果你只想知道一个bitmap的尺寸,但又不想将其加载到内存时。这是一个非常有用的属性。inSampleSize:缩放比例,小于1时,当做1,缩放比例 = 1/inSampleSize。和图片的宽高及像素成正比。inPreferQualityOverSpeed:为true则优先保证Bitmap质量其次是解码速度inMutable:如果设置为true,将返回一个mutable的bitmap,可用于修改BitmapFactory加载而来的bitmap.inBitmap :API Level 11开始。优化Bitmap的内存使用/—-如果InBitmapProvider 不为空—-计算当前像素 根据缩放比例计算—-根据像素获取图片—-根据像素获取图片—-如果图片不为空设置 inBitmap 优化内存—-根据opts加载预览图片—-校验图片是否能应用在GL surface上/GLUitls 使用了getInternalFormat(mPreview) 使用了getType(mPreview)**/—-返回加载状态->>>> loadInBackground 结束->> 根据req.src实例化返回结果BitmapRegionTileSource—–BitmapRegionTileSource—–获取平铺尺寸—–获取旋转角度—–获取图片平铺解码器—–初始化 BitmapFactory.Options—–获取预览图片—–实例化纹理BitmapTexture ->> 判断是否加载成功 Bitmap reusableBitmap = bitmapProvider.forPixelCount(expectedPixles); -> mCropView 可见 参考博客","categories":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}],"tags":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/tags/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"},{"name":"WallpaperPicker","slug":"WallpaperPicker","permalink":"https://blog.musiclake.cn/tags/WallpaperPicker/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}]},{"title":"Android 源码Launcher模块分析","slug":"源码分析/Launcher","date":"2017-12-13T06:00:16.000Z","updated":"2022-05-05T09:56:06.449Z","comments":true,"path":"posts/a37bdd71.html","link":"","permalink":"https://blog.musiclake.cn/posts/a37bdd71.html","excerpt":"","text":"提纲1.什么是Launcher ,Launcher2 和 Launcher3的区别 2.Android 开机启动到Launcher运行的主要流程 3.Launcher 主要类介绍 4.Launcher 主要布局xml介绍 5.Launcher 一般主要需修改的地方 6.Launcher 动画,widget等 7.Launcher 主菜单界面UI Launcher启动的主要流程手机开机->init进程->zygote进程->SystemServer->ActivityManagerService->Launcher init进程: 1.创建一些文件夹并挂载设备2.初始化和启动属性服务3.解析init.rc配置文件并启动zygote进程 初始化init进程system/core/init/init.cpp启动zygote进程system/core/rootdir/root.rc{import /init.${ro.zygote}.rc}不同的平台(32、64及64_32)system/core/rootdir/init.zygote64.rc zygote进程:zygote 进程在初始化时会启动虚拟机,并加载一些系统资源。这样 zygote fork 出子进程后,子进程也继承了能正常工作的虚拟机和各种系统资源,接下来只需装载 apk 文件的字节码就可以运行应用程序了,可以大大缩短应用的启动时间,这就是 zygote 进程的主要作用。 SystemServer:1.启动Binder线程池,这样就可以与其他进程进行通信。2.创建SystemServiceManager用于对系统的服务进行创建、启动和生命周期管理。3.启动各种系统服务。 LauncherLauncher启动 1.Launcher.java:launcher中主要的activity。系统第一个启动的应用程序,在AndroidManifest.xml中定义了 2.LauncherApplication.java:应用程序全局初始化类,创建全局使用的应用程序缓存器Iconcache,创建全局使用的数据库加载类LauncherModel,注册事件监听器,注册数据库变化监听器 (在AndroidManifest.xml定义的Application) 3.IconCache.java:应用程序缓存器,通过一个HashMap<ComponentName,CacheEntry> mcache来缓存应用程序信息。内部类CachEntry对应存储应用程序的基本信息。makeDefaultIcon来创建默认的应用程序图标 4.LauncherModel.java:模型文件,封装了对数据库的操作。包含几个线程,加载所有应用程序和workspace的时候使用(loadAndBindAllApps,loadAndBindWorkspace)。其他的函数就是对数据库的封装,接口Callbacks提供加载程序和快捷方式的抽象方法。 5.LauncherProvider.java:launcher的数据库,里面存储了桌面的item的信息。在创建数据库的时候会调用loadFavorites(db)方法,解析xml目录下的default_workspace.xml文件,把其中的内容读出来写到数据库中,这样就得到了默认桌面的配置。 6.ItemInfo.java:对item的抽象,所有类型item的父类,item包含的属性有id(标识item的id),cellX(在横向位置上的位置,从0开始),cellY(在纵向位置上的位置,从0开始) ,spanX(在横向位置上所占的单位格),spanY(在纵向位置上所占的单位格),screen(在workspace的第几屏,从0开始),itemType(4种item类型,有widget,user_folder,application,shortcut),container(三种item存放的地方desktop,application,user_folder)。 7.Workspace.java:抽象的桌面。由N个cellLayout组成, 从cellLayout更高一级的层面上对事件的处理。Launcher.java中通过bindItems添加cellLayout.实现了DropTarget, DragSource, DragScroller。既是拖拽源,又是拖拽目的地,还可以左右拖动。 8.CellLayout.java:桌面的某一屏。是组成workspace的view,被划分成4X4的cell空间,用boolean[][]mOccupied来标识每个cell是否被占用. 9.AllApp2D.java:显示和存储应用程序列表的视图。。android自带的AllApp2D包括一个GridView和一个HomeButton,MTK修改成PagerControl,HorizontalPager和4个imageview。 10.HorizontalPager.java:AllApp2D中间的网格控件。由N个GridView组成,AllApps2D.java中通过addGridView添加GridView。 11.DeleteZone.java:删除框。在平时是出于隐藏状态,在将item长按拖动的时候会显示出来,如果将item拖动到删除框位置时会删除item。DeleteZone实现了DropTarget和DragListener两个接口。 DragLayer.java:launcher.xml的rootview。DragLayer实际上也是一个抽象的界面,用来处理拖动和对事件进行初步处理然后按情况分发下去,角色是一个DragController。它首先用onInterceptTouchEvent(MotionEvent)来拦截所有的touch事件,如果是长按item拖动的话不把事件传下去,直接交由onTouchEvent()处理,这样就可以实现item的移动了,如果不是拖动item的话就把事件传到目标view,交有目标view的事件处理函数做相应处理。 DragController.java:为拖拽定义的一个接口。包含一个接口,一个方法和两个静态常量。 接口为DragListener(包含onDragStart(),onDragEnd()两个函数), onDragStart()是在刚开始拖动的时候被调用, onDragEnd()是在拖动完成时被调用。 在launcher中典型的应用是DeleteZone,在长按拖动item时调用onDragStart()显示,在拖动结束的时候调用onDragEnd()隐藏。 一个函数包括用于在拖动是传递要拖动的item的信息以及拖动的方式 两个常量为DRAG_ACTION_MOVE,DRAG_ACTION_COPY来标识拖动的方式,DRAG_ACTION_MOVE为移动,表示在拖动的时候需要删除原来的item, DRAG_ACTION_COPY为复制型的拖动,表示保留被拖动的item。 14.UserFolder.java: 用户创建的文件夹。实现了DropTarget,是拖拽目的地,可以将item拖进文件夹,单击时打开文件夹,暂不能处可以重命名文件夹。 15.LiveFolder.java:系统自带的文件夹。从系统中创建出的所有联系人的文件夹等。 16.AddAdapter.java:长按桌面弹出的”添加到主屏幕”对话框所对应的适配器。。 17.ShortcutAdapter.java:添加快捷方式的对话框所对应的适配器 18.LauncherSettings.java:数据库项的字符串定义,","categories":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}],"tags":[{"name":"源码分析","slug":"源码分析","permalink":"https://blog.musiclake.cn/tags/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"},{"name":"Launcher","slug":"Launcher","permalink":"https://blog.musiclake.cn/tags/Launcher/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}]},{"title":"单例模式","slug":"设计模式/单例模式","date":"2017-11-13T06:00:16.000Z","updated":"2022-05-05T09:56:06.451Z","comments":true,"path":"posts/f1601c3e.html","link":"","permalink":"https://blog.musiclake.cn/posts/f1601c3e.html","excerpt":"","text":"前言说实话网上有很多介绍,那么为什么还要自己在写一遍呢?因为在网上看一遍容易忘掉,如果自己再重新写一遍,印象会深一点。 简介单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。 简单的说就是一个对象实例化只实例化一次。 运用单例模式的实现方式有 1、饿汉式 多线程安全描述:这种方式比较常用,但容易产生垃圾对象。优点:没有加锁,执行效率会提高。缺点:类加载时就初始化,浪费内存。 public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } } 2、懒汉式,可以根据不同的条件也可以分类优点:没有加锁,执行效率会提高。缺点:类加载时就初始化,浪费内存。 懒汉式,线程不安全因为没有加锁 synchronized,所以它是线程不安全的。 public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 懒汉式,线程安全增加同步锁,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。优点:第一次调用才初始化,避免内存浪费。优点:第一次调用才初始化,避免内存浪费。缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁) public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 3、双检锁/双重校验锁(DCL,即 double-checked locking)这种方式采用双锁机制,安全且在多线程情况下能保持高性能。 public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } 问题:为什么有两个非空判断问题:双检锁单例的缺点是什么4、登记式/静态内部类这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。 public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } } 5、枚举描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。不能通过 reflection attack 来调用私有构造方法。 public enum Singleton { INSTANCE; public void whateverMethod() { } } 参考链接http://www.runoob.com/design-pattern/singleton-pattern.html","categories":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"keywords":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}]},{"title":"客户化定制开机logo、开机动画","slug":"Android/customer_logo","date":"2017-11-02T13:34:25.000Z","updated":"2022-05-05T09:56:06.444Z","comments":true,"path":"posts/17a37153.html","link":"","permalink":"https://blog.musiclake.cn/posts/17a37153.html","excerpt":"","text":"设置开机logo高通平台1、修改的文件路径 LINUX/android/bootable/bootloader/lk/splash 2、准备好logo图片(png、bmp格式) 3、查看中原图片的分辨率,修改logo图片*保证分辨率一致* 4、生成splash.img镜像文件 生成splash 步骤 The steps to generate a splash.img: 1 sudo apt-get install python-imaging 2 python ./logo_gen.py boot_001.png (*.bmp) MTK平台1、修改的文件路径 vendor\\mediatek\\proprietary\\bootable\\bootloader\\lk\\dev\\logo 2、准备好logo图片(bmp格式) 3、查看中原图片的分辨率,修改logo图片*保证分辨率一致*替换相应格式的logo 设置开关机动画首先自己制作开关机动画包,如果有现成的压缩包那就不用了,压缩包的压缩方式必须是存储方式的zip格式压缩。 bootanimation.zip shutdownanimation.zip 压缩包中有两个文件夹part0、part1和desc.txt文件 part0和part1中主要存放一些开机动画的帧图片 desc.txt中内容如下 720 1680 15 p 1 0 part1 p 0 0 part0 720 1680 代表开机动画图片分辨率 15 代表播放速度。15 帧每秒 p 1(代表着播放一次) 0(空指令)part1 代表这part1文件夹内的图片只按名称顺序播放一次 p 0(代表着播放一次) 0(空指令)part0 代表这part0文件夹内的图片只按名称顺序循环播放直到结束 预览:必须在已经root的手机下才能替换。 //进入root权限 adb root adb remount adb shell //进入开关机动画存放目录 cd system/media //删除手机原先的开关机动画 rm bootanimation.zip rm shutdownanimation.zip //将开关机动画push到手机 adb push 文件路径 system/media 最后重启手机即可看到效果Android 源代码中替换开关机压缩包 高通平台 LINUX/android/vendor/qcom/proprietary/qrdplus/Extension/apps/BootAnimation MTK平台 framework/base/data/sounds","categories":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}],"tags":[{"name":"定制","slug":"定制","permalink":"https://blog.musiclake.cn/tags/%E5%AE%9A%E5%88%B6/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}]},{"title":"代理模式","slug":"设计模式/代理模式","date":"2017-10-13T06:00:16.000Z","updated":"2022-05-05T09:56:06.451Z","comments":true,"path":"posts/7b510e10.html","link":"","permalink":"https://blog.musiclake.cn/posts/7b510e10.html","excerpt":"","text":"代理模式代理模式(Proxy Pattern)也称为委托模式。是一种结构型设计模式。 定义为其他对象提供一种代理以控制对这个对象的访问 使用场景当无法或不想直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口 简单实现1、静态代理2、动态代理Java 提供了一个便捷的动态代理接口,InvocationHandler. Android 的跨进程通信机制与AIDLBinder 四个主要模块:Binder Client、Binder Server、ServerManager和Binder Driver。一先创建一个接口x二继承Binder创建一个Binder子类并实现接口X.三继承Service创建一个Service子类来表示服务端。重写onBind方法返回一个IBinder对象。四客户端Activity实现。 —-跨进程定义一个额外进程的Service。使用刚刚的方法会报一个类型转换的错误。使用AIDL定义接口。结果一样。 AIDL相当于 Binder Server。","categories":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}],"tags":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}]},{"title":"模板方法模式","slug":"设计模式/模板方法模式","date":"2017-10-13T06:00:16.000Z","updated":"2022-05-05T09:56:06.451Z","comments":true,"path":"posts/ecc6414b.html","link":"","permalink":"https://blog.musiclake.cn/posts/ecc6414b.html","excerpt":"","text":"模板方法模式定义定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使子类可以不改面一个算法的结构即可重定义该算法的某些特定步骤。##使用场景1、多个子类有公有方法,并且逻辑基本相同时。2、重要、复杂的算法,可以吧核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。3、重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取带父类中,然后通过钩子函数约束其行为。 示例package com.dp.example.templatemethod; /** * 抽象的Computer * @author mrsimple * */ public abstract class AbstractComputer { protected void powerOn() { System.out.println("开启电源"); } protected void checkHardware() { System.out.println("硬件检查"); } protected void loadOS() { System.out.println("载入操作系统"); } protected void login() { System.out.println("小白的电脑无验证,直接进入系统"); } /** * 启动电脑方法, 步骤固定为开启电源、系统检查、加载操作系统、用户登录。该方法为final, 防止算法框架被覆写. */ public final void startUp() { System.out.println("------ 开机 START ------"); powerOn(); checkHardware(); loadOS(); login(); System.out.println("------ 开机 END ------"); } } package com.dp.example.templatemethod; /** * 码农的计算机 * * @author mrsimple */ public class CoderComputer extends AbstractComputer { @Override protected void login() { System.out.println("码农只需要进行用户和密码验证就可以了"); } } package com.dp.example.templatemethod; /** * 军用计算机 * * @author mrsimple */ public class MilitaryComputer extends AbstractComputer { @Override protected void checkHardware() { super.checkHardware(); System.out.println("检查硬件防火墙"); } @Override protected void login() { System.out.println("进行指纹之别等复杂的用户验证"); } } package com.dp.example.templatemethod; public class Test { public static void main(String[] args) { AbstractComputer comp = new CoderComputer(); comp.startUp(); comp = new MilitaryComputer(); comp.startUp(); } } 总结模板方法模式用四个字概括就是:流程封装。父类提取公共代码,提高代码复用率,同时也带来了更好的可扩展性。优点:封装不变部分,扩展可变部分提取公共部分代码,便于维护缺点模板方法会带来代码阅读的难度,让用户觉得难以理解。","categories":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"keywords":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}]},{"title":"状态模式","slug":"设计模式/状态模式","date":"2017-10-13T06:00:16.000Z","updated":"2022-05-05T09:56:06.452Z","comments":true,"path":"posts/8acb1976.html","link":"","permalink":"https://blog.musiclake.cn/posts/8acb1976.html","excerpt":"","text":"状态模式状态模式中的行为是由状态来决定的,不同的状态下有不同的行为。状态模式和策略模式的机构几乎完全一样,但是它们的目的、本质却完全不一样。状态模式的行为是平行的、不可替代的,策略模式的行为是彼此独立、可相互替换的。用一句话来表述,状态模式把对象的行为包装在不同的状态对象里,每一个状态对象都有一个共同的抽象状态基类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。 定义当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。 使用场景1、一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。2、代码中包含大量与对象状态相关的条件语句,例如,一个操作类中含有庞大的多分支语句(if-else 或者switch-case),且这些分支依赖于该对象的状态。状态模式将每一个条件分支放入一个独立的类中,这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖与其他对象而独立变化,这样通过多态来去除过多的,重复的if-else等分支语句。 实战用户登录系统","categories":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"keywords":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}]},{"title":"组合模式","slug":"设计模式/组合模式","date":"2017-10-13T06:00:16.000Z","updated":"2022-05-05T09:56:06.452Z","comments":true,"path":"posts/df879792.html","link":"","permalink":"https://blog.musiclake.cn/posts/df879792.html","excerpt":"","text":"组合模式定义将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。 使用场景表示对象的部分-整体层次结构时。从一个整体中能独立出部分模块或功能的场景。 简单实现小结用到的比较少,一般更适用于对一些界面UI的架构设计上。优点组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让高层模块忽略了层次的差异,简化了高层模块的代码。缺点","categories":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"keywords":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}]},{"title":"访问者模式","slug":"设计模式/观察者模式","date":"2017-10-13T06:00:16.000Z","updated":"2022-05-05T09:56:06.452Z","comments":true,"path":"posts/64b51ed9.html","link":"","permalink":"https://blog.musiclake.cn/posts/64b51ed9.html","excerpt":"","text":"1.观察者模式(Observer Pattern)释义:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。故事理解:观察者想知道公司所有MM的情况,只要加入公司的MM情报邮件组就行了,tom负责搜集情报,当发现新情报时,不用一个一个通知我们,直接发布给邮件组,我们作为订阅者(观察者)就可以及时收到情报啦。常见实例:1.BaseAdapter.registerDataSetObserver和BaseAdapter.unregisterDataSetObserver两方法来向BaseAdater注册、注销一个DataSetObserver ; 2.使用ContentObserver去监听数据库变化。适用场景:1.当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变;2.当一个对象必须通知其它对象,而它又不能假定其它对象是谁. 观察者模式主要有观察者和被观察者2个对象,在该模式中,Observable表示被观察者,这个对象是一个抽象类,只能被继承。 Observer表示观察者,他是一个接口,所以观察者可以有多个,实现了该接口的类都是属于观察者。这是网上一个生动细致的demo:被观察者: MyPerson是被观察者,类中调用了setChange()以及notifyObservers()两个方法,前者是告知数据改变,后者是发送信号通知观察者。 观察者需要实现Observer接口,其中只有一个update方法,当观察者收到 (被观察者)的通知信号,就会执行该动作。","categories":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"keywords":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}]},{"title":"迭代器模式","slug":"设计模式/迭代器模式","date":"2017-10-13T06:00:16.000Z","updated":"2022-05-05T09:56:06.452Z","comments":true,"path":"posts/9055d217.html","link":"","permalink":"https://blog.musiclake.cn/posts/9055d217.html","excerpt":"","text":"","categories":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"keywords":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}]},{"title":"适配器模式","slug":"设计模式/适配器模式","date":"2017-10-13T06:00:16.000Z","updated":"2022-05-05T09:56:06.452Z","comments":true,"path":"posts/f5c535ea.html","link":"","permalink":"https://blog.musiclake.cn/posts/f5c535ea.html","excerpt":"","text":"前言说实话网上有很多介绍,那么为什么还要自己在写一遍呢?因为在网上看一遍容易忘掉,如果自己再重新写一遍,印象会深一点。 简介适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。","categories":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"keywords":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.musiclake.cn/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}]},{"title":"RecyclerView和ListView的区别","slug":"Android/RecyclerView和ListView的区别","date":"2016-10-20T08:21:17.000Z","updated":"2022-05-05T09:56:06.444Z","comments":true,"path":"posts/befc3031.html","link":"","permalink":"https://blog.musiclake.cn/posts/befc3031.html","excerpt":"RecyclerView是什么? RecyclerView is a more advanced and flexible version of ListView. Thiswidget is a container for large sets of views that can be recycled andscrolled very efficiently. Use the RecyclerView widget when you havelists with elements that change dynamically. RecyclerView是ListView的更高度定制版,当你需要高效的展示大量数据时候,动态改变元素的列表的时候,就用这个。 RecyclerView是support-v7包中新组件,官方介绍RecyclerView 是 ListView 的升级版本,更加先进和灵活,是一个强大的滑动组件,与传统的ListView相比较,他同样拥有item回收复用的功能,但是RecyclerView可以直接把ViewHolder的实现封装起来,用户只要实现自己的ViewHoler就可以了。RecyclerView新特性有:ViewHolder,ItemDecorator,LayoutManager,SmothScroller以及增加或删除item时item动画。","text":"RecyclerView是什么? RecyclerView is a more advanced and flexible version of ListView. Thiswidget is a container for large sets of views that can be recycled andscrolled very efficiently. Use the RecyclerView widget when you havelists with elements that change dynamically. RecyclerView是ListView的更高度定制版,当你需要高效的展示大量数据时候,动态改变元素的列表的时候,就用这个。 RecyclerView是support-v7包中新组件,官方介绍RecyclerView 是 ListView 的升级版本,更加先进和灵活,是一个强大的滑动组件,与传统的ListView相比较,他同样拥有item回收复用的功能,但是RecyclerView可以直接把ViewHolder的实现封装起来,用户只要实现自己的ViewHoler就可以了。RecyclerView新特性有:ViewHolder,ItemDecorator,LayoutManager,SmothScroller以及增加或删除item时item动画。 RecyclerView 用法mRecyclerView = findViewById(R.id.recyclerview); //设置布局管理器 mRecyclerView.setLayoutManager(layout); //设置adapter mRecyclerView.setAdapter(adapter) //设置Item增加、移除动画 mRecyclerView.setItemAnimator(new DefaultItemAnimator()); //添加分割线 mRecyclerView.addItemDecoration(new DividerItemDecoration( getActivity(), DividerItemDecoration.HORIZONTAL_LIST)); 1、将 layout 抽象成了一个 LayoutManager,RecylerView 不负责子 View 的布局, 我们可以自定义 LayoutManager 来实现不同的布局效果, 目前只提供了LinearLayoutManager。 LinearLayoutManager 可以指定方向,默认是垂直, 可以指定水平等,能支持线性布局、网格布局、瀑布流布局三种,而且同时还能够控制横向还是纵向滚动。 2、标准化了 ViewHolder, 编写 Adapter 面向的是 ViewHoder 而不在是View 了, 复用的逻辑被封装了, 写起来更加简单。3、局部刷新4、动画效果:增加或删除item时item动画 RecyclerView 相对于ListView的优缺点优点1、它封装了viewholder的回收复用。RecyclerView.ViewHolder2、RecyclerView使用布局管理器管理子view的位置(目前尚只提供了LinearLayoutManager),也就是说你再不用拘泥于ListView的线性展示方式,如果之后提供其他customLayoutManager的支持,你能够使用复杂的布局来展示一个动态组件。 StaggeredGridLayoutManager mStaggeredGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); //表示两列,并且是竖直方向的瀑布流 mRecyclerView.setLayoutManager(mStaggeredGridLayoutManager); 在布局上支持三种StaggeredGridLayoutManager, GridLayoutManager LinearLayoutManager。3、自带了ItemAnimation,可以设置加载和移除时的动画,方便做出各种动态浏览的效果。4、将ListView中的getView分成了onCreateViewHolder和onBindViewHolder两个方法,更利于代码的维护。5、相对简单我们看下Listview他背后的继承关系 public class ListView extends AbsListView public abstract class AbsListView extends AdapterView<ListAdapter> public abstract class AdapterView<T extends Adapter> extends ViewGroup 三重继承,内容还挺多的,不是直接继承ViewGroup,而相反的RecycleView却是直接继承自ViewGroup的。 缺点1、不能简单的加头和尾。2、不能简单的设置子item的点击事件。 解决方案:自定义接口实现点击事件。 其他ListView可以设置选择模式,并添加MultiChoiceModeListener listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); listView.setMultiChoiceModeListener(new MultiChoiceModeListener() { public boolean onCreateActionMode(ActionMode mode, Menu menu) { ... } public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { ... } public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case R.id.menu_item_delete_crime: CrimeAdapter adapter = (CrimeAdapter)getListAdapter(); CrimeLab crimeLab = CrimeLab.get(getActivity()); for (int i = adapter.getCount() - 1; i >= 0; i--) { if (getListView().isItemChecked(i)) { crimeLab.deleteCrime(adapter.getItem(i)); } } mode.finish(); adapter.notifyDataSetChanged(); return true; default: return false; } public boolean onPrepareActionMode(ActionMode mode, Menu menu) { ... } public void onDestroyActionMode(ActionMode mode) { ... } }); 参考链接:http://blog.csdn.net/sanjay_f/article/details/48830311","categories":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}],"tags":[{"name":"基础","slug":"基础","permalink":"https://blog.musiclake.cn/tags/%E5%9F%BA%E7%A1%80/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}]},{"title":"8-25","slug":"Android/sqlite","date":"2016-08-25T00:21:16.000Z","updated":"2022-05-05T09:56:06.444Z","comments":true,"path":"/post","link":"","permalink":"https://blog.musiclake.cn/post","excerpt":"前言 感觉每天都过得非常快,都没有认真总结所学的。俗话说,好记性不如烂笔头,所以想每天挤出一点时间来写点总结。","text":"前言 感觉每天都过得非常快,都没有认真总结所学的。俗话说,好记性不如烂笔头,所以想每天挤出一点时间来写点总结。 Android 中的数据库操作其实我们可以对数据进行的操作也就无非四种,即 CRUD。其中 C 代表添加 (Create), R代表查询(Retrieve), U代表更新(Update), D代表删除(Delete)。每一种操 作又各自对应了一种 SQL命令,如果你比较熟悉 SQL语言的话,一定会知道添加数据时使 用 insert,查询数据时使用 select,更新数据时使用 update,删除数据时使用 delete。(引用第一行代码的一句话)下面是以一个简单的数据表为例。music 表| id | title | artist || :—-: | :—-: | :—: || 12345 | 歌名 | 歌手 || 12346 | 歌名 | 歌手 | 查询操作query()方法 query()方法用于对数据进行查询。这个方法的参数非常复杂,最短的一个方法重载也需要传入七个参数。那我们就先来看一下 这七个参数各自的含义吧,第一个参数不用说,当然还是表名,表示我们希望从哪张表中查 询数据。第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列。第三、第四个 参数用于去约束查询某一行或某几行的数据,不指定则默认是查询所有行的数据。第五个参 数用于指定需要去 group by的列,不指定则表示不对查询结果进行 group by操作。第六个参 数用于对 group by之后的数据进行进一步的过滤,不指定则表示不进行过滤。第七个参数用 于指定查询结果的排序方式,不指定则表示使用默认的排序方式。 SQLiteDatabase db = dbHelper.getWritableDatabase(); // 查询music表中所有的数据 Cursor cursor = db.query("music", null, null, null, null, null, null); //再遍历游标cursor,获取数据库中的值 if(cursor.moveToFirst()) { ... } query参数详情 query()方法参数 对应 SQL部分 描述 table from table_name 指定查询的表名 columns select column1, column2 指定查询的列名 selection where column = value 指定 where的约束条件 selectionArgs - 为 where中的占位符提供具体的值 group By group by column 指定需要 group by的列 having where column = value 指定 where的约束条件 order By having column = value 对 group by后的结果进一步约束 select 查询语句使用sql语句查询 sql = "select * from table_name"; db.execSQL(sql); 过滤重复数据select distinct “字段名” from “表名” //方法一: sql = "select distinct title from music "; db.execSQL(sql); 插入操作insert()方法insert()方法是专门用于添加数据,有三个参数,第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字。第二个 参数用于在未指定添加数据的情况下给某些可为空的列自动赋值 NULL,一般我们用不到这 个功能,直接传入 null即可。第三个参数是一个 ContentValues对象,它提供了一系列的 put() 方法重载,用于向 ContentValues 中添加数据。 ContentValues values = new ContentValues(); // 开始组装第一条数据 values.put("title", "歌名1"); values.put("artist", "歌手1"); db.insert("music", null, values); 删除操作delete()方法delete()方法专门用于删除数据,这个方法接收三个参数,第一 个参数仍然是表名,这个已经没什么好说的了,第二、第三个参数又是用于去约束删除某一 行或某几行的数据,不指定的话默认就是删除所有行。 db.delete("music", "title = ?", new String[] { "歌名" }); 修改操作update()方法 update()方法用于对数据进行更新,这个方法 接收四个参数,第一个参数和 insert()方法一样,也是表名,在这里指定去更新哪张表里的数 据。第二个参数是 ContentValues 对象,要把更新数据在这里组装进去。第三、第四个参数 用于去约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。 values.put("artist","歌手2"); db.update("music", values, "id = ?", new String[] { "12345" }); 后记虽然现在有些流行开源的sql框架,但基础的sql还是需要的。持续更新中…","categories":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}],"tags":[{"name":"基础","slug":"基础","permalink":"https://blog.musiclake.cn/tags/%E5%9F%BA%E7%A1%80/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"https://blog.musiclake.cn/categories/Android/"}]},{"title":"音乐接口","slug":"api/music","date":"2016-08-25T00:21:16.000Z","updated":"2022-05-05T09:56:06.445Z","comments":true,"path":"posts/28ffda2f.html","link":"","permalink":"https://blog.musiclake.cn/posts/28ffda2f.html","excerpt":"","text":"有点想总结一下音乐接口, 歌曲来源:百度音乐详情请访问原文http://blog.csdn.net/xyw_blog/article/details/38641793?utm_source=tuicool&utm_medium=referral 获取百度音乐排行榜的数据http://musicapi.qianqian.com/v1/restserver/ting?from=android&version=6.0.7.1&channel=huwei&operator=1&method=baidu.ting.billboard.billCategory&format=json&kflag=2 获取频道列表数据http://fm.baidu.com/dev/api/?tn=channellist根据频道id获取频道音乐数据列表信息http://fm.baidu.com/dev/api/?tn=playlist&id=public_tuijian_rege根据songIds获取歌曲信息,如果要获取多首信息,包括歌曲下载链接地址,songIds之间用逗号隔开,如songIds = 913288,872633,3388338http://fm.baidu.com/data/music/songlink?songIds=913288根据songIds获取歌曲信息,如果要获取多首信息,songIds之间用逗号隔开,如songIds = 913288,872633,3388338 http://fm.baidu.com/data/music/songinfo?songIds=913288 歌曲来源:QQ音乐来源于github 开源库MusicAPI 歌曲来源:虾米音乐来源于github 开源库MusicAPI 歌曲来源:网易云音乐来源于github 开源库NeteaseCloudMusicApi 备注因为这些音乐接口都是加密的,而这些开源库是使用py或者js的一些解密算法解密,所有并不能直接调用这些音乐接口,还得解密。 计划kotlin解析音乐API,生成一个库文件","categories":[{"name":"接口","slug":"接口","permalink":"https://blog.musiclake.cn/categories/%E6%8E%A5%E5%8F%A3/"}],"tags":[{"name":"api","slug":"api","permalink":"https://blog.musiclake.cn/tags/api/"}],"keywords":[{"name":"接口","slug":"接口","permalink":"https://blog.musiclake.cn/categories/%E6%8E%A5%E5%8F%A3/"}]},{"title":"Youtube api","slug":"api/youtubu","date":"2016-08-25T00:21:16.000Z","updated":"2022-05-05T09:56:06.446Z","comments":true,"path":"posts/827e1383.html","link":"","permalink":"https://blog.musiclake.cn/posts/827e1383.html","excerpt":"","text":"YouTube Data API 分析最近在对Youtube的开放api进行一些调研和分析,为了有了个更深的印象,所以总结一下,写得不好,欢迎指正! 对于获取数据有两种方式 自己拼接请求地址,然后请求数据, 使用官方的jar,来实现参考文档","categories":[{"name":"接口","slug":"接口","permalink":"https://blog.musiclake.cn/categories/%E6%8E%A5%E5%8F%A3/"}],"tags":[{"name":"api","slug":"api","permalink":"https://blog.musiclake.cn/tags/api/"}],"keywords":[{"name":"接口","slug":"接口","permalink":"https://blog.musiclake.cn/categories/%E6%8E%A5%E5%8F%A3/"}]},{"title":"爱壁纸音乐api","slug":"api/wallpaper","date":"2016-08-25T00:21:16.000Z","updated":"2022-05-05T09:56:06.446Z","comments":true,"path":"posts/f296039d.html","link":"","permalink":"https://blog.musiclake.cn/posts/f296039d.html","excerpt":"","text":"前言通过fildder抓取爱壁纸app的图片接口 limit:10 //壁纸数量, order:hot //排序方式 skip:1 //偏移量 //参数集合 var map: Map<String, Any> = mapOf("limit" to limit, "skip" to skip, "order" to order, "adult" to "false", "first" to "0") 主页获取壁纸图片 GET方式获取http://service.aibizhi.adesk.com/v3/homepage 例如:http://service.aibizhi.adesk.com/v3/homepage?limit=30&adult=false&did=867919026491418&first=1&order=hot 最新http://service.aibizhi.adesk.com/v1/vertical/vertical?limit=30&adult=false&first=1&order=new albumhttp://service.aibizhi.adesk.com/v1/wallpaper/album/50b2e4de0a2ae035e5d7139b/wallpaper?limit=30&adult=false&first=1&order=new 热门http://service.aibizhi.adesk.com/v3/wallpaper?limit=30&adult=false&first=1&order=hot 分类http://service.aibizhi.adesk.com/v1/wallpaper/category?adult=false&first=1 http://service.aibizhi.adesk.com/v1/vertical/category/4e4d610cdf714d2966000000/vertical?limit=10&adult=false&first=1&order=new 返回的数据类型如下 { "msg": "success", "res": { "ads": [], "vertical": [ { "views": 0, "ncos": 0, "rank": 6, "tag": [], "wp": "http://img.aibizhi.adesk.com/5acb3d44e7bce73542df002d", "xr": false, "cr": false, "favs": 6, "atime": 1524575403, "id": "5acb3d44e7bce73542df002d", "desc": "", "thumb": "http://img.aibizhi.adesk.com/5acb3d44e7bce73542df002d?imageMogr2/thumbnail/!350x540r/gravity/Center/crop/350x540", "img": "http://img.aibizhi.adesk.com/5acb3d44e7bce73542df002d?imageMogr2/thumbnail/!720x1280r/gravity/Center/crop/720x1280", "cid": [ "4e4d610cdf714d2966000002" ], "url": [], "rule": "?imageMogr2/thumbnail/!$<Width>x$<Height>r/gravity/Center/crop/$<Width>x$<Height>", "preview": "http://img.aibizhi.adesk.com/5acb3d44e7bce73542df002d", "store": "qiniu" } ] }, "code": 0 } 具体实现效果见WallpaperAPP","categories":[{"name":"接口","slug":"接口","permalink":"https://blog.musiclake.cn/categories/%E6%8E%A5%E5%8F%A3/"}],"tags":[{"name":"api","slug":"api","permalink":"https://blog.musiclake.cn/tags/api/"}],"keywords":[{"name":"接口","slug":"接口","permalink":"https://blog.musiclake.cn/categories/%E6%8E%A5%E5%8F%A3/"}]},{"title":"hexo常用命令","slug":"tools/hexo","date":"2016-05-29T05:49:34.000Z","updated":"2022-05-05T09:56:06.448Z","comments":true,"path":"posts/33f59116.html","link":"","permalink":"https://blog.musiclake.cn/posts/33f59116.html","excerpt":"","text":"hexo常用命令使用hexo+github搭建属于自己的博客 1、新建hexo new "my blog" hexo n "my blog" #缩写 新建一篇文章,文章标题如果存在空格,需要使用双引号,新建的文件在 hexo/source/_posts/my-blog.md 2、编译hexo generate hexo g #缩写 一般部署上去的时候都需要编译一下,编译后,会出现一个 public 文件夹,将所有的md文件编译成html文件 3、开启本地服务hexo server hexo s #缩写 开启本地hexo服务,访问 localhost:4000 4、部署hexo deploy hexo d #缩写 部署到git上的时候,需要用这个命令 5、清除hexo clean hexo c #缩写 当 source 文件夹中的部分资源更改过之后,特别是对文件进行了删除或者路径的改变之后,需要执行这个命令,然后重新编译。 6、常用总结hexo n #写文章 hexo g #生成 hexo s #开启本地服务 hexo d #部署 #可与hexo g合并为 hexo d -g 以上都是一些常用的命令,命令详细请访问官网 indigo样式修改修改文章页面卡片宽 将 contentWidth的原始值960px改为90% ../themes/indigo/source/css/_partial/variable.less","categories":[{"name":"博客","slug":"博客","permalink":"https://blog.musiclake.cn/categories/%E5%8D%9A%E5%AE%A2/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"https://blog.musiclake.cn/tags/hexo/"}],"keywords":[{"name":"博客","slug":"博客","permalink":"https://blog.musiclake.cn/categories/%E5%8D%9A%E5%AE%A2/"}]}]}