├── .gitignore
├── AdavancedPart
├── 1.热修复实现(一).md
├── 2.热修复实现(二).md
├── 3.热修复_addAssetPath不同版本区别原因(三).md
├── AOP.md
├── ARouter.md
├── Android WorkManager.md
├── Android6.0权限系统.md
├── AndroidRuntime_ART与Dalvik.md
├── Android卸载反馈.md
├── Android启动模式详解.md
├── Android开发不申请权限来使用对应功能.md
├── Android开发中的MVP模式详解.md
├── ApplicationId vs PackageName.md
├── BroadcastReceiver安全问题.md
├── ConstraintLaayout简介.md
├── Crash及ANR分析.md
├── Java
├── Library项目中资源id使用case时报错.md
├── Mac下配置adb及Android命令.md
├── OOM问题分析.md
├── RecyclerView专题.md
├── 如何让Service常驻内存.md
├── 屏幕适配之百分比方案详解.md
├── 布局优化.md
├── 性能优化.md
├── 注解使用.md
└── 通过Hardware Layer提高动画性能.md
├── AndroidStudioCourse
├── Android Studio你可能不知道的操作.md
├── AndroidStudio中进行ndk开发.md
├── AndroidStudio使用教程(第一弹).md
├── AndroidStudio使用教程(第七弹).md
├── AndroidStudio使用教程(第三弹).md
├── AndroidStudio使用教程(第二弹).md
├── AndroidStudio使用教程(第五弹).md
├── AndroidStudio使用教程(第六弹).md
├── AndroidStudio使用教程(第四弹).md
└── AndroidStudio提高Build速度.md
├── AppPublish
├── Android应用发布.md
├── Zipalign优化.md
└── 使用Jenkins实现自动化打包.md
├── Architect
├── 1.架构简介.md
└── 2.UML简介.md
├── BasicKnowledge
├── Android入门介绍.md
├── Android动画.md
├── Android四大组件之ContentProvider.md
├── Android四大组件之Service.md
├── Android基础面试题.md
├── Android编码规范.md
├── Ant打包.md
├── Bitmap优化.md
├── Fragment专题.md
├── Home键监听.md
├── HttpClient执行Get和Post请求.md
├── JNI_C语言基础.md
├── JNI基础.md
├── ListView专题.md
├── Parcelable及Serializable.md
├── PopupWindow细节.md
├── SDK Manager无法更新的问题.md
├── Scroller简介.md
├── ScrollingTabs.md
├── Selector使用.md
├── SlidingMenu.md
├── String格式化.md
├── TextView跑马灯效果.md
├── WebView总结.md
├── Widget(窗口小部件).md
├── Wifi状态监听.md
├── XmlPullParser.md
├── adb logcat使用简介.md
├── 下拉刷新ListView.md
├── 代码混淆.md
├── 任务管理器(ActivityManager).md
├── 修改系统组件样式.md
├── 内存泄漏.md
├── 反编译.md
├── 多线程断点下载.md
├── 安全退出应用程序.md
├── 屏幕适配.md
├── 应用后台唤醒后数据的刷新.md
├── 应用安装.md
├── 开发中Log的管理.md
├── 开发中异常的处理.md
├── 快捷方式工具类.md
├── 手机摇晃.md
├── 搜索框.md
├── 数据存储.md
├── 文件上传.md
├── 来电号码归属地提示框.md
├── 来电监听及录音.md
├── 横向ListView.md
├── 滑动切换Activity(GestureDetector).md
├── 病毒.md
├── 知识大杂烩.md
├── 短信广播接收者.md
├── 程序的启动、卸载和分享.md
├── 竖着的Seekbar.md
├── 自定义Toast.md
├── 自定义控件.md
├── 自定义状态栏通知.md
├── 自定义背景.md
├── 获取位置(LocationManager).md
├── 获取应用程序缓存及一键清理.md
├── 获取手机中所有安装的程序.md
├── 获取手机及SD卡可用存储空间.md
├── 获取联系人.md
├── 读取用户logcat日志.md
├── 资源文件拷贝的三种方式.md
├── 超级管理员(DevicePoliceManager).md
├── 锁屏以及解锁监听.md
├── 零权限上传数据.md
├── 音量及屏幕亮度调节.md
└── 黑名单挂断电话及删除电话记录.md
├── Dagger2
├── 1.Dagger2简介(一).md
├── 2.Dagger2入门demo(二).md
├── 3.Dagger2入门demo扩展(三).md
├── 4.Dagger2单例(四).md
├── 5.Dagger2Lay和Provider(五).md
├── 6.Dagger2Android示例代码(六).md
├── 7.Dagger2之dagger-android(七).md
├── 8.Dagger2与MVP(八).md
└── 9.Dagger2原理分析(九).md
├── Gradle&Maven
├── Composing builds简介.md
├── Gradle专题.md
├── duplicate class冲突解决.md
├── kts.md
└── 发布library到Maven仓库.md
├── ImageLoaderLibrary
├── Coil简介.md
├── Glide简介(上).md
├── Glide简介(下).md
└── 图片加载库比较.md
├── JavaKnowledge
├── Base64加密.md
├── Git简介.md
├── HashMap实现原理分析.md
├── Http与Https的区别.md
├── JVM垃圾回收机制.md
├── JVM架构.md
├── Java内存模型.md
├── Java基础面试题.md
├── Java并发编程之原子性、可见性以及有序性.md
├── MD5加密.md
├── MVC与MVP及MVVM.md
├── RMB大小写转换.md
├── Top-K问题.md
├── UML类图.pdf
├── Vim使用教程.md
├── hashCode与equals.md
├── python3入门.md
├── shell.md
├── volatile和Synchronized区别.md
├── 剑指Offer(上).md
├── 剑指Offer(下).md
├── 动态代理.md
├── 单例的最佳实现方式.md
├── 常用命令行大全.md
├── 强引用、软引用、弱引用、虚引用.md
├── 数据加密及解密.md
├── 数据结构和算法
│ ├── 1. LeetCode_两数之和.md
│ ├── 2. LeetCode_两数相加.md
│ ├── 3. LeetCode_无重复字符的最长子串.md
│ ├── 八种排序算法.md
│ ├── 数据结构.md
│ └── 算法.md
├── 死锁.md
├── 生产者消费者.md
├── 线程池简介.md
├── 网络请求相关内容总结.md
├── 获取今后多少天后的日期.md
└── 设计模式.md
├── Jetpack
├── Jetpack简介.md
├── architecture
│ ├── 1.简介.md
│ ├── 10.DataStore简介.md
│ ├── 11.Hilt简介.md
│ ├── 12.Navigation简介.md
│ ├── 13.Jetpack MVVM简介.md
│ ├── 14.findViewById的过去及未来.md
│ ├── 2.ViewBinding简介.md
│ ├── 3.Lifecycle简介.md
│ ├── 4.ViewModel简介.md
│ ├── 5.LiveData简介.md
│ ├── 6.DataBinding简介.md
│ ├── 7.Room简介.md
│ ├── 8.PagingLibrary简介.md
│ └── 9.App Startup简介.md
├── behavior
│ └── 1.简介.md
├── foundation
│ └── 1.简介.md
└── ui
│ ├── Jetpack Compose_Modifier.md
│ ├── Jetpack Compose简介.md
│ ├── Jetpack Compose组件.md
│ └── material
│ ├── 1.MaterialToolbar简介.md
│ ├── 2.NavigationView简介.md
│ ├── 3.NestedScrollView简介.md
│ ├── 4.CoordinatorLayout简介.md
│ ├── 5.AppBarLayout简介.md
│ ├── 6.CollapsingToolbarLayout简介.md
│ ├── 7.Snackbar简介.md
│ ├── 8.TabLayout简介.md
│ └── 9.BottomNavigation简介.md
├── KotlinCourse
├── 1.Kotlin_简介&变量&类&接口.md
├── 10.Kotlin_设计模式.md
├── 2.Kotlin_高阶函数&Lambda&内联函数.md
├── 3.Kotlin_数字&字符串&数组&集合.md
├── 4.Kotlin_表达式&关键字.md
├── 5.Kotlin_内部类&密封类&枚举&委托.md
├── 6.Kotlin_多继承问题.md
├── 7.Kotlin_注解&反射&扩展.md
├── 8.Kotlin_协程.md
└── 9.Kotlin_androidktx.md
├── MobileAIModel
└── TensorFlow Lite
├── OperatingSystem
├── 1.操作系统简介.md
├── 2.进程与线程.md
├── 3.内存管理.md
├── 4.调度.md
├── 5.IO.md
├── 6.文件管理.md
├── 7.嵌入式系统.md
├── 8.虚拟机.md
└── AndroidKernal
│ ├── 1.Android进程间通信.md
│ ├── 2.Android线程间通信之Handler消息机制.md
│ ├── 3.Android Framework框架.md
│ ├── 4.ActivityManagerService简介.md
│ ├── 5.Android消息获取.md
│ ├── 6.屏幕绘制基础.md
│ ├── 7.View绘制原理.md
│ ├── 8.WindowManagerService简介.md
│ └── 9.PackageManagerService简介.md
├── README.md
├── RxJavaPart
├── 1.RxJava详解(一).md
├── 2.RxJava详解(二).md
├── 3.RxJava详解(三).md
├── 4.RxJava详解之执行原理(四).md
├── 5.RxJava详解之操作符执行原理(五).md
├── 6.RxJava详解之线程调度原理(六).md
└── 7.RxJava系列全家桶.md
├── SourceAnalysis
├── ARouter解析.md
├── Activity启动过程.md
├── Activity界面绘制过程详解.md
├── Android Touch事件分发详解.md
├── AsyncTask详解.md
├── InstantRun详解.md
├── LeakCanary源码分析.md
├── ListView源码分析.md
├── Netowork
│ ├── HttpURLConnection与HttpClient.md
│ ├── HttpURLConnection详解.md
│ ├── Retrofit详解(上).md
│ ├── Retrofit详解(下).md
│ ├── Volley源码分析.md
│ └── volley-retrofit-okhttp之我们该如何选择网路框架.md
├── VideoView源码分析.md
├── View绘制过程详解.md
├── butterknife源码详解.md
└── 自定义View详解.md
├── Tools&Library
├── Android开发工具及类库.md
├── Github个人主页绑定域名.md
├── Icon制作.md
├── MAT内存分析.md
├── Markdown学习手册.md
├── 性能优化相关工具.md
├── 目前流行的开发组合.md
└── 调试平台Flipper.md
└── VideoDevelopment
├── Android音视频开发
├── 1.音视频基础知识.md
├── 11.播放组件封装.md
├── 2.系统播放器MediaPlayer.md
├── Android WebRTC简介.md
├── AudioTrack简介.md
├── CameraX结合OpenGL.md
├── DLNA简介.md
├── MediaExtractor、MediaCodec、MediaMuxer.md
├── MediaMetadataRetriever.md
├── SurfaceView与TextureView.md
├── 播放器性能优化.md
├── 视频解码之软解与硬解.md
├── 音视频同步原理.md
└── 音视频场景.md
├── CDN及PCDN.md
├── DNS及HTTPDNS.md
├── Danmaku
└── Android弹幕实现.md
├── ExoPlayer
├── 1. ExoPlayer简介.md
├── 2. ExoPlayer MediaSource简介.md
├── 3. ExoPlayer源码分析之prepare方法.md
├── 4. ExoPlayer源码分析之prepare序列图.md
└── 5. ExoPlayer源码分析之PlayerView.md
├── FFmpeg
├── 1.FFmpeg简介.md
├── 2.FFmpeg常用命令行.md
├── 3.FFmpeg切片.md
├── 4.开发环境配置.md
├── 5.FFmpeg核心功能.md
└── 6.视频播放简介.md
├── OpenCV
├── 1.OpenCV简介.md
└── 2.绘制图形.md
├── OpenGL
├── 1.OpenGL简介.md
├── 10.GLSurfaceView+MediaPlayer播放视频.md
├── 11.OpenGL ES滤镜.md
├── 12.FBO.md
├── 13.LUT滤镜.md
├── 14.实例化.md
├── 15.美颜滤镜.md
├── 16.色温、色调、饱和度.md
├── 17.EGL和GL线程.md
├── 18.其他.md
├── 2.GLSurfaceView简介.md
├── 3.GLSurfaceView源码解析.md
├── 4.GLTextureView实现.md
├── 5.OpenGL ES绘制三角形.md
├── 6.OpenGL ES绘制矩形及圆形.md
├── 7.OpenGL ES着色器语言GLSL.md
├── 8.GLES类及Matrix类.md
└── 9.OpenGL ES纹理.md
├── P2P技术
├── P2P.md
└── P2P原理_NAT穿透.md
├── WebRTC.md
├── 关键帧.md
├── 搭建nginx+rtmp服务器.md
├── 流媒体协议
├── DASH.md
├── HLS.md
├── HTTP FLV.md
├── RTMP.md
└── 流媒体协议.md
├── 视频封装格式
├── AVI.md
├── FLV.md
├── M3U8.md
├── MP4格式详解.md
├── TS.md
├── fMP4 vs ts.md
├── fMP4格式详解.md
└── 视频封装格式.md
├── 视频播放相关内容总结.md
├── 视频编码
├── AV1.md
├── H264.md
├── H265.md
└── 视频编码原理.md
└── 音频编码
├── AAC.md
├── PCM.md
├── WAV.md
└── 音频编码格式.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /.project
2 | /.DS_Store
3 | /.idea
4 |
--------------------------------------------------------------------------------
/AdavancedPart/AOP.md:
--------------------------------------------------------------------------------
1 | AOP
2 | ---
3 |
4 |
5 | AOP(Aspect Oriented Programing),面向切面编程。
6 | 是OOP(Object Oriented Programing)面向对象编程的延续。
7 |
8 | 在OOP思想中,我们会把问题划分为各个模块,如语言、表情等。
9 | 在划分这些模块的过程中,也会出现一些共同特征(如埋点)。它的逻辑被分散到了各个模块,导致了代码复杂度提高,可复用性降低。
10 |
11 | 而AOP,就是将各个模块中的通用逻辑抽离出来。
12 | 我们将这些逻辑视为Aspect(切面),然后动态地把代码插入到类的指定方法、指定位置中。
13 |
14 | 一句话概括: 在运行时,动态的将代码切入到类的指定方法、指定位置上的编程思想就是面相切面的编程。
15 |
16 |
17 | 一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。
18 |
19 |
20 | ### AOP的实现方式
21 |
22 | #### 静态AOP
23 |
24 | 在编译器,切面直接以字节码的形式编译到目标字节码文件中。
25 |
26 | 1. AspectJ
27 | AspectJ属于静态AOP,它是在编译时进行增强,会在编译时期将AOP逻辑织入到代码中。
28 |
29 | 由于是在编译器织入,所以它的优点是不影响运行时性能,缺点是不够灵活。
30 |
31 | 2. AbstractProcessor
32 | 自定义一个AbstractProcessor,在编译期去解析编译的类,并且根据需求生成一个实现了特定接口的子类(代理类)
33 |
34 | #### 动态AOP
35 | 1. JDK动态代理
36 | 通过实现InvocationHandler接口,可以实现对一个类的动态代理,通过动态代理可以生成代理类,从而在代理类方法中,在执行被代理类方法前后,添加自己的实现内容,从而实现AOP。
37 |
38 | 2. 动态字节码生成
39 | 在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中,没有接口也可以织入,但扩展类的实例方法为final时,则无法进行织入。比如Cglib
40 |
41 | CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。
42 |
43 | 3. 自定义类加载器
44 | 在运行期,目标加载前,将切面逻辑加到目标字节码里。如:Javassist
45 |
46 | Javassist是可以动态编辑Java字节码的类库。它可以在Java程序运行时定义一个新的类,并加载到JVM中;还可以在JVM加载时修改一个类文件。
47 |
48 | 4. ASM
49 | ASM可以在编译期直接修改编译出的字节码文件,也可以像Javassit一样,在运行期,类文件加载前,去修改字节码。
50 |
51 |
52 |
--------------------------------------------------------------------------------
/AdavancedPart/ARouter.md:
--------------------------------------------------------------------------------
1 | ARouter
2 | ---
3 |
4 |
5 | [ARouter](https://github.com/alibaba/ARouter)
6 | 一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦
7 |
8 |
9 |
10 |
11 | ```java
12 | Intent intent = new Intent(mContext, XxxActivity.class);
13 | intent.putExtra("key","value");
14 | startActivity(intent);
15 |
16 | Intent intent = new Intent(mContext, XxxActivity.class);
17 | intent.putExtra("key","value");
18 | startActivityForResult(intent, 666);
19 | ```
20 | 在未使用ARouter路由框架之前的原生页面跳转方式。
21 |
22 |
23 | ## 原生路由方案的缺点
24 |
25 | 1. 显式: 直接的类依赖,耦合严重
26 | 2. 隐式: 会在Manifest文件中进行集中管理,写作困难
27 |
28 |
29 | ## ARouter的优势
30 |
31 | 1. 使用注解,实现了映射关系自动注册与分布式路由管理
32 | 2. 编译期间处理注解,并生成映射文件,没有使用反射,不影响运行时性能
33 | 3. 映射关系按组分类,分级管理,按需初始化。
34 | 4. 灵活的降级策略,每次跳转都会回调跳转结果,避免startActivity()一旦失败会抛出异常
35 | 5. 自定义拦截器,自定义拦截顺序,可以对路由进行拦截,比如登录判断和埋点处理
36 | 6. 支持依赖注入,可单独作为依赖注入框架使用,从而实现跨模块API调用
37 | 7. 支持直接解析标准url进行跳转,并自动注入参数到目标页面中
38 | 8. 支持多模块使用,支持组件化开发
39 |
40 |
41 |
42 | ## 基本使用
43 |
44 | 添加依赖并初始化后的基本使用:
45 |
46 | ```java
47 | // 在支持路由的页面上添加注解(必选)
48 | // 这里的路径需要注意的是至少需要有两级,/xx/xx
49 | @Route(path = "/test/activity")
50 | public class YourActivity extend Activity {
51 | ...
52 | }
53 |
54 | // 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)
55 | ARouter.getInstance().build("/test/activity").navigation();
56 |
57 | // 2. 跳转并携带参数
58 | ARouter.getInstance().build("/test/1")
59 | .withLong("key1", 666L)
60 | .withString("key3", "888")
61 | .withObject("key4", new Test("Jack", "Rose"))
62 | .navigation();
63 | ```
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/AdavancedPart/Android开发不申请权限来使用对应功能.md:
--------------------------------------------------------------------------------
1 | Android开发不申请权限来使用对应功能
2 | ===
3 |
4 | 从用户角度来说很难获取到正确的`android`权限。通常你只需要做一些很基础的事(例如编辑一个联系人)但实际你申请的权限却远远比这更强大(例如可以获取到所有的联系人明细等)。
5 |
6 | 这样能很容易的理解到用户会怀疑到你的应用。如果你的应用不是开源的,那他们就没有方法来验证你会不会下载所有的联系人数据并上传到服务器。及时你去解释为什么需要这个权限,但是人们不会相信你。原来我会选择不去使用这些敏感的权限来防止用户产生不信任。(当然很多应用他们申请权限只是为了后台获取你的联系人数据上传- -!以及之前被爆的某宝使用摄像头拍照的问题)
7 |
8 | 这就是说,有一件事在困扰着我,**如何能在做一些操作时不去申请权限。**
9 |
10 | 打个比方说:`android.permission.CALL_PHONE`这个权限。你需要它来在应用中拨打电话,是吗?这就是你怎么去实现拨号的吗?
11 | ```java
12 | Intent intent = new Intent(Intent.ACTION_CALL);
13 | intent.setData(Uri.parse("tel:1234567890"))
14 | startActivity(intent);
15 | ```
16 | 错!,你通过这段代码需要该权限的原因是因为你可以在任何时间在不需要用户操作的情况下打电话。也就是说如果我的应用申请了这个权限,我可以在你不知情的情况下每天凌晨三点去拨打骚扰电话。
17 |
18 | 正确的方式是使用`ACTION_VIEW`或者`ACTION_DIAL`:
19 | ```java
20 | Intent intent = new Intent(Intent.ACTION_DIAL);
21 | intent.setData(Uri.parse("tel:1234567890"))
22 | startActivity(intent);
23 | ```
24 |
25 | 这个问题的完美解决方案就是不需要申请权限了。原因就是你不是直接拨号,而是用指定的号码调起拨号器,仍然需要用户点击”拨号”来开始打电话。老实的说,这样让人感觉更好。
26 |
27 | 简单的说就是如果我想要的操作不是让用户在应用内点击某个按钮就直接开始拨打电话,而是让用户点击在应用内点击某个按钮是我们去调起拨号程序,并且显示指定号码,让用户在拨号器中点击拨号后再开始拨打电话。这样的话我们就完全不用申请拨号权限了。
28 |
29 | 另一个例子: 我想获取某一个联系人的号码,你可能会想这需要申请获取所有联系人的权限。这是错的!。
30 | ```java
31 | Intent intent = new Intent(Intent.ACTION_PICK);
32 | intent.setType(StructuredPostal.CONTENT_TYPE);
33 | startActivityForResult(intent, 1);
34 | ```
35 | 我们可以使用上面的代码,来启动联系人管理器,让用户来选择某一个联系人。这样不仅是不需要申请任何权限,也不需要提供任何联系人相关的`UI`。这样也能完全保证你选择联系人时的体验。
36 |
37 |
38 | `Android`系统最酷的部分之一就是`Intent`系统,这意味着我不需要自己来实现所有的东西。应用可以注册处理它所擅长的指定数据,像电话号码、短信或者联系人。如果这些都要自己在一个应用中去实现,那这将会是很大的工作量,也会让应用变得臃肿。
39 |
40 | `Android`系统的另一个优势就是你可以使用其他应用申请的权限,而不用自己申请。这样才保证了上面的情况。拨号器需要申请拨打电话的权限,我只需要一个能调起拨号器的`Intent`就好了。用户信任拨号器拨打电话,而不是我们的应用。他们无论如何都宁愿使用系统的拨号器。
41 |
42 | 写这篇文章的意义是**在你想要申请一个权限的时候,你需要至少看看[Intent的官方文档](https://developer.android.com/reference/android/content/Intent.html)看能否请求另外一个应用来帮我们做这些操作。**如果想要深入的研究,可以学习下[关于权限的详细介绍](https://developer.android.com/guide/topics/security/permissions.html),这里面包含了很多精细的权限。
43 |
44 | 使用更少的权限可以不但可以让用户更加信任你,而且可以让用户有一个更好的体验,因为他们仍然在使用他们所期望的应用。
45 |
46 | 1. 遗憾的是,不是一个真实的号码。
47 | 2. 不幸的是,`Intent`系统的属性也建立了[可能会被滥用的漏洞](http://css.csail.mit.edu/6.858/2012/projects/ocderby-dennisw-kcasteel.pdf),但你也不会写一个滥用的应用,是吗?
48 |
49 |
50 | - (译)[感谢Dan Lew](http://blog.danlew.net/2014/11/26/i-dont-need-your-permission/)
51 |
52 |
53 | ---
54 |
55 | - 邮箱 :charon.chui@gmail.com
56 | - Good Luck!
--------------------------------------------------------------------------------
/AdavancedPart/ApplicationId vs PackageName.md:
--------------------------------------------------------------------------------
1 | ApplicationId vs PackageName
2 | ===
3 |
4 | 曾几何时,自从转入`Studio`阵营后就发现多了个`applicationId "com.xx.xxx"`,虽然知道肯定会有区别,但是我却没有仔细去看,只想着把它和`packageName`设置成相同即可(原谅我的懒惰- -!)。
5 |
6 | 直到今天在官网看`Gradle`使用时,终于忍不住要搞明白它俩的区别。
7 |
8 | 在`Android`官方文档中有一句是这样描述`applicationId`的:`applicationId : the effective packageName`,真是言简意赅,那既然`applicationId`是有效的包明了,`packageName`算啥?
9 |
10 | 所有`Android`应用都有一个包名。包名在设备上能唯一的标识一个应用,它在`Google Play`应用商店中也是唯一的。这就意味着一旦你使用一个包名发布应用后,你就永 远不能改变它的包名;如果你改了包名就会导致你的应用被认为是一个新的应用,并且已经使用你之前应用的用户将不会看到作为更新的新应用包。
11 |
12 | 之前的`Android Gradle`构建系统中,应用的包名是由你的`manifest`文件中的根元素中的`package`属性定义的:
13 |
14 | `AndroidManifest.xml: `
15 |
16 | ```
17 |
18 |
22 | ```
23 | 然而,这里定义的包也有第二个目的:就是被用来命名你的`R`资源类(以及解析任何与`Activities`相关的类名)的包。在上面的示例中,生成的`R`类就是`com.example.my.app.R`,所以如果你在其他的包中想引用资源,就需要导入`com.example.my.app.R`。
24 |
25 | 伴随着新的`Android Gradle`构建系统,你可以很简单的为你的应用构建多个不同的版本;例如,你可以同时为你的应用构建一个免费版本和一个专业版(使用`flavors`),并且他们应该在`Google Play`商店中有不同的包,这样才能让他们可以被单独安装和购买,同时安装两个,等等。同样的你也可能同时为你的应用构建`debug`版、`alpha`版和`beta`版(使用`build types`),这些也可以同样使用不同的包名。
26 |
27 | 在这同时,你在代码中导入的`R`类必须一直保持一直;在为应用构建不同的版本时`.java`源文件都不应该发生变化。
28 |
29 | 因此,我们解耦了`package name`的两种用法:
30 |
31 | - 在生成的`.apk`中的`manifest`文件中使用的最终的包名以及在你的设备和`Google Play`商店中用来标示你的包名叫做`application id`的值。
32 | - 在源代码中指向`R`类的包名以及在解析任何与`activity/service`注册相关的包名继续叫做`package name`。
33 |
34 | 可以在`gradle`文件中像如下指定`application id`:
35 |
36 | `app/build.gradle:`
37 |
38 | ```
39 | apply plugin: 'com.android.application'
40 |
41 | android {
42 | compileSdkVersion 19
43 | buildToolsVersion "19.1"
44 |
45 | defaultConfig {
46 | applicationId "com.example.my.app"
47 | minSdkVersion 15
48 | targetSdkVersion 19
49 | versionCode 1
50 | versionName "1.0"
51 | }
52 | ...
53 | ```
54 |
55 | 像之前一样,你需要在`Manifest`文件中指定你在代码中使用的`package name`,像上面`AndroidManifest.xml`的例子。
56 | 下面进入关键部分了:当你按照上面的方式做完后,这两个包就是相互独立的了。你现在可以很简单的重构你的代码-通过修改`Manifest`中的包名来修改在你的`activitise`和`services`中使用的包和在重构你在代码中的引用声明。这不会影响你应用的最终`id`,也就是在`Gradle`文件中的`applicationId`。
57 |
58 | 你可以通过以下`Gradle DSL`方法为应用的`flavors`和`build types`指定不同的`applicationId`:
59 |
60 | `app/buid.gradle: `
61 |
62 | ```
63 | productFlavors {
64 | pro {
65 | applicationId = "com.example.my.pkg.pro"
66 | }
67 | free {
68 | applicationId = "com.example.my.pkg.free"
69 | }
70 | }
71 |
72 | buildTypes {
73 | debug {
74 | applicationIdSuffix ".debug"
75 | }
76 | }
77 | ....
78 | ```
79 | (在`Android Studio`中你也可以通过图形化的`Project Structure`的对话框来更改上面所有的配置)
80 |
81 | 注意:为了兼容性,如果你在`build.gradle`文件中没有定义`applicationId` ,那`applicationId`就是与`AndroidManifest.xml`中配置的包名相同的默认值。在这种情况下,这两者显然脱不了干系,如果你试图重构代码中的包就将会导致同时会改变你应用程序的`id`!在`Android Studio`中新创建的项目都是同时指定他们俩。
82 |
83 | 注意2:`package name`必须在默认的`AndroidManifest.xml`文件中指定。如果有多个`manifest`文件(例如对每个`flavor`制定一个`manifest`或者每个`build type`制定一个`manifest`)时,`package name`是可选的,但是如果你指定的话,它必须与主`manifest`中指定的`pakcage`相同。
84 |
85 |
86 | ---
87 |
88 | - 邮箱 :charon.chui@gmail.com
89 | - Good Luck!
90 |
--------------------------------------------------------------------------------
/AdavancedPart/BroadcastReceiver安全问题.md:
--------------------------------------------------------------------------------
1 | BroadcastReceiver安全问题
2 | ===
3 |
4 | `BroadcastReceiver`设计的初衷是从全局考虑可以方便应用程序和系统、应用程序之间、应用程序内的通信,所以对单个应用程序而言`BroadcastReceiver`是存在安全性问题的(恶意程序脚本不断的去发送你所接收的广播)
5 | - 保证发送的广播要发送给指定的对象
6 | 当应用程序发送某个广播时系统会将发送的`Intent`与系统中所有注册的`BroadcastReceiver`的`IntentFilter`进行匹配,若匹配成功则执行相应的`onReceive`函数。可以通过类似`sendBroadcast(Intent, String)`的接口在发送广播时指定接收者必须具备的`permission`或通过`Intent.setPackage`设置广播仅对某个程序有效。
7 |
8 | - 保证我接收到的广播是指定对象发送过来的
9 | 当应用程序注册了某个广播时,即便设置了`IntentFilter`还是会接收到来自其他应用程序的广播进行匹配判断。对于动态注册的广播可以通过类似`registerReceiver(BroadcastReceiver, IntentFilter, String, android.os.Handler)`的接口指定发送者必须具备的`permission`,对于静态注册的广播可以通过`android:exported="false"`属性表示接收者对外部应用程序不可用,即不接受来自外部的广播。
10 |
11 | `android.support.v4.content.LocalBroadcastManager`工具类,可以实现在自己的进程内进行局部广播发送与注册,使用它比直接通过sendBroadcast(Intent)发送系统全局广播有以下几个好处:
12 | - 因广播数据在本应用范围内传播,你不用担心隐私数据泄露的问题。
13 | - 不用担心别的应用伪造广播,造成安全隐患。
14 | - 相比在系统内发送全局广播,它更高效。
15 |
16 | ```java
17 | LocalBroadcastManager mLocalBroadcastManager;
18 | BroadcastReceiver mReceiver;
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | IntentFilter filter = new IntentFilter();
24 | filter.addAction("test");
25 |
26 | mReceiver = new BroadcastReceiver() {
27 | @Override
28 | public void onReceive(Context context, Intent intent) {
29 | if (intent.getAction().equals("test")) {
30 | //Do Something
31 | }
32 | }
33 | };
34 | mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
35 | mLocalBroadcastManager.registerReceiver(mReceiver, filter);
36 | }
37 |
38 |
39 | @Override
40 | protected void onDestroy() {
41 | mLocalBroadcastManager.unregisterReceiver(mReceiver);
42 | super.onDestroy();
43 | }
44 | ```
45 |
46 | ---
47 |
48 | - 邮箱 :charon.chui@gmail.com
49 | - Good Luck!
50 |
--------------------------------------------------------------------------------
/AdavancedPart/Java:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharonChui/AndroidNote/428cb4eed395020e4c6828c1e96ab35a0c17dad6/AdavancedPart/Java
--------------------------------------------------------------------------------
/AdavancedPart/Library项目中资源id使用case时报错.md:
--------------------------------------------------------------------------------
1 | Library项目中资源id使用case时报错
2 | ===
3 |
4 | 在平时开发的过程中对一些常量的判断,都是使用`switch case`语句,因为`switch case`比`if else`的效率稍高。
5 | 但是也遇到了问题,就是在`library`中使用时会报错:
6 | ```java
7 | public void testClick(View view) {
8 | int id = view.getId();
9 | switch (id) {
10 | case R.id.bt_pull:
11 |
12 | break;
13 | case R.id.bt_push:
14 |
15 | break;
16 | }
17 | }
18 | ```
19 | 我们来看一下错误提示:
20 |
21 | 
22 |
23 | 意思就是在`Android Library`的`Module`中的`switch`语句不能使用资源`ID`.这是为什么呢? 我们都知道`R`文件中都是常量啊,`public static final`的,为什么不能用在`switch`语句中,继续看下去:
24 | ```
25 | Resource IDs are non final in th library projects since SDK tools r14, means that the library code cannot treat this IDs as constants.
26 | ```
27 | 说的灰常明白了,也就是说从14开始,library中的资源`id`就不是`final`类型的了,所以不是常量了。
28 |
29 | 在一个常规的`Android`项目中,资源`R`文件中的常量都是如下这样声明的:
30 | `public static final int main=0x7f030004;`
31 | 然后,从`ADT`14开始,在`library`项目中,它们将被这样声明:
32 | `public static int main=0x7f030004;`
33 |
34 | 也就是说在`library`项目中这些常量不是`final`的了。为什么这样做的原因也非常简单:当多个`library`项目结合时,这些字段的实际值(必须唯一的值)可能会冲突。在`ADT`14之前,所有的字段都是`final`的,这样就导致了一个结果,就是所有的`libraries`不论何时在被使用时都必须把他们所有的资源和相关的`Java`代码都伴随着主项目一起重新编译。这样做是不合理的,因为它会导致编译非常慢。这也会阻碍一些没有源码的`libraray`项目进行发布,极大的限制了`library`项目的使用范围。
35 |
36 | 那些字段不再是`final`的原因就是意味着`library jars`可以被编译一次,然后在其他项目中直接被服用。也就是意味着允许发布二进制版本的`library`项目(r15中开始支持),这让编译变的更快。
37 |
38 | 然而,这对`library`的源码会有一个影响。类似下面这样的代码就将无法编译:
39 | ```java
40 | public void testClick(View view) {
41 | int id = view.getId();
42 | switch (id) {
43 | case R.id.bt_pull:
44 |
45 | break;
46 | case R.id.bt_push:
47 |
48 | break;
49 | }
50 | }
51 | ```
52 | 这是因为`swtich`语句要求所有`case`后的字段,例如`R.di.bt_pull`在编译的时候都应该是常量(就像可以直接把值拷贝进`.class`文件中)
53 |
54 | 当然针对这个问题的解决方法也很简单:就是把`switch`语句改成`if-else`语句。幸运的是,在`eclise`中这是非常简单的。只需要在`switch`的关键字上按`Ctrl+1`(`Mac`上是`Cmd+1`)。(`eclipse`都辣么方便了,`Studio`更不用说了啊,直接按修复的快捷键就阔以了)。
55 | 上面的代码就将变成如下:
56 |
57 | ```java
58 | public void testClick(View view) {
59 | int id = view.getId();
60 | if (id == R.id.bt_pull) {
61 | } else if (id == R.id.bt_push) {
62 | }
63 | }
64 | ```
65 | 这在UI代码和性能的影响通常是微不足道的 - -!。
66 |
67 | ---
68 |
69 | - 邮箱 :charon.chui@gmail.com
70 | - Good Luck!
71 |
72 |
--------------------------------------------------------------------------------
/AdavancedPart/Mac下配置adb及Android命令.md:
--------------------------------------------------------------------------------
1 | Mac下配置adb及Android命令
2 | ===
3 |
4 | 带着欣喜若狂的心情,买了一个`Mac`本,刚拿到它的时候感觉真的是一件艺术品,哈哈。废话不多说,里面配置`Android`开发环境。
5 |
6 | 1. 找到`android sdk`的本地路径,
7 | 我的是:
8 | - `ADB`
9 | `/Android/adt-bundle-mac-x86_64-20131030/sdk/platform-tools`
10 | - `Android`
11 | `/Android/adt-bundle-mac-x86_64-20131030/sdk/tools`
12 |
13 | 2. 打开终端输入
14 | - `touch .bash_profile`创建
15 | - `open -e .bash_profile`打开
16 |
17 | 3. 添加路径
18 | `.bash_profile`打开了,我们在这里添加路径,
19 | - 如果打开的文档里面已经有内容,我们只要之后添加`:XXXX`**(注意前面一定要用冒号隔开)**
20 | - 如果是一个空白文档的话,我们就输入一下内容
21 | `export PATH=${PATH}:XXXX`
22 | 我的是
23 | `export PATH=${PATH}:/Android/adt-bundle-mac-x86_64-20131030/sdk/tools:${PATH}:/Android/adt-bundle-mac-x86_64-20131030/sdk/platform-tools;`
24 | 保存,关掉这个文档,
25 | 4. 终端输入命令
26 | `source .bash_profile`
27 | 5. 大功告成
28 |
29 |
30 |
31 | ---
32 |
33 | - 邮箱 :charon.chui@gmail.com
34 | - Good Luck!
--------------------------------------------------------------------------------
/AdavancedPart/如何让Service常驻内存.md:
--------------------------------------------------------------------------------
1 | 如何让Service常驻内存
2 | ===
3 |
4 | 我非常鄙视这种行文,一个公司应该想到如何把产品做的更完善,而不是用这些技术来损害用户的利益,来获取自己肮脏的所谓的功能。
5 |
6 | - `Service`设置成`START_STICKY`,`kill`后会被重启(等待5秒左右),重传`Intent`,保持与重启前一样
7 | - 通过`startForeground`将进程设置为前台进程,做前台服务,优先级和前台应用一个级别,除非在系统内存非常缺,否则此进程不会被`kill`
8 | - 双进程`Service`:让2个进程互相保护,其中一个`Service`被清理后,另外没被清理的进程可以立即重启进程
9 | - `QQ`黑科技:在应用退到后台后,另起一个只有1像素的页面停留在桌面上,让自己保持前台状态,保护自己不被后台清理工具杀死
10 | - 在已经`root`的设备下,修改相应的权限文件,将`App`伪装成系统级的应用(`Android4.0`系列的一个漏洞,已经确认可行)
11 | - `Android`系统中当前进程(`Process`)`fork`出来的子进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以存活,并不受影响。
12 | 鉴于目前提到的在`Android-Service`层做双守护都会失败,我们可以`fork`出`c`进程,多进程守护。死循环在那检查是否还存在,
13 | 具体的思路如下(`Android5.0`以下可行)
14 | - 用`C`编写守护进程(即子进程),守护进程做的事情就是循环检查目标进程是否存在,不存在则启动它。
15 | - 在`NDK`环境中将1中编写的`C`代码编译打包成可执行文件(`BUILD_EXECUTABLE`)。
16 | - 主进程启动时将守护进程放入私有目录下,赋予可执行权限,启动它即可。
17 |
18 | - 联系厂商,加入白名单
19 |
20 |
21 |
22 | ---
23 |
24 | - 邮箱 :charon.chui@gmail.com
25 | - Good Luck!
--------------------------------------------------------------------------------
/AdavancedPart/性能优化.md:
--------------------------------------------------------------------------------
1 | 性能优化
2 | ===
3 |
4 | 代码优化原则:
5 | ---
6 |
7 | - 时间换时间:
8 | 如禁用电脑的一些开机启动项,通过减少这些没必要的启动项的时间从而节省开机时间
9 | 如网站界面上数据的分批获取,`AJAX`技术
10 | - 时间换空间:
11 | 如拷贝文件时new一个字节数组当缓冲器,即`byte[] buffer = new byte[1024]`。
12 | 为什么只`new`一个1024个字节的数组呢,`new`一个更大的字节数组不是一下就把文件拷贝完了么?这么做就是为了牺牲时间节省有限的内存空间
13 | - 空间换时间:
14 | 如用`Windows`系统自带的搜索文件的功能搜索文件时会很慢,但是我们牺牲电脑硬盘空间安装一个`everything`软件来搜索文件就特别快
15 | - 空间换空间:
16 | 如虚拟内存
17 |
18 | `Android`开发中的体现
19 | ---
20 |
21 | - 采用硬件加速,在清单文件中`application`节点添加`android:hardwareAccelerated=”true”`。不过这个需要在`android 3.0`才可以使用。`android4.0`这个选项是默认开启的。
22 | - `View`中设置缓存属性`setDrawingCache`为`true`.
23 | - 优化你的布局.
24 | - 动态加载`View`. 采用`ViewStub`避免一些不经常的视图长期握住引用.
25 | - 将`Acitivity`中的`Window`的背景图设置为空。`getWindow().setBackgroundDrawable(null);``android`的默认背景是不是为空。
26 | - 采用``优化布局层数。 采用``来共享布局。
27 | - 利用`TraceView`查看跟踪函数调用。有的放矢的优化。
28 | - `cursor`的使用。不过要注意管理好`cursor`,不要每次打开关闭`cursor`.因为打开关闭`Cursor`非常耗时。 `Cursor.require`用于刷`cursor`.
29 | - 采用`SurfaceView`在子线程刷新`UI`, 避免手势的处理和绘制在同一`UI`线程(普通`View`都这样做)。
30 | - 采用`JNI`,将耗时间的处理放到`c/c++`层来处理。
31 | - 有些能用文件操作的,尽量采用文件操作,文件操作的速度比数据库的操作要快10倍左右。
32 | - 避免创建不必要的对象
33 | - 如果方法用不到成员变量,可以把方法申明为`static`,性能会提高到15%到20%
34 | - 避免使用`getter/setter`存取`field`,可以把`field`申明为`public`,直接访问
35 | - `static`的变量如果不需要修改,应该使用`static final`修饰符定义为常量
36 | - 使用增强`for`循环,比普通`for`循环效率高,但是也有缺点就是在遍历 集合过程中,不能对集合本身进行操作
37 | - 合理利用浮点数,浮点数比整型慢两倍;
38 | - 针对`ListView`的性能优化
39 |
40 | ---
41 |
42 | - 邮箱 :charon.chui@gmail.com
43 | - Good Luck!
--------------------------------------------------------------------------------
/AndroidStudioCourse/Android Studio你可能不知道的操作.md:
--------------------------------------------------------------------------------
1 | Android Studio你可能不知道的操作
2 | ===
3 |
4 | 今天看在`Youtube`看视频,看到`Reto Meier`在讲解`Studio`,
5 | 一查才知道他现在是`Studio`的开发人员。
6 | 想起刚开始学`Android`时买的他写的书`Professional Android 4 Application Development`,
7 | 当时很多内容没看懂。不过看了这个视频才发现大神写代码如此之快…
8 |
9 | 现在天天用着大神开发的工具,没有理由不去好好学习下。
10 |
11 | 工欲善其事必先利其器,一个好的开发工具可以让你事半功倍。`Android Studio`是一个非常好的开发工具,但是虽然都在用,你可能还是了解的不全面,今天就来说一下一些你可能不知道的功能。
12 |
13 | 熟练使用快捷键是非常有必要的:
14 | 
15 |
16 |
17 | - 自动导入
18 | 经常听到同事抱怨,`Studio`怎么没有`Eclipse`那种批量导包啊,那么多类要到,费劲了。其实不用一个个导的。
19 | 使用`Command+Shift+A(Windows或Linux是Ctrl+Shift+A)`快速的查找设置命令。我们输入`auto import`后将`Add unambiguous imports on fly`选项开启就好了,很爽有木有?你的是不是也没开啊?
20 | 
21 | 你可以快速打开一些设置,例如你想在`finder`中查看该文件,直接输入`find`就好了。
22 | 
23 |
24 | - 在自动完成代码时使用`Tab`键来替换已存在的方法和参数。
25 | 经常如我们想要修改一个已经存在的方法时,我们移动到对象的`.`的提示区域,如果使用`Command+Space`来补充代码选择后按`enter`的话,会把之前已经存在的代码往后移动,这样还要再去删除,很不方便。但是如果我们直接使用`Tag`键,那就会直接替换当前已经存在的方法。
26 | 
27 |
28 | - 内容选择技巧
29 | 使用`Control+↑或↓`能在方法间移动。使用`Shift+↑或↓`来选择至上一行或者至下一行代码的代码?那么使用`option++↑或↓(Windows或Linux是alt++↑或↓)`呢?它能选择或者取消选择和你鼠标所在位置的代码区域。同时使用`option+shift++↑或↓(Windows或Linux是alt+shift++↑或↓)可以交换选中代码块与上一行的位置。这样我们就不需要剪切和粘贴了。
30 |
31 | - 使用模板补全代码
32 | 你可以在代码后加用后缀的方式补充代码块,也可以用`Command+J(Windows是Ctrl+J)`来提示。
33 | 
34 | 对于一些更多的模式,在代码自动完成时也支持生成对应的模板,例如使用`Toast`的快捷键可以很方便的生成一个新的`toast`对象,你只需要指定文字就可以了。
35 | 
36 | 用`Command+Shift+A`然后输入`live template`然后打开对应的页面,可以看到目前已经存在的模板。当然你也可以添加新的模板。
37 | 
38 |
39 | - 在`Evaluating Expressions`中为`Object`对象指定显示内容
40 | 如果我们在`debug`的时候查看断点处的变量值或者`evaluating expressions`,你会发现`objects`会显示他们的`toString()`值。如果你的变量是`String`类型或者基础类型那不会有问题,但是大多数其他对象,这样没有什么意义。
41 | 尤其是在集合对象时,你看的是一个`CallsName:HashValue`的列表。而为了需要看清数据,我们需要知道每个对象的内容。
42 | 你当然可以去对每个对象类型指定索要显示的内容。在对应的对象上邮件`View as`然后创建你想要显示的内容就可以了。
43 | 
44 |
45 | - 结构性的搜索、替换、和检查
46 | `Command+shift+A`然后输入`structural `后选择`search structurally`或者`replace structurally`,然后可以对应的结构性搜索的模板,完成之后所有符合该模板的代码都会提示`warning`。
47 | 
48 | 更有用的是可以使用快速修复来替换一些代码,例如一些废弃的代码或者你在`review`时发现的其他团队成员提交的一些普遍的错误。
49 |
50 |
51 | ---
52 |
53 | - 邮箱 :charon.chui@gmail.com
54 | - Good Luck!
--------------------------------------------------------------------------------
/AndroidStudioCourse/AndroidStudio中进行ndk开发.md:
--------------------------------------------------------------------------------
1 | AndroidStudio中进行ndk开发
2 | ===
3 |
4 | - 创建工程,声明`native`方法。
5 | ```java
6 | private native void startDaemon(String serviceName, int sdkVersion);
7 |
8 | static {
9 | System.loadLibrary("daemon");
10 | }
11 | ```
12 |
13 |
14 | - 生成`class`文件。
15 | 执行`Build-Make Project`命令,生成`class`文件。所在目录为`app_path/build/intermediates/classes/debug`
16 |
17 |
18 | - 执行`javah`生成`.h文件`
19 | ```
20 | C:\Users\Administrator>javah -help
21 | 用法:
22 | javah [options]
23 | 其中, [options] 包括:
24 | -o 输出文件 (只能使用 -d 或 -o 之一)
25 | -d 输出目录
26 | -v -verbose 启用详细输出
27 | -h --help -? 输出此消息
28 | -version 输出版本信息
29 | -jni 生成 JNI 样式的标头文件 (默认值)
30 | -force 始终写入输出文件
31 | -classpath 从中加载类的路径
32 | -cp 从中加载类的路径
33 | -bootclasspath 从中加载引导类的路径
34 | ```
35 | 在`Studio Terminal`中进入到`src/main`目录下执行`javah`命令:
36 | `javah -d jni -classpath ; `
37 |
38 | `F:\DaemonService\app\src\main>javah -d jni -classpath C:\develop\android-sdk-windows\platforms\android-22\android.jar;..\..\build\intermediates\classes\debug com.charonchui.daemonservice.service.DaemonService`
39 | 执行完成后就会在`src/main/jni`目录下生成`com_charonchui_daemonservice_service_DaemonService.h`文件。
40 |
41 | - 在`module/src/main/jni`目录下创建对应的`.c`文件。
42 |
43 |
44 |
45 | - 配置`ndk`路径,在项目右键`Moudle Setting`中设置。
46 | 
47 |
48 | - 在`build.gradle`中配置`ndk`选项
49 |
50 | ```java
51 | android {
52 | compileSdkVersion 23
53 | buildToolsVersion "23.0.1"
54 |
55 | defaultConfig {
56 | applicationId "com.charonchui.daemonservice"
57 | minSdkVersion 8
58 | targetSdkVersion 23
59 | versionCode 1
60 | versionName "1.0"
61 |
62 | ndk {
63 | moduleName "uninstall_feedback" // 配置so名字
64 | ldLibs "log"
65 | // abiFilters "armeabi", "x86" // 默认就是全部的,加了配置才会生成选中的
66 | }
67 | }
68 | buildTypes {
69 | release {
70 | minifyEnabled false
71 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
72 | }
73 | }
74 | }
75 | ```
76 | 这里可能会出现错误:
77 | - `Error: NDK integration is deprecated in the current plugin. Consider trying the new experimental plugin. For details, see http://tools.android.com/tech-docs/new-build-system/gradle-experimental. Set "android.useDeprecatedNdk=true" in gradle.properties to continue using the current NDK integration.`
78 | 解决方法就是在`gradle.properties`文件中添加`android:useDeprecatedNdk=true`就可以了。
79 | - `Error:Execution failed for task ':app:compileDebugNdk'.
80 | > com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command 'E:\android-ndk-r10\ndk-build.cmd'' finished with non-zero exit value 2`
81 | 解决方法就是在`jni`目录建一个任意名字的`.c`空文件就可以了。
82 |
83 | - 执行Build
84 | 然后就可以在`app/build/intermediates/ndk/debug/obj/local`下看到所有架构的`so`了。
85 | 
86 |
87 | ---
88 |
89 | - 邮箱 :charon.chui@gmail.com
90 | - Good Luck!
--------------------------------------------------------------------------------
/AndroidStudioCourse/AndroidStudio使用教程(第三弹).md:
--------------------------------------------------------------------------------
1 | AndroidStudio使用教程(第三弹)
2 | ===
3 |
4 | 熟悉了基本的使用之后,可能关心的就是版本控制了。
5 |
6 | - `SVN`
7 | - 下载`Subversion command line`
8 | - 方法一
9 | 下载地址是[Subversion](http://subversion.apache.org/packages.html)里面有不同系统的版本。
10 | 以`Windows`为例,我们采用熟悉的`VisualSVN`.
11 | 
12 | 进入下载页后下载`Apache Subversion command line tools`, 解压即可。
13 | 
14 |
15 | - 方法二
16 | `Windows`下的`Tortoise SVN`也是带有`command line`的,但是安装的时候默认是不安装这个选项的,所以安装时要注意选择一下。
17 | 
18 | 选择安装即可
19 | 
20 |
21 | - 配置`SVN`
22 | 进入设置中心,搜索`Version Control`后选择`Subversion`, 将右侧的`Use command line client`设置你本地的`command line`路径即可。
23 | 
24 | 如果是用第二种方式安装`Tortoise SVN`的话地址就是:
25 | 
26 | PS: 设置成自己的路径啊,不要写我的...
27 |
28 | - `Git`
29 | 安装`Git`, [Git](http://git-scm.com/)
30 | 选择相应系统版本安装即可。
31 | 安装完成后进入`Android Studio`设置中心-> `Version Control` -> `Git`设置`Path to Git executable`即可。
32 | 
33 |
34 | - `Github`
35 | 怎么能少了它呢?哈哈
36 | 这个就不用安装了,直接配置下用户名和密码就好了。
37 | 
38 |
39 | - `Checkout`
40 | 在工具栏中点击 `VCS` 能看到相应检出和导入功能。这里以`Github`检出为例介绍一下。
41 | 
42 | 
43 | 确定之后就能在底部导航栏看到检出进度
44 | 
45 |
46 | 完成之后会提示是否想要把刚才检出的项目导入`AndroidStudio`中。
47 | Why not?
48 | 以`Gradle`方式导入, 然后`next`, 然后`next`然后就没有然后了。
49 | 
50 |
51 | ---
52 |
53 | - 邮箱 :charon.chui@gmail.com
54 | - Good Luck!
--------------------------------------------------------------------------------
/AndroidStudioCourse/AndroidStudio使用教程(第二弹).md:
--------------------------------------------------------------------------------
1 | AndroidStudio使用教程(第二弹)
2 | ===
3 |
4 | - 迁移`Eclipse`工程到`Android Studio`
5 |
6 | 官方文档中说`Android Studio`可以兼容`Eclipse`的现有工程,但需要做一些操作:
7 |
8 | - `Eclipse`进行项目构建
9 | 首先升级`ADT`到最新版本, 好像是22之后,选择需要从`Eclipse`导出的工程,右键选择`Export`并选择`Android`下的`Generate Gradle Build Files`,
10 | 运行完成之后你会发现在项目目录中多了一个`build.gradle`, 这就是`Android Studio`所识别的文件。
11 | PS:官方文档中说明如果没有`Grade build`文件,也是可以将项目导入到`Android Studio`中,它会用现有的`Ant build`文件进行配置.
12 | 但为了更好地使用之后的功能和充分使用构建变量,
13 | 还是强烈地建议先从`ADT`插件中生成`Gradle`文件再导入`Android Studio`.
14 |
15 | - 导入
16 | 在`Android Studio`中选择`Import Project`,并选择刚才工程目录下的`build.gradle`即可。
17 |
18 | 有些时候会发现导入之后在运行按钮左边显示不出`Module`来,可能是你导入之前的`SDK`版本不同导致的,只要在`build.gradle`中配置相应的`SDK`版本就可以了。
19 | ```java
20 | android {
21 | compileSdkVersion 19
22 | buildToolsVersion "21.1.1"
23 | ...
24 | }
25 | ```
26 |
27 | - 创建工程
28 | 创建工程和`Eclipse`流程基本差不多,大家一看就明白了,这里就不说了。
29 |
30 | - 使用`Android`项目视图
31 | 这里纯粹看个人爱好,不过对于标准的`AndroidStudio`工程,一般我们常用的部分,都在`Android`项目视图中显示出来了。
32 | 
33 | 效果如下图:
34 | 
35 |
36 | - 使用布局编辑器
37 |
38 | 布局编辑器的适时和多屏幕预览的确是一个亮点。
39 |
40 | - 点击布局页面右侧的`Preview`按钮,可以进行预览。
41 |
42 | 想预览多屏幕效果时可以在预览界面设备的下拉菜单上选择`Preview All Screen Sizes`.
43 | 
44 |
45 | - 选择主题
46 |
47 | 想给应用设置一个主题,可以点击`Theme`图标,
48 | 就会显示出选择对话框。
49 | 
50 |
51 | - 国际化
52 | 对于国际化的适配选择国际化图标,
53 | 然后在弹出的列表中选择需要进行国际化的国家进行适配即可。
54 |
55 | - 常用功能
56 | 有些人进来之后可能找不到`DDMS`了.
57 | 
58 | 上图中的三个图标分别为, `AVD Manager`、`SDK Manager`、`DDMS`
59 |
60 |
61 |
62 | ---
63 |
64 | - 邮箱 :charon.chui@gmail.com
65 | - Good Luck!
--------------------------------------------------------------------------------
/AndroidStudioCourse/AndroidStudio使用教程(第六弹).md:
--------------------------------------------------------------------------------
1 | AndroidStudio使用教程(第六弹)
2 | ===
3 |
4 | Debug
5 | ---
6 |
7 | `Andorid Studio`中进行`debug`:
8 | - 在`Android Studio`中打开应用程序。
9 | - 点击状态栏中的`Debug`图标。
10 | - 在接下来的选择设备窗口选择相应的设备或创建虚拟机, 点击`OK`即可。
11 | `Android Studio`在`debug`时会打开`Debug`工具栏, 可以点击`Debug`图标打开`Debug`窗口。
12 |
13 | ###设置断点
14 | 与`Eclipse`十分相似, 在代码左侧位置点击一下即可, 圆点的颜色变了。
15 |
16 | ###Attach the debugger to a running process
17 | 在`debug`时不用每次都去重启应用程序。 我们可以对正在运行的程序进行`debug`:
18 | - 点击`Attach debugger to Android proccess`图标。
19 | - 设备选择窗口选择想要`debug`的设备。
20 | - 点击`Debug`图标打开`Debug`工具栏。
21 | - 在`Debug`工具栏中有`Stop Over`, `Stop Into`等图标和快捷键,这些就不仔细说明了, 和`Eclipse`都差不多。
22 |
23 | ### View the system log
24 | 在`Android DDMS`中和`Debug`工具栏中都可以查看系统`log`日志,
25 | - 在窗口底部栏点击`Android` 图标打开`Android DDMS`工具栏。
26 | - 如果此时`Logcat`窗口中的日志是空得,点击`Restart`图标。
27 | - 如果想要只显示当前某个进程的信息点击`Only Show Logcat from Selected Process`. 如果当时设备窗口不可见,点击右上角的`Restore Devices View`图标,该图标只有设备窗口不可见时才会显示。
28 |
29 | 删除`Project`及`Module`
30 | ---
31 |
32 | 很多人都在问`AndroidStudio`中如何删除`Project`,如何删除`Module`?怎么和`Eclipse`不同啊,找不到`delete`或`remove`选项。
33 | - 删除`Project`
34 | 点击左侧`File`-->`Close project`,关闭当前工程, 然后直接找到工程所在本地文件进行删除(慎重啊), 删除完之后点击最近列表中的该项目就会提示不存在,我们把他从最近项目中移除即可.你会发现,点击`remove`之后没效果,以后估计会解决。
35 | - 删除`Module`
36 | 在该`Module`邮件选择`Open Module Settings`。
37 | 
38 | 进入设置页后选中要删除的`Module`点击左上角的删除图标`-`后点击确定。
39 | 
40 |
41 |
42 | ---
43 |
44 | - 邮箱 :charon.chui@gmail.com
45 | - Good Luck!
--------------------------------------------------------------------------------
/AppPublish/Android应用发布.md:
--------------------------------------------------------------------------------
1 | Android应用发布
2 | ===
3 | 需要身份证扫描件、手持身份证正面照、图标(96x96、512x512)、应用截图(一般480x800,6张)进行每个网站的开发者注册(需要审核,有的比较慢,尽量提前审核)
4 |
5 | - **[GooglePlay](https://play.google.com/apps/publish)自动审核,速度很快,但是要上缴25刀**
6 | - **[腾讯开发平台](http://open.qq.com/?from=tap)史上最好,没有之一、快、大,腾讯帝国根深蒂固**
7 | - **[淘宝手机助手](http://app.taobao.com)很快,每次他都是首发...,但是需要jpg格式的截图和图标,十八罗汉速度非凡**
8 | - **[360应用开发平台](http://open.app.360.cn/)实力不容小视,但是对广告审核很严,感觉不是很好**
9 | - **[百度开发者中心(关联安卓及91)](http://developer.baidu.com/)这个不多说了,都是泪,各种麻烦、各种慢、各种不合理,众里寻他千百度,他想几度就几度**
10 | - **[安卓市场](http://dev.apk.hiapk.com/login)特别慢,百度上了,他各种理由不给上。**
11 | - **[91](http://market.sj.91.com/Users/Login.aspx?ReturnUrl=%2fDefault.aspx)慢,不说了,三家市场不如别人一家**
12 | - **[豌豆荚开发者中心](http://developer.wandoujia.com)还不错,账号用了手机号**
13 | - **[安智市场](http://dev.anzhi.com/)账号不是邮箱,而是账号名,不支持广告**
14 | - **[机锋](http://dev.gfan.com/)必须用机锋的广告,太霸道了**
15 | - **[木蚂蚁](http://dev.mumayi.com/index/)需要在有米广告中加入木蚂蚁的渠道号**
16 | - **[小米](http://developer.xiaomi.com)**
17 | - **[优亿市场](http://dev.eoemarket.com/)账号是用户名不是邮箱**
18 | - **[10086](http://dev.10086.cn/)密码最后一位是***
19 | - **[魅族](http://developer.meizu.com)风格独特要做适配**
20 | - **[易用汇](http://www.anzhuoapk.com)**
21 | - **[天翼开发平台](http://open.189.cn/)**
22 | - **[易优市场](http://www.eomarket.com/developer)**
23 | - **[安极市场](apk.angeeks.com)**
24 | - **[3G安卓市场](http://dev.3g.cn/)**
25 | - **[N多市场](http://www.nduoa.com/developer)**
26 | - **[安卓星空](http://dev.liqucn.com/index.php?m=member&c=index&a=login)**
27 | - **[搜狐应用市场](http://admin.app.sohu.com/platform/index)账号为搜狐邮箱**
28 | - **[沃商城](http://dev.wo.com.cn/index.action)**
29 |
30 |
31 | 做死系列
32 | - **[应用汇](http://dev.appchina.com/)貌似已经不收录个人应用,也不说明一下,等你提交后就说不收录该类内容**
33 | - **[网讯安卓应用市场](http://dev.51vapp.com/)(要软件著作权,不然通过不了)**
34 | - **[联想](http://developer.lenovomm.com)很难发布,他会把你定位成劣质应用,好吧,劣质公司,你看你吧ThinkPad弄成什么样了**
35 | - **[华为](http://developer.huawei.com/)不收录个人应用**
36 | - **[网易](http://m.163.com/android/)灰常垃圾,等你全部弄好后还要加他QQ让他审核,完了他会告诉你我们不接受个人应用,不做死就不会死**
37 | - **[京东](http://play.jd.com/download/)不好好卖东西,整个应用市场,又没人管理**
38 |
39 |
40 |
41 |
42 | ---
43 |
44 | - 邮箱 :charon.chui@gmail.com
45 | - Good Luck!
46 |
--------------------------------------------------------------------------------
/AppPublish/Zipalign优化.md:
--------------------------------------------------------------------------------
1 | Zipalign优化
2 | ===
3 |
4 | `Zipalign`优化工具是`SDK`中自带的优化工具,在`android-sdk-windows\build-tools\23.0.1`,在我们上传`Google Pay`的时候都会遇到您上传的`Apk`没有经过`Zipalign`处理
5 | 的失败提示,就是说如果你的`apk`没有使用`zipalign`优化,那`google play`是拒绝给你上架的,从这里能看出`zipalign`优化是多么滴重要。
6 |
7 | ```
8 | zipalign is an archive alignment tool that provides important optimization to Android application (.apk) files.
9 | The purpose is to ensure that all uncompressed data starts with a particular alignment relative to the start of the file.
10 | Specifically, it causes all uncompressed data within the .apk, such as images or raw files, to be aligned on 4-byte boundaries.
11 | This allows all portions to be accessed directly with mmap() even if they contain binary data with alignment restrictions.
12 | The benefit is a reduction in the amount of RAM consumed when running the application.
13 | ```
14 |
15 | ```
16 | Caution: zipalign must only be performed after the .apk file has been signed with your private key.
17 | If you perform zipalign before signing, then the signing procedure will undo the alignment.
18 | Also, do not make alterations to the aligned package.
19 | Alterations to the archive, such as renaming or deleting entries,
20 | will potentially disrupt the alignment of the modified entry and all later entries.
21 | And any files added to an "aligned" archive will not be aligned.
22 | ```
23 |
24 | 大意就是它提供了一个灰常重要滴功能来确保所有未压缩的数据都从文件的开始位置以指定的4字节对齐方式排列,例如图片或者
25 | `raw`文件。当然好处也是大大的,就是能够减少内存的资源消耗。最后他还特意提醒了你一下就是一定在对`apk`签完名之后再用`zipalign`
26 | 优化,如果你在之前用,那无效。
27 |
28 | 废多看用法:
29 |
30 | - 首先我要检查下我的`apk`到底用没用过`zipalign`优化呢?
31 | `zipalign -c -v 4 test.apk`
32 | 这个4是神马呢?就是4个字节的队列方式
33 | 命令一顿执行,然后打出来了`Verification failed`,我不想再解释了。
34 |
35 | - 如何使用?
36 | `zipalign -f -v 4 test.apk zip.apk`
37 | 就是把当前的`test.apk`使用`zipalign`优化,优化完成后的是`zip.apk`
38 |
39 | Flag:
40 |
41 | - -f : overwrite existing outfile.zip
42 | - -v : verbose output
43 | - -c : confirm the alignment of the given file
44 |
45 |
46 | ---
47 |
48 | - 邮箱 :charon.chui@gmail.com
49 | - Good Luck!
50 |
--------------------------------------------------------------------------------
/Architect/1.架构简介.md:
--------------------------------------------------------------------------------
1 | 1.系统架构
2 | ===
3 |
4 | #### 什么是系统架构
5 |
6 | 关于系统架构,维基百科给出了一个非常好的定义。
7 | A system architecture is the conceptual model that defines the structure, behavior, and more views of a system.[
8 | (系统架构是概念模型,定义了系统的结构、行为和更多的视图)
9 |
10 | * 系统架构是一个概念模型。
11 | * 系统架构定义了系统的结构、行为以及更多的视图。
12 | 关于这个定义,这里给出了另外一种解读,供大家参考。
13 | * 静。首先,从静止的角度,描述系统如何组成,以及系统的功能在这些组成部分之间是如何划分的。这就是系统的“结构”。一般要描述的是:系统包含哪些子系统,每个子系统有什么功能。在做这些描述时,应感觉自己是一名导游,带着游客在系统的子系统间参观。
14 | * 动。然后,从动态的角度,描述各子系统之间是如何联动的,它们是如何相互配合完成系统预定的任务或流程的。这就是系统的“行为”。在做这个描述时,应感觉自己是一名电影导演,将系统的各种运行情况通过一个个短片展现出来。
15 | * 细。最后,在以上两种描述的基础上,从不通的角度,更详细的刻画出系统的细节和全貌。这就是“更多的视图”。
16 |
17 |
18 |
19 |
20 | 好代码的特性:
21 | 1. 鲁棒(Solid and Robust)
22 | 2. 高效(Fast)
23 | 3. 简洁(Maintainable and Simple)
24 | 4. 简短(Small)
25 | 5. 可测试(Testable)
26 | 6. 共享(Re-Usable)
27 | 7. 可移植(Portable)
28 | 8. 可观测(Observvable)/可监控(Monitorable)
29 | 9. 可运维(Operational): 可运维重点关注成本、效率和稳定性三个方面
30 | 10. 可扩展(Scalable and Extensible)
31 |
32 |
33 | 工程能力的定义:
34 | 使用系统化的方法,在保证质量的前提下,更高效率的为客户/用户持续交付有价值的软件或服务的能力。
35 |
36 | 在《软件开发的201个原则》一书中,将“质量第一”列为全书的第一个原则,可见其重要性。
37 | Edward Yourdon建议,当你被要求加快测试、忽视剩余的少量Bug、在设计或需求达成一致前就开始编码时,要直接说“不”。
38 | 开发前期的设计文档、技术评审3天以上100%。代码规范,缺乏认真的代码评审。
39 | 降低质量要求,事实上不会降低研发成本,反而会增加整体的研发成本。在研发阶段通过降低质量所“节省”的研发成本,会在软件维护阶段加倍偿还。
40 |
41 | 在研发前期(需求分析和系统设计)多投入资源,相对于把资源都投入在研发后期(编码、测试等),其收益更大。
42 |
43 |
44 | ### 架构三要素
45 |
46 | #### 构件
47 |
48 | 构件在软件领域是指可复用的模块,它可以是被封装的对象类、类树、一些功能模块、软件框架(framework)、软件架构(或体系结构Architectural)、文档、分析件、设计模式(Pattern)。但是,操作集合、过程、函数即使可以复用也不能成为一个构件。
49 |
50 | ##### 构件的属性:
51 | 1. 有用性(Usefulness):构件必须提供有用的功能。
52 | 2. 可用性(Usability):构件必须易于理解和使用,可以正常运行。
53 | 3. 质量(Quality):构件及其变形必须能正确工作,质量好坏与可用性相互补充。
54 | 4. 适应性(Adaptability):构件应该易于通过参数化等方式再不同环境中进行配置,比较高端一点的复用性,接收外界各种入参,产生不同的结果,健壮性比较高。
55 | 5. 可移植性(Portability):构件应能在不同的硬件运行平台和软件环境中工作,可移植性比较好,跨平台。
56 |
57 |
58 | #### 模式(Pattern)
59 |
60 | 其实就是解决某一类问题的方法论,是生产经验和生活经验中经过抽象和升华提炼出来的核心知识体系。 模式就是一个完整的流程闭环,能够解决一些问题的通用方法(比如资本运作、玩家不同的需求等),软件中的模式大多源于生活,是人类智慧的结晶。
61 |
62 | #### 规划
63 |
64 | 规划是系统架构中最重要的组成部分,是个人或者组织制定的比较全面长远的发展计划,是对未来整体性、长期性、基本性问题的思考和考量。设计未来整套行动的方案。很早就有规划这个概念了,例如:国家的十一五规划等。当然软件开发也和生活紧密联系,一个大型的系统也需要良好的规划,规划可以说是基石,是系统架构的前提。
65 |
66 |
67 | 系统架构虽然是软件系统的结构,行为,属性的高级抽象,但其根本就是在需求分析的基础行为下,制定技术框架,对需求的技术实现。
68 |
69 |
70 | ### 系统设计的原则和方法:
71 |
72 | * 单一目的
73 | * 对外关系清晰
74 | * 重视资源约束
75 | * 根据需求做决策
76 | * 基于模型思考
77 |
78 |
79 | #### 单一职责原则
80 |
81 |
82 |
83 | #### 开放-封闭原则
84 | 无论模块是多么的‘封闭’,都会存在一些无法对之封闭的变化。既然不可能完全封闭,设计人员必须对于他设计的模块应该对哪种变化封闭做出选择。他必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化
85 |
86 | 开放-封闭原则是面向对象设计的核心所在。遵循这个原则可以带来面向对象技术所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中呈现出频繁变化的那些部分做出抽象,然而,对于应用程序中的每个部分都刻意地进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要
87 |
88 |
89 | #### 依赖倒转原则
90 |
91 |
92 |
93 | #### 迪米特法则
94 | 迪米特法则首先强调的前提是在类的结构设计上,每一个类都应当尽量降低成员的访问权限,也就是说,一个类包装好自己的private状态,不需要让别的类知道的字段或行为就不要公开
95 |
96 | 我们在程序设计时,类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。也就是说,信息的隐藏促进了软件的复用。”
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | ---
105 | - 邮箱 :charon.chui@gmail.com
106 | - Good Luck!
107 |
108 |
109 |
--------------------------------------------------------------------------------
/Architect/2.UML简介.md:
--------------------------------------------------------------------------------
1 | 2.UML简介
2 |
3 | 推荐使用[Visual Paradigm](https://www.visual-paradigm.com/cn/),如果是非商业用途可以下载社区版,免费使用
4 |
5 |
6 |
7 | 
8 |
9 |
10 | 类图分三层,第一层显示类的名称,如果是抽象类,则就用斜体显示。第二层是类的特性,通常就是字段和属性。第三层是类的操作,通常是方法或行为。注意前面的符号,‘+’表示public,‘-’表示private,‘#’表示protected。”
11 |
12 | 继承关系用空心三角形+实线来表示。
13 | 接口用空心三角形+虚线来表示。
14 | 关联关系用实线箭头来表示。
15 | 聚合表示一种弱的‘拥有’关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分。聚合关系用空心的菱形+实线箭头来表示。
16 | 合成(Composition,也有翻译成‘组合’的)是一种强的‘拥有’关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样.合成关系用实心的菱形+实线箭头来表示
17 | 依赖关系(Dependency),用虚线箭头来表示。
18 |
19 |
20 |
21 |
22 | ## 类图
23 |
24 | ## 时序图
25 |
26 |
27 |
--------------------------------------------------------------------------------
/BasicKnowledge/Ant打包.md:
--------------------------------------------------------------------------------
1 | Ant打包
2 | ===
3 |
4 | 使用步骤:
5 | 1. 对于已经存在的工程需要利用`Ant`命令更新一下:
6 | `android update project -n Test -p D:/workspace/Test -s -t 1`
7 | -n (name) 后面跟的是这个工程的名子
8 | -p (path)后面跟的是这个工程的目录路径
9 | -t (target)后面是当前共有的`SDK`版本。表明我们的*目标版本*(如果有了`project.properties`就不用再跟`target`这个参数了).
10 | `android list target`这样就能够列出来所有的sdk版本
11 |
12 | 2. 将签名文件keystore复制到工程根目录下,并且在根目录下新建`ant.properties`内容如下(配置签名文件):
13 | ```
14 | key.store=keystore.keystore //把签名放到根目录中
15 | key.alias=tencent
16 | key.store.password=1234
17 | key.alias.password=1234
18 | ```
19 |
20 | 3. 刷新工程
21 | 在`eclipse`中的`Ant`视图中右键`add build files`选择工程中的`build.xml`,选择最下面的`release`或者是`debug`,
22 | 注意`release`是生成带签名的`apk`包.生成的apk在`bin`目录中,名字为工程名`-release.apk`.
23 |
24 | 4. 常见错误:
25 | 有时候在用`ant`打包的时候会报一些错误,一般按照错误的提示进行修改即可,如文件的非法字符等。
26 | ```java
27 | Error occurred during initialization of VM
28 | Could not reserve enough space for object heap
29 | Error: Could not create the Java Virtual Machine.
30 | Error: A fatal exception has occurred. Program will exit.
31 | ```
32 | 如果发现以上错误,就是说明栈内存不足了,一种是内存设置的太小,还有一种情况就是你设置的内存大小已经超过了当前系统限制的大小。
33 | 打开`D:\Java\adt-bundle-windows\sdk\build-tools\android-4.4\dx.bat`将`set defaultXmx=-Xmx1024M`改为`set defaultXmx=-Xmx512M`即可。
34 |
35 | ---
36 |
37 | - 邮箱 :charon.chui@gmail.com
38 | - Good Luck!
39 |
--------------------------------------------------------------------------------
/BasicKnowledge/Home键监听.md:
--------------------------------------------------------------------------------
1 | Home键监听
2 | ===
3 |
4 | 1. `Home`键是一个系统的按钮,我们无法通过`onKeyDown`进行拦截,它是拦截不到的,我们只能得到他在什么时候被按下了。就是通过广播接收者
5 | ```java
6 | public class HomeKeyEventBroadCastReceiver extends BroadcastReceiver {
7 | static final String SYSTEM_REASON = "reason";
8 | static final String SYSTEM_HOME_KEY = "homekey";
9 | static final String SYSTEM_RECENT_APPS = "recentapps";
10 |
11 | @Override
12 | public void onReceive(Context context, Intent intent) {
13 | String action = intent.getAction();
14 | if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
15 | String reason = intent.getStringExtra(SYSTEM_REASON);
16 | if (reason != null) {
17 | if (reason.equals(SYSTEM_HOME_KEY)) {
18 | // home key处理点
19 | } else if (reason.equals(SYSTEM_RECENT_APPS)) {
20 | // long home key处理点
21 | }
22 | }
23 | }
24 | }
25 | }
26 | ```
27 |
28 | 2. 在`Activity`中去注册这个广播接收者
29 | ```java
30 | receiver = new HomeKeyEventBroadCastReceiver();
31 | registerReceiver(receiver, new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
32 | ```
33 |
34 | 3. 在`Activity`销毁的方法中去取消注册
35 | ```java
36 | unRegisterReceiver(receiver);
37 | ```
38 |
39 | ----
40 |
41 | - 邮箱 :charon.chui@gmail.com
42 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/Parcelable及Serializable.md:
--------------------------------------------------------------------------------
1 | Parcelable及Serializable
2 | ===
3 |
4 |
5 |
6 | ### Serializable
7 |
8 | 在Java中Serializable接口是一个允许将对象转换为字节流(序列化)然后重新构造回对象(反序列化)的标记接口。
9 |
10 | 它会使用反射,并且会创建许多临时对象,导致内存使用率升高,并可能产生性能问题。
11 |
12 |
13 | `Serializable`的作用是为了保存对象的属性到本地文件、数据库、网络流、`rmi`以方便数据传输,
14 | 当然这种传输可以是程序内的也可以是两个程序间的。而`Parcelable`的设计初衷是因为`Serializable`效率过慢,
15 | 为了在程序内不同组件间以及不同`Android`程序间(`AIDL`)高效的传输数据而设计,这些数据仅在内存中存在,`Parcelable`是通过`IBinder`通信的消息的载体。
16 |
17 | `Parcelable`的性能比`Serializable`好,在内存开销方面较小,所以在内存间数据传输时推荐使用`Parcelable`,
18 | 如`activity`间传输数据,而`Serializable`可将数据持久化方便保存,所以在需要保存或网络传输数据时选择
19 | `Serializable`,因为`android`不同版本`Parcelable`可能不同,所以不推荐使用`Parcelable`进行数据持久化。
20 |
21 | Parcelable不同于将对象进行序列化,Parcelable方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能了。
22 |
23 |
24 | ### Parcelable实现
25 |
26 | ```kotlin
27 | data class Developer(val name: String, val age: Int) : Parcelable {
28 |
29 | constructor(parcel: Parcel) : this(
30 | parcel.readString(),
31 | parcel.readInt()
32 | )
33 |
34 | override fun writeToParcel(parcel: Parcel, flags: Int) {
35 | parcel.writeString(name)
36 | parcel.writeInt(age)
37 | }
38 |
39 | // code removed for brevity
40 |
41 | companion object CREATOR : Parcelable.Creator {
42 | override fun createFromParcel(parcel: Parcel): Developer {
43 | return Developer(parcel)
44 | }
45 |
46 | override fun newArray(size: Int): Array {
47 | return arrayOfNulls(size)
48 | }
49 | }
50 | }
51 |
52 | ```
53 | 上面是实现Parcelable的代码,可以看到有很多重复的代码。
54 | 为了避免写这些重复的代码,可以使用kotlin-parcelize插件,并在类上使用@Parcelize注解。
55 |
56 | ```kotlin
57 | @Parcelize
58 | data class Developer(val name: String, val age: Int) : Parcelable
59 | ```
60 |
61 | 当在一个类上声明`@Parcelize`注解后,就会自动生成对应的代码。
62 |
63 |
64 |
65 |
66 |
67 | Parcelable不会使用反射,并且在序列化过程中会产生更少的临时对象,这样就会减少垃圾回收的压力:
68 |
69 | - Parcelable不会使用反射
70 | - Parcelable是Android平台特定的接口
71 |
72 | 所以Parcelable比Serializable更快。
73 |
74 |
75 | 区别:
76 | - Parcelable is faster than serializable interface
77 | - Parcelable interface takes more time for implemetation compared to serializable interface
78 | - serializable interface is easier to implement
79 | - serializable interface create a lot of temporary objects and cause quite a bit of garbage collection
80 | - Parcelable array can be pass via Intent in android.
81 |
82 | ----
83 | - 邮箱 :charon.chui@gmail.com
84 | - Good Luck!
85 |
--------------------------------------------------------------------------------
/BasicKnowledge/PopupWindow细节.md:
--------------------------------------------------------------------------------
1 | PopupWindow细节
2 | ===
3 |
4 | ## 简介
5 |
6 | `A popup window that can be used to display an arbitrary view. The popup windows is a floating container that appears on top of the current activity.`
7 |
8 | 1. 显示
9 | ```java
10 | View contentView = View.inflate(getApplicationContext(),R.layout.popup_appmanger, null);
11 | //这里最后一个参数是指定是否能够获取焦点,如果为false那么点击弹出来之后就不消失了,但是设置为true之后点击一个条目它弹出来了,
12 | 再点击别的条目的时候这个popupWindow窗口没有焦点了就自己消失了
13 | PopupWindow popwindow = new PopupWindow(contentView ,LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,true);
14 | popwindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
15 | int[] location = new int[2];
16 | //得到当前组件的位置
17 | view.getLocationInWindow(location);
18 | //显示popupWindow
19 | popwindow.showAtLocation(parent,Gravity.LEFT | Gravity.TOP, DensityUtil.dip2px(getApplicationContext(), location[0] + 70),location[1]);
20 | ```
21 |
22 | 2. 取消
23 | ```java
24 | if (popwindow != null && popwindow.isShowing()) {
25 | popwindow.dismiss();
26 | popwindow = null;
27 | }
28 | ```
29 |
30 | 3. 细节
31 | `PopupWindow`是存在到`Activity`上的,如果`PopupWindow`在显示的时候按退出键的时候该`Activity`已经销毁,但是`PopupWindow`没有销毁,
32 | 所以就报错了(`Logcat`有报错信息但是程序不会崩溃),所以我们在`Activity`的`onDestroy`方法中要判断一下`PopupWindow`是否在显示,如果在显示就取消显示。
33 | `PopupWindow`默认是没有背景的,如果想让它播放动画就没有效果了,因为没有背景就什么也播放不了,所以我们在用这个`PopupWindow`的时候必须要给它设置一个背景,
34 | 通常可以给它设置为透明色,这样再播放动画就有效果了
35 | `popwindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));`
36 |
37 | ## 让`PopupWindow`响应`Back`键后关闭。
38 |
39 | - 最简单
40 | 在`new`的时候,使用下面的方法:
41 | ```java
42 | new PopupWindow(view, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, true);
43 | ```
44 |
45 | 当然你也可以手动设置它:
46 | ```java
47 | mPopupWindow.setFocusable(true);
48 | mPopupWindow.setFocusableInTouchMode(true);
49 | ```
50 |
51 | 此时实际上还是不能起作用的,必须加入下面这行作用未知的语句才能发挥作用:
52 | ```java
53 | mPopupWindow.setBackgroundDrawable(new BitmapDrawable());
54 | ```
55 | 设置 `BackgroundDrawable`并不会改变你在配置文件中设置的背景颜色或图像。
56 |
57 | - 最通用
58 | 首先在布局文件`(*.xml)`中随意选取一个不影响任何操作的`View`,推荐使用最外层的`Layout`。
59 | 然后设置该`Layout`的`Focusable`和`FocusableInTouchMode`都为`true`。
60 | 接着回到代码中对该`View`重写`OnKeyListener()`事件了。捕获`KEYCODE_BACK`给对话框`dismiss()`。
61 |
62 | 给出一段示例:
63 | ```java
64 | private PopupWindow mPopupWindow;
65 | private View view;
66 | private LinearLayout layMenu;
67 |
68 | LayoutInflater inflater = (LayoutInflater) main.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
69 | view = inflater.inflate(R.layout.popup_main_menu, null, false);
70 | layMenu = (LinearLayout) view.findViewById(R.id.layMenu);
71 | mPopupWindow = new PopupWindow(view, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, true);
72 |
73 | layMenu.setOnKeyListener(new OnKeyListener() {
74 | public boolean onKey(View v, int keyCode, KeyEvent event) {
75 | if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK) {
76 | mPopupWindow.dismiss();
77 | }
78 |
79 | return false;
80 | }
81 | });
82 | ```
83 |
84 | ---
85 |
86 | - 邮箱 :charon.chui@gmail.com
87 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/SDK Manager无法更新的问题.md:
--------------------------------------------------------------------------------
1 | SDK Manager无法更新的问题
2 | ===
3 |
4 | 由于伟大的防火墙,大陆访问`Google`服务会无法连接。不过作为程序猿,一般都会科学上网,所以这都不是事。今天这里说明一下普通情况下`SDK Manager`无法更新的问题.
5 |
6 |
7 | - 在更新的时候使用`Http`协议而不是`Https`协议,因为`Https`进行了加密处理,大陆无法审查,所以禁止了,而`Http`协议在过滤的时候发现不是一些禁止内容就没问题了。
8 | 在`SDK Manager`下`Tools->Options`,选中`Force https://… sources to be fetched using http://…`,强制使用`Http`协议.
9 | - 修改`hosts`
10 | `Windows`在`C:\WINDOWS\system32\drivers\etc`目录下,`Linux`用户打开`/etc/hosts`文件打开后添加以下内容:
11 | ```
12 | 203.208.46.146 www.google.com
13 | 74.125.113.121 developer.android.com
14 | 203.208.46.146 dl.google.com
15 | 203.208.46.146 dl-ssl.google.com
16 | ```
17 |
18 |
19 |
20 | ----
21 | - 邮箱 :charon.chui@gmail.com
22 | - Good Luck!
23 |
--------------------------------------------------------------------------------
/BasicKnowledge/Scroller简介.md:
--------------------------------------------------------------------------------
1 | Scroller简介
2 | ===
3 |
4 | 在`SlidingMenu`项目中为了实现控件的滑动,需要用到`Scroller`类来实现缓慢的滑动过程,至于有人说`View`类可以直接调用`scrollTo()`方法,
5 | 这里`scrollTo()`方法也能实现移动,但是它的移动是很快一下子就移过去了,就像穿越一样,直接从现实回到了过去,而`Scroller`类能够实现过程的移动。
6 | 可以理解为一步步的走。
7 |
8 | 1. 查看Scroller源码
9 | ```java
10 | public class Scroller {
11 | //...
12 | }
13 | ```
14 | 发现`Scroller`类并不是`View`的子类,只是一个普通的类,这个类中封装了滚动的操作,记录了滚动的位置以及时间等。
15 | 该类有两个重要的方法:
16 | - `computeScrollOffset()`:
17 | 文档的说明为`Call this when you want to know the new location.`查看源码可以发现,如果在移动到指定位置后就会返回false.正在移动的过程中返回true。
18 | - `startScroll()`:
19 | 该方法的内部实现,并没有具体的移动方法,而是设置了一些移动所需的数据,包括移动持续的时间、开始位置、结束位置等。从而我们可以知道调用`Scroller.startScroll()`方法并没有真正的移动,而是设置了一些数据。
20 |
21 | 2. `Scroller.startScoll()`是如何与`View`的移动相关联呢?在`View`的源码中:
22 | ```java
23 | /**
24 | * Called by a parent to request that a child update its values for mScrollX
25 | * and mScrollY if necessary. This will typically be done if the child is
26 | * animating a scroll using a {@link android.widget.Scroller Scroller}
27 | * object.
28 | */
29 | public void computeScroll() {
30 | }
31 | ```
32 | 通过注释我们可以看到该方法由父类调用根据滚动的值去更新`View`,在使用`Scroller`的时候通常都要实现该方法。来达到子`View`的滚动效果。
33 | 继续往下跟发现在`draw()`方法中会去调用`computeScroll()`,而`draw()`方法会在父布局调用`drawChild()`的时候使用。
34 |
35 | 3. 具体关联
36 | 通过上面两步大体能得到`Scroller`与`View`的移动要通过`computeScroll()`来完成,但是在究竟如何进行代码实现。
37 | `Scroller.startScroll()`方法被调用后会储存要滚动的起始位置、结束位置、持续时间。所以我们可以在`computeScroll()`方法中去判断一下当前是否已经滚动完成,如果没有滚动完成,
38 | 我们就去不断的获取当前`Scroller的位置`,根据这个位置,来把相应的`View`移动到这里。
39 | ```java
40 | public void computeScroll() {
41 | if (mScroller.computeScrollOffset()) {
42 | //如果还没有滚动完成,我们就去让当前的View移动到指定位置去
43 | mCenterView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
44 | //移动完后,我们应该继续调用computeScoll方法去获取并且移动当前View。所以我们调用invalidate方法去请求重绘,这样父类就会调用computeScroll
45 | postInvalidate();
46 | }
47 | }
48 | ```
49 |
50 | ---
51 |
52 | - 邮箱 :charon.chui@gmail.com
53 | - Good Luck!
54 |
--------------------------------------------------------------------------------
/BasicKnowledge/String格式化.md:
--------------------------------------------------------------------------------
1 | String格式化
2 | ===
3 |
4 | - 指定内容替换
5 | - Int类型
6 | 经常会遇到这种类型比如"共为您找到几条视频",我们需要通过代码获取把条数设置进去
7 | 在`string.xml`中可以这样写,`共为您找到%1$d条视频`
8 | ```java
9 | String tip = getResources().getString(R.string.video_num_tip);
10 | // 将`%1$d`替换为8;
11 | tip = String.format(tip, 8);
12 | ```
13 | `%1$d`的意思是整个`video_num_tip`中第一个整型的替代。如果有两个需要替换的整型内容,则第二个写为:`%2$d`,以此类推
14 |
15 | - String类型
16 | 比如“俺叫某某,俺来自某某地,俺为俺自己代言”这里有两个地方需要替换
17 | `俺叫%1$s,俺来自%2$s,俺为俺自己代言`
18 | ```java
19 | String intro = getResources().getString(R.string.introduction);
20 | intro = String.format(intro, "张三","火星");
21 | ```
22 |
23 | - 混合类型
24 | `您已看了%1$d个电影,还差%2$d个即可获得美女%3$s一枚!`
25 | ```java
26 | String text = String.format(getResources().getString(R.string.friendly_tip), 2,18,"苍老师");
27 | ```
28 |
29 | - 颜色改变
30 | ```java
31 | TextView tv = (TextView) findViewById(R.id.tv);
32 | String html =
33 | "强调
"
34 | + "斜体"
35 | + "超链接百度一下,你就知道
"
36 | + "图片
";
37 |
38 | tv.setText(Html.fromHtml("红色其它颜色"));
39 | tv.setText(Html.fromHtml("标题1
"));
40 | //这样会发现图片显示不出来,因为牵扯到图片的时候必须要使用另外一个构造参数
41 | tv.setText(Html.fromHtml(html));
42 |
43 | //图片要用到该构造参数
44 | tv.setText(Html.fromHtml(html, new ImageGetter() {
45 | @Override
46 | public Drawable getDrawable(String source) {
47 | int id = Integer.parseInt(source);
48 | Drawable d = getResources().getDrawable(id);
49 | d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
50 | return d;
51 | }
52 | }, null));
53 | ```
54 |
55 | ---
56 |
57 | - 邮箱 :charon.chui@gmail.com
58 | - Good Luck!
59 |
--------------------------------------------------------------------------------
/BasicKnowledge/TextView跑马灯效果.md:
--------------------------------------------------------------------------------
1 | TextView跑马灯效果
2 | ===
3 |
4 | TextView跑马灯效果实现方式一:
5 | ---
6 |
7 | 当`TextView`内容过多时默认会采用截取的方式以`...`来截取。如何能够实现内容过多时的跑马灯效果。
8 |
9 | 自定义视图步骤:
10 | ----
11 |
12 | 1. 自定义一个类继承`TextView`,重写它的`isFocused()`方法
13 | 2. 在布局的文件中使用自定义的`TextView`
14 |
15 |
16 | 示例代码:
17 | ----
18 |
19 | 1. 继承TextView
20 | ```java
21 | //继承TextView并且实现抽象方法
22 | public class FocusedTextView extends TextView {
23 |
24 | public FocusedTextView(Context context, AttributeSet attrs, int defStyle){
25 | super(context, attrs, defStyle);
26 | }
27 |
28 | public FocusedTextView(Context context, AttributeSet attrs) {
29 | super(context, attrs);
30 | }
31 |
32 | public FocusedTextView(Context context) {
33 | super(context);
34 | }
35 |
36 | //重写isFocused方法,让其一直返回true
37 | public boolean isFocused() {
38 | return true;
39 | }
40 | }
41 | ```
42 |
43 | 2. 在清单文件中使用该类
44 | ```xml
45 |
51 | ```
52 |
53 | TextView跑马灯效果实现方式二:
54 | ---
55 |
56 | TextView实现跑马灯的效果,不用自定义View
57 | ```xml
58 |
67 | ```
68 |
69 | ---
70 | - 邮箱 :charon.chui@gmail.com
71 | - Good Luck!
72 |
--------------------------------------------------------------------------------
/BasicKnowledge/Wifi状态监听.md:
--------------------------------------------------------------------------------
1 | Wifi状态监听
2 | ===
3 |
4 | ```java
5 | /**
6 | * 监控Wifi状态的广播接收器
7 | */
8 | private final class WifiStateReceiver extends BroadcastReceiver {
9 | @Override
10 | public void onReceive(Context c, Intent intent) {
11 | Bundle bundle = intent.getExtras();
12 | int statusInt = bundle.getInt("wifi_state");
13 | switch (statusInt) {
14 | case WifiManager.WIFI_STATE_UNKNOWN:
15 | break;
16 | case WifiManager.WIFI_STATE_ENABLING:
17 | break;
18 | case WifiManager.WIFI_STATE_ENABLED:
19 | LogUtil.e(tag, "wifi enable");
20 | if(!isWifiEnable) {
21 | isWifiEnable = true;
22 | //断网后又连上了
23 | isGoon = false;
24 | if (!Util.isServiceRun(MultiPointControlActivity.this,
25 | DLNAServiceName)) {
26 | LogUtil.e(tag, "start dlna service");
27 | }else {
28 | LogUtil.e(tag, "runing .... stop dlna service");
29 | stopDLNAService();
30 | }
31 | startDLNAService();
32 | firstPlay();
33 | }
34 | break;
35 | case WifiManager.WIFI_STATE_DISABLING:
36 | break;
37 | case WifiManager.WIFI_STATE_DISABLED:
38 | isWifiEnable = false;
39 | LogUtil.e(tag, "wifi disable");
40 | break;
41 | default:
42 | break;
43 | }
44 | }
45 | }
46 |
47 | private void registReceiver() {
48 | receiver = new WifiStateReceiver();
49 | IntentFilter filter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
50 | registerReceiver(receiver, filter);
51 | }
52 | ```
53 |
54 | ---
55 |
56 | - 邮箱 :charon.chui@gmail.com
57 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/XmlPullParser.md:
--------------------------------------------------------------------------------
1 | XmlPullParser
2 | ===
3 |
4 | ```java
5 | public class PersonService {
6 | /**
7 | * 接收一个包含XML文件的输入流, 解析出XML中的Person对象, 装入一个List返回
8 | * @param in 包含XML数据的输入流
9 | * @return 包含Person对象的List集合
10 | */
11 | public List getPersons(InputStream in) throws Exception {
12 | //1.获取xml文件
13 | InputStream is = PersonService.class.getClassLoader().getResourceAsStream();
14 | //2.获取解析器(Android中提供了方便的方法就是使用Android中的工具类Xml)
15 | XmlPullParser parser = Xml.newPullParser();
16 | //3.解析器解析xml文件
17 | parser.setInput(in, "UTF-8");
18 |
19 | List persons = new ArrayList();
20 | Person p = null;
21 | //4.循环解析
22 | for (int type = parser.getEventType(); type != XmlPullParser.END_DOCUMENT; type = parser.next()) { // 循环解析
23 | if (type == XmlPullParser.START_TAG) { // 判断如果遇到开始标签事件
24 | if ("person".equals(parser.getName())) { // 标签名为person
25 | p = new Person(); // 创建Person对象
26 | String id = parser.getAttributeValue(0); // 获取属性
27 | p.setId(Integer.parseInt(id)); // 设置ID
28 | persons.add(p); // 把Person对象装入集合
29 | } else if ("name".equals(parser.getName())) { // 标签名为name
30 | String name = parser.nextText(); // 获取下一个文本
31 | p.setName(name); // 设置name
32 | } else if ("age".equals(parser.getName())) { // 标签名为age
33 | String age = parser.nextText(); // 获取下一个文本
34 | p.setAge(Integer.parseInt(age)); // 设置age
35 | }
36 | }
37 | }
38 | return persons;
39 | }
40 |
41 | /**
42 | *将数据写入到Xml文件中.
43 | *@param out 输出到要被写入数据的Xml文件的输出流//就相当于 OutputStream os = new FileOutputStream("a.xml");
44 | */
45 | public void writePersons(List Books, OutputStream out) throws Exception {
46 |
47 | //1.获得XmlSerializer(Xml序列化工具)(通过Android中的工具类Xml得到)
48 | XmlSerializer serializer = Xml.newSerializer();
49 | //2.设置输出流(明确要将数据写入那个xml文件中)
50 | serializer.setOutput(out, "UTF-8");
51 | //3.写入开始文档
52 | serializer.startDocument("UTF-8", true);
53 | //4.开始标签
54 | serializer.startTag(null, "bookstore");
55 | //5.循环遍历
56 | for (Book p : books) {
57 | //6.开始标签
58 | serializer.startTag(null, "book");
59 | //7.给这个标签设置属性
60 | serializer.attribute(null, "id", book.getId().toString());
61 | //8.子标签
62 | serializer.startTag(null, "name");
63 | //9.设置子标签的内容
64 | serializer.text(book.getName());
65 | //10.子标签结束
66 | serializer.endTag(null, "name");
67 | //11.标签结束
68 | serializer.endTag(null, "person");
69 | }
70 | //12.根标签结束
71 | serializer.endTag(null, "persons");
72 | //13.文档结束
73 | serializer.endDocument();
74 | }
75 | }
76 | ```
77 |
78 | ---
79 | - 邮箱 :charon.chui@gmail.com
80 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/反编译.md:
--------------------------------------------------------------------------------
1 | 反编译
2 | ===
3 |
4 | - [资源文件获取Apktool](https://ibotpeaches.github.io/Apktool/install/)
5 | 按照官网的指示配置完成后,执行apktool命令
6 |
7 | ```
8 | apktool d xxx.apk
9 | // 如果提示-bash: /usr/local/bin/apktool: Permission denied
10 | cd /usr/local/bin
11 | sudo chmod +x apktool
12 | sudo chmod +x apktool.jar
13 | ```
14 | 执行完成后会在apktool.jar的目录下升级一个文件,里面可以看到xml的信息
15 | ```
16 | cd /usr/loca/bin
17 | open .
18 | ```
19 | - [源码文件获取dex2jar](https://github.com/pxb1988/dex2jar)
20 | 将apk文件解压后获取class.dex文件,然后将dex文件拷贝到dex2jar目录中
21 | ```
22 | sh d2j-dex2jar.sh classes.dex
23 | d2j-dex2jar.sh: line 36: ./d2j_invoke.sh: Permission denied
24 | // chmod一下
25 | sudo chmod +x d2j_invoke.sh
26 | sh d2j-dex2jar.sh classes.dex
27 | ```
28 | 执行完成后会在当前目录中生成一个 classes-dex2jar.jar
29 |
30 | - [jar包源码查看工具jd-gui](https://github.com/java-decompiler/jd-gui)
31 | 里面有osx的版本,安装后直接打开上面用dex2jar编译出来的.jar文件就可以查看源码了
32 |
33 |
34 | ### 反编译后的源码修改
35 |
36 | 修改代码及资源,最好的方式是修改apktool反编译后的资源级smali代码。JD-GUI查看的java代码不适宜修改,因为修改后还需要重新转换成smali,才能重新编译打包会apk。
37 | 至于smali的修改,则要学习smali语言的语法了,smali是一种类似汇编语言的语言,具体语法可自行上网学习。
38 | 在smali文件夹中找到与具体类对应的smali文件,然后进行修改
39 | 可以用到[java2smali](https://plugins.jetbrains.com/plugin/7385-java2smali)将java代码转换成smali代码
40 |
41 | ### 重新打包
42 |
43 | ```
44 | B0000000134553m:bin xuchuanren$ apktool b xxxfilename
45 | I: Using Apktool 2.4.0
46 | I: Checking whether sources has changed...
47 | I: Smaling smali folder into classes.dex...
48 | I: Checking whether resources has changed...
49 | I: Building resources...
50 | S: WARNING: Could not write to ( instead...
51 | S: Please be aware this is a volatile directory and frameworks could go missing, please utilize --frame-path if the default storage directory is unavailable
52 | I: Copying libs... (/lib)
53 | I: Building apk file...
54 | I: Copying unknown files/dir...
55 | I: Built apk...
56 |
57 | ```
58 | 生成的apk在该文件xxxfilename中的dist目录中
59 |
60 | ### 重新签名
61 |
62 | 重新签名用的是jarsigner命令
63 | ```
64 | jarsigner -verbose -keystore [your_key_store_path] -signedjar [signed_apk_name] [usigned_apk_name] [your_key_store_alias] -digestalg SHA1 -sigalg MD5withRSA
65 | ```
66 |
67 | - [your_key_store_path]:密钥所在位置的绝对路径
68 | - [signed_apk_name]:签名后安装包名称
69 | - [usigned_apk_name]:未签名的安装包名称
70 | - [your_key_store_alias]:密钥的别名
71 |
72 | 如:
73 |
74 | ```
75 | jarsigner -verbose -keystore /development/key.keystore -signedjar signed_apk.apk xxx.apk charon -digestalg SHA1 -sigalg MD5withRSA
76 | Enter Passphrase for keystore:
77 | adding: META-INF/MANIFEST.MF
78 | adding: META-INF/CHARON.SF
79 | adding: META-INF/CHARON.RSA
80 |
81 | ```
82 | 执行完后会在当前目录下生成签名后的apk文件
83 |
84 |
85 | ---
86 |
87 | - 邮箱 :charon.chui@gmail.com
88 | - Good Luck!
89 |
--------------------------------------------------------------------------------
/BasicKnowledge/安全退出应用程序.md:
--------------------------------------------------------------------------------
1 | 安全退出应用程序
2 | ===
3 |
4 | 1. 杀死进程。
5 | 这种方法是没有效果的只能杀死当前的`Activity`无法关闭程序,在1.5的时候有用,谷歌设计的时候规定程序不能自杀
6 | `android.os.Process.killProcess(android.os.Process.myPid())`.
7 | 2. 终止当前正在运行的Java虚拟机,导致程序终止.
8 | 这种方法也是没有效果的,因为`Android`用的是`dalvik`虚拟机
9 | `System.exit(0);`
10 | 3. 强制关闭与该包有关联的一切执行
11 | 这种方法只能杀死别人,无法杀死自己
12 | ```java
13 | ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
14 | manager.restartPackage(getPackageName());
15 | 同时需要申请权限
16 |
17 | ```
18 |
19 | 既然上面介绍的三种方法都没有效果,那么怎么才能退出应用程序呢?
20 | 就是自定义一个`Application`,在该`Application`中去定义一个`List`的集合来记录中每一个开启的`Activity`,在退出的时候去遍历这个`List`集合,
21 | 然后挨个的进行`mActivity.finish()`方法,这要求在每开启一个`Activity`的时候都加入到`List`集合中,并且在`Activity`退出的时候从`List`集合中将其移除。
22 | ```java
23 | public class Activity01 extends Activity {
24 |
25 | @Override
26 | public void onCreate(Bundle savedInstanceState) {
27 | super.onCreate(savedInstanceState);
28 | setContentView(R.layout.main);
29 | MyApp myApp = (MyApp) getApplication();
30 | myApp.activies.add(this);
31 | }
32 |
33 | @Override
34 | protected void onDestroy() {
35 | super.onDestroy();
36 | MyApp myApp = (MyApp) getApplication();
37 | myApp.activies.remove(this);
38 | }
39 |
40 | public void click1(View view){
41 | Intent intent = new Intent(this,Activity01.class);
42 | startActivity(intent);
43 | }
44 | public void click2(View view){
45 | Intent intent = new Intent(this,Activity02.class);
46 | startActivity(intent);
47 | }
48 |
49 | public void exit(View view){
50 | MyApp myApp = (MyApp) getApplication();
51 | for(Activity ac : myApp.activies){
52 | ac.finish();
53 | }
54 | }
55 | }
56 |
57 | public class Activity02 extends Activity {
58 |
59 | @Override
60 | public void onCreate(Bundle savedInstanceState) {
61 | super.onCreate(savedInstanceState);
62 | setContentView(R.layout.main2);
63 | MyApp myApp = (MyApp) getApplication();
64 | myApp.activies.add(this);
65 | }
66 |
67 | public void click1(View view) {
68 | Intent intent = new Intent(this, Activity01.class);
69 | startActivity(intent);
70 | }
71 |
72 | public void click2(View view) {
73 | Intent intent = new Intent(this, Activity02.class);
74 | startActivity(intent);
75 | }
76 |
77 | public void exit(View view) {
78 | MyApp myApp = (MyApp) getApplication();
79 | for (Activity ac : myApp.activies) {
80 | ac.finish();
81 | }
82 | }
83 |
84 | @Override
85 | protected void onDestroy() {
86 | super.onDestroy();
87 | MyApp myApp = (MyApp) getApplication();
88 | myApp.activies.remove(this);
89 | }
90 | }
91 |
92 | public class MyApp extends Application {
93 | //存放当前应用程序里面打开的所有的activity
94 | public List activies;
95 | @Override
96 | public void onCreate() {
97 | activies = new ArrayList();
98 | super.onCreate();
99 | }
100 | }
101 | ```
102 |
103 | ---
104 |
105 | - 邮箱 :charon.chui@gmail.com
106 | - Good Luck!
107 |
--------------------------------------------------------------------------------
/BasicKnowledge/应用后台唤醒后数据的刷新.md:
--------------------------------------------------------------------------------
1 | 应用后台唤醒后数据的刷新
2 | ===
3 |
4 | 1. 如何判断程序是否是在后台运行了
5 | ```java
6 | /**
7 | * 判断当前的应用程序是否在后台运行,使用该程序需要声明权限android.permission.GET_TASKS
8 | * @param context Context
9 | * @return true表示当前应用程序在后台运行。false为在前台运行
10 | */
11 | public static boolean isApplicationBroughtToBackground(Context context) {
12 | ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
13 | List tasks = am.getRunningTasks(1);
14 | if (tasks != null && !tasks.isEmpty()) {
15 | ComponentName topActivity = tasks.get(0).topActivity;
16 | if (!topActivity.getPackageName().equals(context.getPackageName())) {
17 | return true;
18 | }
19 | }
20 | return false;
21 | }
22 | ```
23 |
24 | 2. 在Activity中的onStop方法中去判断当前的应用程序是否在后台运行,同时用一个成员变量去记录该Activity是否为后台,
25 | 在onResume方法中去判断记录程序后台的变量是否为true,如为true就说明现在是程序从后台切换到前台了,这时候就要去刷新数据了
26 | ```java
27 | /**
28 | * 用于记录当前应用程序是否在后台运行,这样只作用于一个Activity,如果想让所有的
29 | * Activity都知道程序从后台到前台了,这时候要弄一个基类BaseActivity了,在
30 | * BaseActivity中去执行这些代码,让其他的Activity都继承该BaseActivity。并且要将
31 | * isApplicationBroughtToBackground变成static的。
32 | * 然后在onResume方法中不要执行isApplicationBroughtToBackground = false;这样其他
33 | * Activity在onResume方法中判断时就知道应用是从后台切换到前台的了,不用担心这样会导
34 | * 致isApplicationBroughtToBackground无法恢复为false,因为在onStop方法中,我们
35 | * 判断了如果现在程序不是后台,就将isApplicationBroughtToBackground 变为false了
36 | */
37 | private boolean isApplicationBroughtToBackground;
38 |
39 | @Override
40 | protected void onStop() {
41 | super.onStop();
42 | if(isApplicationBroughtToBackground(this)) {
43 | //程序后台了
44 | LogUtil.i(TAG, "后台了...");
45 | isApplicationBroughtToBackground = true;
46 | }else {
47 | LogUtil.i(TAG, "木有后台");
48 | isApplicationBroughtToBackground = false;
49 | }
50 |
51 | }
52 |
53 | protected void onResume() {
54 | super.onResume();
55 | if(isApplicationBroughtToBackground) {
56 | //从后台切换到前台了
57 | LogUtil.i(TAG, "从后台切换到前台了,刷新数据");
58 | loadFocusInfoData();
59 | }
60 | isApplicationBroughtToBackground = false;
61 | };
62 | ```
63 |
64 | ---
65 | - 邮箱 :charon.chui@gmail.com
66 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/应用安装.md:
--------------------------------------------------------------------------------
1 | 应用安装
2 | ===
3 |
4 | 1. 在应用程序中安装程序需要权限
5 | ` `
6 |
7 | 2. 示例代码
8 | 安卓中提供了安装程序的功能,我们只要启动安装程序的Activity,并把我们的数据传入即可。
9 | ```java
10 | //获取到要安装的apk文件的File对象
11 | File file = new File(Environment.getExternalStorageDirectory(), "test.apk");
12 | //创建一个意图
13 | Intent intent = new Intent();
14 | //设置意图动作
15 | intent.setAction(Intent.ACTION_VIEW); //android.intent.action.VIEW
16 | //设置意图数据和类型
17 | intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
18 | //启动安装程序的Activity
19 | startActivity(intent);
20 | ```
21 |
22 | **Tip:**
23 | - `Uri.fromFile(File file)`方法能从一个`File`对象得到它的`Uri`
24 | - `Intent`有`setData(Uri uri)`和`setType(String type)`方法,但是这里如果我们分开写就会报错,
25 | 原因是`setData()`方法在执行的时候会自动清空所有在此之前调用的`setType`方法所设置过的type,
26 | 同样`setType`方法在执行的时候也会自动清空所有在此之前调用`setData`设置的`Data`,所以这里必须使用`setDataAndType`方法而不能分开使用`setData`和`setType`.
27 | - `Android`中提供了安装应用程序的功能,在`Android`系统源码中`apps/PackageInstaller`中。我们找到这个`PackageInstaller`的清单文件,
28 | 然后找到`PackageInstallerActivity`来查找该`Activity`的意图:如下
29 | `android_source/packages/apps/PackageInstaller/AndroidManifest.xml`
30 | ```xml
31 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | ```
50 |
51 | ---
52 |
53 | - 邮箱 :charon.chui@gmail.com
54 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/开发中Log的管理.md:
--------------------------------------------------------------------------------
1 | 开发中Log的管理
2 | ===
3 |
4 | **LogUtil**是一个管理`Log`打印的工具类。在开发的不同阶段中通过对该类的控制来实现不同级别`Log`的打印。
5 | ```java
6 | public class LogUtil {
7 | public static final int VERBOSE = 5;
8 | public static final int DEBUG = 4;
9 | public static final int INFO = 3;
10 | public static final int WARN = 2;
11 | public static final int ERROR = 1;
12 |
13 | /** 通过改变该值来进行不同级别的Log打印,上线的时将该值改为0来关闭所有log打印 */
14 | public final static int LOG_LEVEL = 6;
15 |
16 | public static void v(String tag, String msg) {
17 | if (LOG_LEVEL > VERBOSE) {
18 | Log.v(tag, msg);
19 | }
20 | }
21 |
22 | public static void d(String tag, String msg) {
23 | if (LOG_LEVEL > DEBUG) {
24 | Log.d(tag, msg);
25 | }
26 | }
27 |
28 | public static void i(String tag, String msg) {
29 | if (LOG_LEVEL > INFO) {
30 | Log.i(tag, msg);
31 | }
32 | }
33 |
34 | public static void w(String tag, String msg) {
35 | if (LOG_LEVEL > WARN) {
36 | Log.w(tag, msg);
37 | }
38 | }
39 |
40 | public static void e(String tag, String msg) {
41 | if (LOG_LEVEL > ERROR) {
42 | Log.e(tag, msg);
43 | }
44 | }
45 | }
46 | ```
47 |
48 | ---
49 |
50 | - 邮箱 :charon.chui@gmail.com
51 | - Good Luck!
52 |
--------------------------------------------------------------------------------
/BasicKnowledge/开发中异常的处理.md:
--------------------------------------------------------------------------------
1 | 开发中异常的处理
2 | ===
3 |
4 | 
5 |
6 | 1. 实现未捕捉异常处理器
7 |
8 | ```java
9 | public class MyExceptionHandler implements UncaughtExceptionHandler {
10 | private static final String TAG = "MyExceptionHandler";
11 | @Override
12 | public void uncaughtException(Thread arg0, Throwable arg1) {
13 | Logger.i(TAG, "发生了异常,但是被哥捕获了...");
14 | try {
15 | Field[] fields = Build.class.getDeclaredFields();//可以通过Build的属性来获取到手机的硬件信息,由于不同手机的硬件信息部一定有,所以要用反射得到
16 | StringBuffer sb = new StringBuffer();
17 | for(Field field: fields){
18 | String info = field.getName()+ ":"+field.get(null)+"\n";
19 | sb.append(info);
20 | }
21 | StringWriter sw = new StringWriter();
22 | PrintWriter pw = new PrintWriter(sw);
23 | arg1.printStackTrace(pw);//通过这个来得到异常信息
24 | String errorlog = sw.toString();
25 |
26 | File file = new File(Environment.getExternalStorageDirectory(),
27 | "error.log");
28 | FileOutputStream fos = new FileOutputStream(file);
29 | sb.append(errorlog);
30 | fos.write(sb.toString().getBytes());
31 | fos.close();
32 | } catch (Exception e) {
33 | e.printStackTrace();
34 | }
35 | android.os.Process.killProcess(android.os.Process.myPid());//这个是只能杀死自己不能杀死别人,这时候系统发现程序在自己的范围之内死了,
36 | 系统就会重启程序到出现错误之前的那个Activity。
37 | }
38 | }
39 | ```
40 |
41 | 2. 让这个处理器生效
42 |
43 | ```java
44 | /**
45 | * 代表的是当前应用程序的进程.
46 | */
47 | public class MobliesafeApplication extends Application {
48 | public BlackNumberInfo info;
49 |
50 | @Override
51 | public void onCreate() {
52 | super.onCreate();
53 | Thread.currentThread().setUncaughtExceptionHandler(new MyExceptionHandler());//这样就能够让异常的处理器设置到我们的程序中
54 | }
55 | }
56 | ```
57 |
58 | ---
59 |
60 | - 邮箱 :charon.chui@gmail.com
61 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/文件上传.md:
--------------------------------------------------------------------------------
1 | 文件上传
2 | ===
3 |
4 | 1. HttpClient模拟表单上传
5 | 如果`Android`中自带的`HttpClient`不能实现上传的功能,就下载`HttpClient 3.1`版本
6 |
7 | ```java
8 | public void upload(View view){
9 | HttpClient client = new HttpClient();
10 | PostMethod filePost = new PostMethod("http://192.168.1.100:8080/web/UploadServlet");;
11 | try {
12 | String path = et_path.getText().toString().trim();
13 | File file = new File(path);
14 | if(file.exists()&&file.length()>0){
15 | Part[] parts = {new StringPart("nameaaaa", "valueaaa"),
16 | new StringPart("namebbb", "valuebbb"),
17 | new FilePart("pic", new File(file.getAbsolutePath()))};
18 | filePost.setRequestEntity(new MultipartRequestEntity(parts, filePost.getParams()));
19 | client.getHttpConnectionManager().getParams()
20 | .setConnectionTimeout(5000);
21 | int status = client.executeMethod(filePost);
22 | if(status ==200){
23 | Toast.makeText(this, "上传成功", 1).show();
24 | }else{
25 | Toast.makeText(this, "上传失败", 1).show();
26 | }
27 | }
28 | else{
29 | Toast.makeText(this, "上传的文件不存在", 0).show();
30 | }
31 | } catch (Exception e) {
32 | e.printStackTrace();
33 | /**如果出现异常一定记得要释放*/
34 | filePost.releaseConnection();
35 | }
36 | }
37 | ```
38 |
39 | 2. 模拟Http请求上传
40 |
41 |
42 | ---
43 |
44 | - 邮箱 :charon.chui@gmail.com
45 | - Good Luck!
46 |
--------------------------------------------------------------------------------
/BasicKnowledge/来电监听及录音.md:
--------------------------------------------------------------------------------
1 | 来电监听及录音
2 | ===
3 |
4 | 1. 来电状态监听
5 | ```java
6 | public class MyService extends Service {
7 |
8 | private MediaRecorder mRecorder;
9 | private String num;
10 |
11 | @Override
12 | public void onCreate() {
13 | super.onCreate();
14 | TelephonyManager manager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); // 获取电话管理器
15 | manager.listen(new MyPhoneStateListener(), PhoneStateListener.LISTEN_CALL_STATE); // 监听电话状态
16 | }
17 |
18 | private class MyPhoneStateListener extends PhoneStateListener { // 电话状态监听器
19 | public void onCallStateChanged(int state, String incomingNumber) { // 在电话状态改变时执行
20 | switch (state) {
21 | case TelephonyManager.CALL_STATE_RINGING: // 振铃
22 | System.out.println("来电话了");
23 | num = incomingNumber;//得到来电号码
24 | break;
25 | case TelephonyManager.CALL_STATE_OFFHOOK: // 摘机
26 | System.out.println("开始通话");
27 | start();
28 | break;
29 | case TelephonyManager.CALL_STATE_IDLE: // 空闲
30 | System.out.println("挂断电话");
31 | stop();
32 | break;
33 | }
34 | }
35 | }
36 |
37 | public void onDestroy() {
38 | tm.listen(listener, PhoneStateListener.LISTEN_NONE);// 取消监听
39 | listener = null;
40 | }
41 | ```
42 |
43 | 2. 录音
44 | ```java
45 | private void start() {
46 | mRecorder = new MediaRecorder(); // 创建媒体记录器
47 | mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL); // 指定音频源(麦克风)
48 | mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); // 设置输出格式(3gp)
49 | mRecorder.setOutputFile("/mnt/sdcard/" + num + "-" + System.currentTimeMillis() + ".3gp"); // 指定文件路径(SD卡)
50 | mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); // 指定编码(AMR_NB)
51 | try {
52 | mRecorder.prepare(); // 准备
53 | } catch (Exception e) {
54 | e.printStackTrace();
55 | }
56 | mRecorder.start(); // 开始
57 | }
58 |
59 | @Override
60 | public IBinder onBind(Intent intent) {
61 | return null;
62 | }
63 |
64 | //下面是录音
65 | private void stop() {
66 | if (mRecorder != null) {
67 | mRecorder.stop(); // 停止
68 | mRecorder.release(); // 释放
69 | mRecorder = null; // 垃圾回收
70 | }
71 | }
72 | ```
73 |
74 | ----
75 | - 邮箱 :charon.chui@gmail.com
76 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/短信广播接收者.md:
--------------------------------------------------------------------------------
1 | 短信广播接收者
2 | ===
3 |
4 | 短信拦截
5 | ---
6 |
7 | `Android`系统在收到短信的时候会发送一条有序广播,我们如果定义一个接收者接收这个广播,就可以得到短信内容,也可以拦截短信。
8 | 定义广播接收者接收广播`android.provider.Telephony.SMS_RECEIVED`
9 | 需要接收短信权限:``
10 | `Android`系统中收到短信的通知是一个**有序广播**,我们如需拦截垃圾短信,可以配置较高的`Priority`,收到信息进行判断是否`abortBroadcast()`
11 |
12 | ```java
13 | public class SmsReceiver extends BroadcastReceiver {
14 |
15 | @Override
16 | public void onReceive(Context context, Intent intent) {
17 | Object[] pdus = (Object[]) intent.getExtras().get("pdus"); // 获取短信数据(可能有多段)
18 | for (Object pdu : pdus) {
19 | SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdu); // 把短信数据封装成SmsMessage对象
20 | Date date = new Date(sms.getTimestampMillis()); // 短信时间
21 | String address = sms.getOriginatingAddress(); // 获取发信人号码
22 | String body = sms.getMessageBody(); // 短信内容
23 |
24 | System.out.println(date + ", " + address + ", " + body);
25 | if ("18600012345".equals(address)) {
26 | abortBroadcast();
27 | return;
28 | }
29 | }
30 | }
31 | }
32 | ```
33 | ---
34 |
35 | - 邮箱 :charon.chui@gmail.com
36 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/程序的启动、卸载和分享.md:
--------------------------------------------------------------------------------
1 | 程序的启动、卸载和分享
2 | ===
3 |
4 | 1. 启动
5 |
6 | ```java
7 | /**
8 | * 开启一个应用程序
9 | */
10 | private void startApk() {
11 | PackageManager pm = getPackageManager();
12 | try {
13 | // 原来的时候我们在得到PakageInfo的时候第二个参数都是设置为0.这个PackageInfo代表的就是某个程序的清单文件,
14 | // 默认情况下在解析这个清单文件的时候得到的只是清单文件中的一些版本信息的等这些常用的内容,因为要获取更多的内容需要解析更多的内容,
15 | // 就会消耗时间消耗资源,所以默认的时候都是只解析一些常用的,当我们要获取Activity等这些的时候就要给它一个标记,让它知道多解析这些你想要得到的内容,
16 | // 如果我们想得到里面的activity或者service等这些啊就必须将第二个参数设置为相应的PackageManager.GET_ACTIVITYS等
17 | PackageInfo info = pm.getPackageInfo(selectedAppInfo.getPackname(),PackageManager.GET_ACTIVITIES);
18 | ActivityInfo[] activityInfos = info.activities;//获取清单中所有Activity信息的数据
19 | if (activityInfos != null && activityInfos.length > 0) {//由于一些服务或者接收者等没有Activity所以这里必须进行判断
20 | ActivityInfo activitInfo = activityInfos[0];//清单文件中配置的第一个Activity就是程序的启动Activity
21 | Intent intent = new Intent();
22 | intent.setClassName(selectedAppInfo.getPackname(),activitInfo.name);//这个activityInfo就是清单中activity节点的name,这样就能得到Activity的全类名
23 | startActivity(intent);
24 | } else {
25 | Toast.makeText(this, "无法启动应用程序", 0).show();
26 | }
27 | }
28 | }
29 | ```
30 |
31 | 2. 卸载
32 |
33 | ```java
34 | 安卓系统提供了程序的卸载Activity,我们只要调用它的卸载就可以了,也是系统的PackageInstaller中的
35 | Intent intent = new Intent();
36 | intent.setAction("android.intent.action.VIEW");
37 | intent.setAction("android.intent.action.DELETE");
38 | intent.addCategory("android.intent.category.DEFAULT");
39 | intent.setData(Uri.parse("package:" + selectedAppInfo.getPackname()));//意图的数据必须是package://和包名
40 | startActivity(intent);
41 | ```
42 |
43 | 3. 分享
44 |
45 | 就是启动出来信息的发送页面,将内容给填充进去所以这里要启动系统发送短信的Activity,要用到系统发送短信的Activity
46 |
47 | ```java
48 | /**
49 | * 分享一个应用程序
50 | */
51 | private void shareApk() {
52 | Intent intent = new Intent();
53 | //
54 | //
55 | //
56 | //
57 | //
58 | intent.setAction(Intent.ACTION_SEND);
59 | intent.addCategory(Intent.CATEGORY_DEFAULT);
60 | intent.setType("text/plain");
61 | intent.putExtra(
62 | Intent.EXTRA_TEXT,
63 | "推荐你使用一款软件,名称为" + selectedAppInfo.getAppName()
64 | + "下载地址:google play xxx,版本:"
65 | + selectedAppInfo.getVersion());
66 | startActivity(intent);
67 | }
68 | ```
69 |
70 | 谷歌工程师在设计这个程序的时候,任何应用程序如果想使用分享的功能都可以通过实现它的Intent来实现,点击的时候可以选择不同的程序,同样也能分享到微博,
71 | 邮件等程序
72 |
73 | ---
74 |
75 | - 邮箱 :charon.chui@gmail.com
76 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/竖着的Seekbar.md:
--------------------------------------------------------------------------------
1 | 竖着的Seekbar
2 | ===
3 |
4 | 视频播放器页面音量控制`Seekbar`实现竖直的效果。竖直只是将`Seekbar`转了90度或-90度,我们可以把画布转一个角度,然后交给系统去画,
5 | 具体的做法就是重写`ondraw()`调整画布,然后调用`super.onDraw()`。
6 |
7 | - 向上的Seekbar
8 | ```java
9 | protected void onDraw(Canvas c) {
10 | c.rotate(-90);
11 | c.translate(-height,0);//height是你的verticalseekbar的高
12 | super.onDraw(c);
13 | }
14 | ```
15 |
16 | - 向下的seekbar则应该是:
17 | ```java
18 | protected void onDraw(Canvas c) {
19 | c.rotate(90);
20 | c.translate(0,-width);//width是你的verticalseekbar的宽
21 | super.onDraw(c);
22 | }
23 | ```
24 | - 示例代码
25 | ```java
26 | public class VerticalSeekBar extends SeekBar {
27 | public VerticalSeekBar(Context context) {
28 | super(context);
29 | }
30 | public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
31 | super(context, attrs, defStyle);
32 | }
33 | public VerticalSeekBar(Context context, AttributeSet attrs) {
34 | super(context, attrs);
35 | }
36 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
37 | super.onSizeChanged(h, w, oldh, oldw);
38 | }
39 |
40 | @Override
41 | protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
42 | super.onMeasure(heightMeasureSpec, widthMeasureSpec);
43 | setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
44 | }
45 |
46 | protected void onDraw(Canvas c) {
47 | c.rotate(-90);
48 | c.translate(-getHeight(), 0);
49 | super.onDraw(c);
50 | }
51 |
52 | @Override
53 | public synchronized void setProgress(int progress) {
54 | super.setProgress(progress);
55 | onSizeChanged(getWidth(), getHeight(), 0, 0);
56 | }
57 |
58 | @Override
59 | public boolean onTouchEvent(MotionEvent event) {
60 | if (!isEnabled()) {
61 | return false;
62 | }
63 | switch (event.getAction()) {
64 | case MotionEvent.ACTION_DOWN:
65 | case MotionEvent.ACTION_MOVE:
66 | case MotionEvent.ACTION_UP:
67 | setProgress((int) ((int) getMax()- (getMax() * event.getY() / getHeight())));
68 | break;
69 | default:
70 | return super.onTouchEvent(event);
71 | }
72 | return true;
73 | }
74 | }
75 | ```
76 |
77 | ---
78 |
79 | - 邮箱 :charon.chui@gmail.com
80 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/自定义背景.md:
--------------------------------------------------------------------------------
1 | 自定义背景
2 | ===
3 |
4 | 1. 自定义一个背景颜色,让颜色从左到右变化的那种
5 | 在`res-drawable`目录下新建一个`xml`文件。里面`xml`文件内容的根节点是`shape`
6 | ```xml
7 |
8 | //指定样式这里rectangle就是指定长方形
10 | //指定边角的弧度
11 | //颜色渐变,这里指定了开始、中间、结束三个颜色,样式就会从开始的比较淡变成中间的比较深,
12 |
16 |
17 |
21 |
22 |
27 |
28 | ```
29 |
30 | 2. 自定义一个实心的小圆点
31 | ```xml
32 |
33 | //指定是椭圆,我们只要在使用的时候讲宽高设置为相等就是园了
35 |
36 | //填充颜色
37 |
38 | ```
39 |
40 | ---
41 |
42 | - 邮箱 :charon.chui@gmail.com
43 | - Good Luck!
44 |
--------------------------------------------------------------------------------
/BasicKnowledge/获取位置(LocationManager).md:
--------------------------------------------------------------------------------
1 | 获取位置(LocationManager)
2 | ===
3 |
4 | 1. 需要申请权限
5 | ```xml
6 |
7 |
8 | ```
9 |
10 | 2. 代码
11 | ```java
12 | public class TestgpsActivity extends Activity {
13 | private LocationManager lm;
14 | private MyListener listener;
15 |
16 | @Override
17 | public void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.main);
20 | lm = (LocationManager) getSystemService(LOCATION_SERVICE);
21 |
22 | Criteria criteria = new Criteria();
23 | criteria.setAccuracy(Criteria.ACCURACY_FINE);
24 | criteria.setCostAllowed(true);
25 | criteria.setPowerRequirement(Criteria.POWER_HIGH);
26 | criteria.setSpeedRequired(true);
27 | String provider = lm.getBestProvider(criteria, true);
28 | //第一个参数 位置提供者 第二个参数 最短更新时间 第三参数 最短的更新的距离
29 | listener = new MyListener();
30 | lm.requestLocationUpdates(provider, 0, 0, listener);
31 |
32 | }
33 | @Override
34 | protected void onDestroy() {
35 | lm.removeUpdates(listener);
36 | super.onDestroy();
37 | }
38 |
39 | private class MyListener implements LocationListener{
40 |
41 | /**
42 | * 当位置改变的时候
43 | */
44 | @Override
45 | public void onLocationChanged(Location location) {
46 | float accuracy = location.getAccuracy();
47 | double wlong = location.getLatitude(); //纬度
48 | double jlong = location.getLongitude(); //经度
49 |
50 | TextView tv = new TextView(getApplicationContext());
51 | tv.setText("经度:"+jlong+"\n"+"纬度:"+wlong+"\n"+ accuracy);
52 | setContentView(tv);
53 |
54 | }
55 | /**
56 | * 某一个位置提供者的状态发生改变的时候调用的方法
57 | */
58 | @Override
59 | public void onStatusChanged(String provider, int status, Bundle extras) {
60 |
61 | }
62 |
63 | @Override
64 | public void onProviderEnabled(String provider) {
65 |
66 | }
67 |
68 | @Override
69 | public void onProviderDisabled(String provider) {
70 |
71 | }
72 | }
73 | }
74 | ```
75 |
76 | ---
77 |
78 | - 邮箱 :charon.chui@gmail.com
79 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/获取应用程序缓存及一键清理.md:
--------------------------------------------------------------------------------
1 | 获取应用程序缓存及一键清理
2 | ===
3 |
4 | 1. 什么是缓存呢?
5 | 在手机ROM里面的缓存就是每个程序的cache文件夹
6 |
7 | 2. 获取缓存思路(参考手机设置页面)
8 | 通过`PakcageManager.getPakcageSizeInfo()`能得到程序的缓存,但是这个方法被隐藏了,而系统的`Setting`页面之所以能使用是因为它们的权限高,
9 | 我们要想使用就必须通过反射来得到,这里`getPackageSizeInfo()`方法的第二个参数是一个远程的`aidl`文件。
10 | - 所以必须要在本地的工程中新建一个包,名字为`android.content.pm`
11 | - 拷贝`IPakcageStatsObserver.aidl`到该包中,导入后发现报错,是因为还要导入另外一个`aidl`文件`PackageStats.aidl`
12 |
13 | 3. 获取缓存大小
14 | ```java
15 | protected Void doInBackground(Void... params) {
16 | try {
17 | List infos = pm.getInstalledPackages(0);
18 | //pm.getInstalledApplications(flags);
19 | pb.setMax(infos.size());
20 | int total = 0;
21 |
22 | for (PackageInfo info : infos) {
23 | String packname = info.packageName;
24 | Method method = PackageManager.class.getDeclaredMethod(
25 | "getPackageSizeInfo", new Class[] {
26 | String.class,
27 | IPackageStatsObserver.class });
28 | method.invoke(pm, new Object[] { packname,
29 | new MyObserver(packname) });
30 | publishProgress("正在扫描:" + packname);
31 | total++;
32 | pb.setProgress(total);
33 | Thread.sleep(80);
34 | }
35 | } catch (Exception e) {
36 | e.printStackTrace();
37 | }
38 | return null;
39 | }
40 |
41 | private class MyObserver extends IPackageStatsObserver.Stub {
42 | private String packname;
43 | public MyObserver(String packname) {
44 | this.packname = packname;
45 | }
46 | //回调方法,到得到状态之后就会调用该方法,我们可以通过PackageStats中的属性来得到缓存的大小
47 | public void onGetStatsCompleted(PackageStats pStats, boolean succeeded)
48 | throws RemoteException {
49 | long cache = pStats.cacheSize;
50 | long code = pStats.codeSize;
51 | long data = pStats.dataSize;
52 | if (cache > 0) {
53 | cacheInfo.put(packname, cache);
54 | }
55 | }
56 | }
57 | ```
58 |
59 | 4. 缓存清理
60 | 得到每个程序的缓存大小后,该怎么去清理程序的缓存呢?调用`PackageManager.deleteApplicationCacheFiles`,这个方法是隐藏的,我们通过反射来执行但是发现需要权限,
61 | 设置权限后还是提示需要权限,这是因为没有系统权限我们不能清理,设置页面之所以能够使用这个方法,因为是系统的`API`,
62 | 所以我们只能是点击条目之后跳转到系统的设置页面,让通过设置页面来删除缓存.
63 |
64 | 5. 一键清理
65 | 一键自动清理使用`freeStorageAndNotify`方法,该方法能够向系统申请释放多大的内存,系统会根据你申请的大小,尽可能的去是释放可以释放的大小。
66 | ```java
67 | public void cleanAll(View view) {
68 | try {
69 | Method[] ms = PackageManager.class.getDeclaredMethods();
70 | for (Method m : ms) {
71 | if ("freeStorageAndNotify".equals(m.getName())) {
72 | m.invoke(pm, new Object[] { Long.MAX_VALUE,
73 | new MyDataObersver() });
74 | }
75 | }
76 | } catch (Exception e) {
77 | e.printStackTrace();
78 | }
79 | }
80 |
81 | //这是一个aidl
82 | private class MyDataObersver extends IPackageDataObserver.Stub {
83 | public void onRemoveCompleted(String packageName, boolean succeeded)
84 | throws RemoteException {
85 | }
86 | }
87 | ```
88 |
89 | ---
90 |
91 | - 邮箱 :charon.chui@gmail.com
92 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/获取手机中所有安装的程序.md:
--------------------------------------------------------------------------------
1 | 获取手机中所有安装的程序
2 | ===
3 |
4 | - `PackageManger`
5 | 包管理者封装了当前应用程序的所有信息,可以通过包管理者拿到当前应用程序的所有信息。
6 | - `PackageInfo`
7 | 该类封装了应用程序清单文件中的所有信息。可以通过这个类拿到当前应用程序的版本
8 |
9 | ```java
10 | /**
11 | * 返回应用程序的信息
12 | */
13 | public static List getAppinfos(Context context){
14 | PackageManager pm = context.getPackageManager();
15 | //如果后面想通过PackageInfo拿到每个程序的权限信息,那么这里getInstalledPackages的参数就必须是
16 | //PackageManager.GET_PERMISSIONS,不然后面通过PackageInfo就无法得到应用程序清单中申请的权限
17 | List packinfos = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS );
18 | List appinfos = new ArrayList();
19 | for(PackageInfo info : packinfos){
20 | AppInfo appInfo = new AppInfo();
21 | appInfo.setPackname(info.packageName);
22 | appInfo.setVersion(info.versionName);
23 | appInfo.setAppName(info.applicationInfo.loadLabel(pm).toString());
24 | appInfo.setAppIcon(info.applicationInfo.loadIcon(pm));
25 | if((info.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE)==0){//通过这个来判断安装到了手机还是SD卡
26 | appInfo.setInRom(true);
27 | }else{
28 | appInfo.setInRom(false);
29 | }
30 | //判断程序是不是系统程序
31 | if(filterApp(info.applicationInfo)){
32 | appInfo.setUserApp(true);
33 | }else{
34 | appInfo.setUserApp(false);
35 | }
36 | //获取到某个应用程序的全部权限信息.
37 | String[] permissions = info.requestedPermissions;
38 | if(permissions!=null && permissions.length>0){
39 | for(String p: permissions){
40 | if("android.permission.INTERNET".equals(p)){
41 | appInfo.setUseNetwork(true);
42 | }else if("android.permission.ACCESS_FINE_LOCATION".equals(p)){
43 | appInfo.setUseGPS(true);
44 | }else if("android.permission.READ_CONTACTS".equals(p)){
45 | appInfo.setUseContact(true);
46 | }
47 | }
48 | }
49 | appinfos.add(appInfo);
50 | appInfo = null;
51 | }
52 | return appinfos;
53 | }
54 | ```
55 | ```java
56 | /**
57 | * 该方法提供了用于判断一个程序是系统程序还是用户程序的功能。
58 | * @param info
59 | * @return true 用户自己安装的软件
60 | * fasle 系统软件.
61 | */
62 | public static boolean filterApp(ApplicationInfo info) {
63 | if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
64 | return true;
65 | } else if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
66 | return true;
67 | }
68 | return false;
69 | }
70 | ```
71 |
72 | ---
73 |
74 | - 邮箱 :charon.chui@gmail.com
75 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/获取手机及SD卡可用存储空间.md:
--------------------------------------------------------------------------------
1 | 获取手机及SD卡可用存储空间
2 | ===
3 |
4 | 存储设备都是分块的,获取一共有多少块,然后算出来每一块的大小就能得到总的大小
5 | ```java
6 | File file = Environment.getExternalStorageDirectory();//获取SD卡的目录
7 | StatFs statf = new StatFs(file.getAbsolutePath());
8 | long count = statf.getAvailableBlocks();
9 | long size = statf.getBlockSize();
10 | System.out.println("sd卡可用空间:"+ Formatter.formatFileSize(this, count*size));
11 |
12 | File path = Environment.getDataDirectory();//获取手机存储设备的目录
13 | StatFs stat = new StatFs(path.getPath());
14 | long blockSize = stat.getBlockSize();
15 | long availableBlocks = stat.getAvailableBlocks();
16 | System.out.println("手机内部空间:"+ Formatter.formatFileSize(this, availableBlocks*blockSize));
17 | ```
18 | Formatter类的formatFileSize内部能自动将一个比特大小的值转换成M,G,K等
19 | ```
20 | static String formatFileSize(Context context, long number)
21 | Formats a content size to be in the form of bytes, kilobytes, megabytes, etc
22 | ```
23 |
24 | ---
25 |
26 | - 邮箱 :charon.chui@gmail.com
27 | - Good Luck!
28 |
--------------------------------------------------------------------------------
/BasicKnowledge/获取联系人.md:
--------------------------------------------------------------------------------
1 | 获取联系人
2 | ===
3 |
4 | `Android`系统中的联系人也是通过`ContentProvider`来对外提供数据的
5 | 数据库路径为:`/data/data/com.android.providers.contacts/database/contacts2.db`
6 | 我们需要关注的有3张表
7 | - raw_contacts:其中保存了联系人id
8 | - data:和raw_contacts是多对一的关系,保存了联系人的各项数据
9 | - mimetypes:为数据类型
10 |
11 | 先查询`raw_contacts`得到每个联系人的`id`,在使用`id`从`data`表中查询对应数据,根据`mimetype`分类数据这地方在数据库中的是一个`mimetype_id`
12 | 是一个外键引用到了`mimetype`这个表中,它内部做了一个关联查询,这地方不能写`mimetype_id`,只能写`mimetype`不然会报错
13 |
14 | ```java
15 | /**
16 | * 获取联系人
17 | * @return
18 | */
19 | public static List getContactInfos(Context context) {
20 | ContentResolver resolver = context.getContentResolver();
21 | Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
22 | Uri dataUri = Uri.parse("content://com.android.contacts/data");
23 | List contactInfos = new ArrayList();
24 | Cursor cursor = resolver.query(uri, new String[] { "contact_id" },
25 | null, null, null);
26 | while (cursor.moveToNext()) {
27 | String id = cursor.getString(0);
28 | if (id != null) {
29 | ContactInfo info = new ContactInfo();
30 | Cursor dataCursor = resolver.query(dataUri, new String[] {
31 | "mimetype", "data1" }, "raw_contact_id=?",
32 | new String[] { id }, null);
33 | while (dataCursor.moveToNext()) {
34 | if("vnd.android.cursor.item/name".equals( dataCursor.getString(0))){
35 | info.setName(dataCursor.getString(1));
36 | }else if("vnd.android.cursor.item/phone_v2".equals( dataCursor.getString(0))){
37 | info.setPhone(dataCursor.getString(1));
38 | }
39 | }
40 | contactInfos.add(info);
41 | dataCursor.close();
42 | }
43 | }
44 | cursor.close();
45 | return contactInfos;
46 | }
47 | ```
48 | 要使用到这三个表,但是谷歌内部在查询这个`data`表的时候内部使用的并不是查询`data`表而是查询了`data`表的视图,
49 | 这个视图中直接将`mime_Type`类型给关联好了,所以这里直接查询的就是`mimetype`这个类型就可以了,
50 | 还有就是如果自己的手机中如果删除了一个联系人在查询的时候就会报错,这里因为是在手机中删除一个联系人的时候并*不是清空数据库中的数据*,
51 | 而是将这个**raw_contacks中的id置为null**,所以会报错,这里就要进行判断一下查出来的id是否为null。
52 |
53 | ---
54 |
55 | - 邮箱 :charon.chui@gmail.com
56 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/读取用户logcat日志.md:
--------------------------------------------------------------------------------
1 | 读取用户logcat日志
2 | ===
3 |
4 | 1. 读取用户日志需要权限`android.permission.READ_LOGS`
5 |
6 | 2. 在一个服务中开启logcat程序,然后读取
7 | ```java
8 | public void onCreate() {
9 | super.onCreate();
10 | new Thread(){
11 | public void run() {
12 | try {
13 | File file = new File(Environment.getExternalStorageDirectory(),"log.txt");
14 | FileOutputStream fos = new FileOutputStream(file);
15 | Process process = Runtime.getRuntime().exec("logcat");
16 | InputStream is = process.getInputStream();
17 | BufferedReader br = new BufferedReader(new InputStreamReader(is));
18 | String line;
19 | while((line = br.readLine())!=null){
20 | if(line.contains("I/ActivityManager")){
21 | fos.write(line.getBytes());
22 | fos.flush();
23 | }
24 | }
25 | fos.close();
26 | } catch (Exception e) {
27 | e.printStackTrace();
28 | }
29 | };
30 | }.start();
31 | }
32 | ```
33 |
34 | ---
35 |
36 | - 邮箱 :charon.chui@gmail.com
37 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/资源文件拷贝的三种方式.md:
--------------------------------------------------------------------------------
1 | 资源文件拷贝的三种方式
2 | ===
3 |
4 | - 类加载器(类路径)
5 | - 用`Classloader.getResourceAsStream()`来读取类路径中的资源,然后用`FileOutputStream`写入到自己的应用中(*sdk开发的时候经常用这种方式*)。
6 | - 这种方式**必须**要将数据库`address.db`放到**src**目录下,这样编译后就会直接将`address.db`生成到`bin/classes`目录中,会在类路径下,
7 | 所以可以使用`Classloader`进行加载.
8 |
9 | ```java
10 | InputStream is = getClassLoader().getResourceAsStream("address.db");
11 | File file = new File(/data/data/包名/files/address.db);
12 | FileOutputStream fos = new FileOutputStream(file);
13 | ```
14 |
15 | - Raw目录
16 | 将资源文件放到`res-raw`下, 然后用`getResources.openRawResource(R.raw.addresss);`(要求资源最好不超过1M,因为系统会编译`res`目录)
17 |
18 | - Assets目录
19 | 将资源文件放到`Assets`目录中。然后用`mContext.getAssets().open("address.db");`来读取该资源(`Assets`目录中的文件不会被编译,会原封不动的打包到apk中,
20 | 所以一般用来存放比较大的资源文件)
21 | 注意:上面这是在`Eclipse`中的使用方式,因为在`Eclipse`中`assets`目录是在`res`下,但是在`Android Studio`中如果这样使用会报`FileNotFoundException`,为什么呢?
22 | 文件名字没错,而且明明是在`assets`目录中的,这是因为在`Studio`中`assets`文件夹的目录层级发生了变化,直接在`module`下,与`src`目录平级了?该如何修改呢?
23 | 答案就是把`assets`目录移`src/main`下面就可以了?为什么呢?因为我们打开`app.impl`可以看到``。
24 | 所以把`assets`移到`src/main/`下就可以了。
25 |
26 | 那`Raw`和`Assets`两者有什么区别呢?
27 |
28 | - 两者目录下的文件在打包后会原封不动的保存在`apk`包中,不会被编译成二进制。
29 | - 在读取这两个资源文件夹中的文件时会有一定的限制,即单个文件大小不能超过`1M` ,如果读取超过1M的文件会报`Data exceeds UNCOMPRESS_DATA_MAX (1314625 vs 1048576)` 的`IOException`。
30 | - `raw`中的文件会被映射到`R`文件中,访问的时候直接使用资源`ID`;`assets`文件夹下的文件不会被映射到`R`文件中。
31 | - `raw`不可以有目录结构,而`assets`则可以有目录结构,也就是`assets`目录下可以再建立文件夹。
32 |
33 |
34 |
35 | ---
36 |
37 | - 邮箱 :charon.chui@gmail.com
38 | - Good Luck!
39 |
--------------------------------------------------------------------------------
/BasicKnowledge/超级管理员(DevicePoliceManager).md:
--------------------------------------------------------------------------------
1 | 超级管理员(DevicePoliceManager)
2 | ===
3 |
4 | DevicePolicyManager
5 | ---
6 |
7 | Public interface for managing policies enforced on a device. Most clients of this class must have published a DeviceAdminReceiver that the user
8 | has currently enabled.
9 |
10 | ```java
11 | DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
12 | if (dpm.isAdminActive(new ComponentName(context, MyAdmin.class))) {
13 | dpm.resetPassword("321", 0);
14 | dpm.lockNow();
15 | }
16 | ```
17 |
18 | DevicePolicyManager中的方法
19 |
20 | - void lockNow()
21 | Make the device lock immediately, as if the lock screen timeout has expired at the point of this call.
22 |
23 | - boolean resetPassword(String password, int flags)
24 | Force a new device unlock password (the password needed to access the entire device, not for individual accounts) on the user.
25 |
26 | - void wipeData(int flags)
27 | Ask the user date be wiped.
28 |
29 | - boolean isAdminActive(ComponentName who)
30 | Return true if the given administrator component is currently active (enabled) in the system
31 |
32 | 使用超级管理员DevicePolicyManager的步骤
33 | 1. 要写一个类继承DeviceAdminReceiver
34 | ```java
35 | public class MyAdmin extends DeviceAdminReceiver {
36 | //继承就可以不用重写任何内容
37 | }
38 | ```
39 |
40 | 2. 在清单文件中配置自定义的类
41 | ```xml
42 |
47 |
50 |
51 |
52 |
53 |
54 |
55 | ```
56 |
57 | 3. 完成第二步中所需的meta-data。在res下新建一个xml文件,device_admin_sample.xml
58 | ```xml
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | ```
72 | 经过上面的三步使用后仍报错,这是因为对于超级管理员,用户必须在手机的应用程序-设备管理员中激活我们的程序才能使用,但是对于一般的人不知道要这样激活。
73 | 所以我们要在自己的程序中提供一个按钮让用户点击就能进入这个激活超级管理员的`Activity`
74 | 这里可以通过下面的方式来启动这个激活超级管理员的`Activity`
75 | ```xml
76 | // 激活设备超级管理员
77 | public void zouni(View view) {
78 | Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
79 | // 初始化要激活的组件
80 | ComponentName mDeviceAdminSample = new ComponentName(mContext, MyAdmin.class);
81 | intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mDeviceAdminSample);
82 | intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "激活可以防止随意卸载应用");
83 | startActivity(intent);
84 | }
85 | ```
86 | 一旦程序有了管理员权限程序就不能卸载了,要想卸载必须在手机上取消该程序的超级管理员,以后如果要防止程序的卸载可以通过这种超级管理员的方式。
87 |
88 | ---
89 |
90 | - 邮箱 :charon.chui@gmail.com
91 | - Good Luck!
92 |
--------------------------------------------------------------------------------
/BasicKnowledge/锁屏以及解锁监听.md:
--------------------------------------------------------------------------------
1 | 锁屏以及解锁监听
2 | ===
3 |
4 | 屏幕锁屏以及解锁时会分别发送`SCREEN_ON`和`SCREEN_OFF`广播,但是这两个广播**只能通过代码的形式注册**才能被监听到,在`AndroidManifest.xml`中注册根本监听不到。
5 | ```java
6 | public class ScreenActionReceiver extends BroadcastReceiver {
7 | private boolean isRegisterReceiver = false;
8 |
9 | @Override
10 | public void onReceive(Context context, Intent intent) {
11 | String action = intent.getAction();
12 | if (action.equals(Intent.ACTION_SCREEN_ON)) {
13 | Logcat.d(TAG, "屏幕解锁广播...");
14 | } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
15 | Logcat.d(TAG, "屏幕加锁广播...");
16 | }
17 | }
18 |
19 | public void registerScreenActionReceiver(Context mContext) {
20 | if (!isRegisterReceiver) {
21 | isRegisterReceiver = true;
22 |
23 | IntentFilter filter = new IntentFilter();
24 | filter.addAction(Intent.ACTION_SCREEN_OFF);
25 | filter.addAction(Intent.ACTION_SCREEN_ON);
26 | Logcat.d(TAG, "注册屏幕解锁、加锁广播接收者...");
27 | mContext.registerReceiver(ScreenActionReceiver.this, filter);
28 | }
29 | }
30 |
31 | public void unRegisterScreenActionReceiver(Context mContext) {
32 | if (isRegisterReceiver) {
33 | isRegisterReceiver = false;
34 | Logcat.d(TAG, "注销屏幕解锁、加锁广播接收者...");
35 | mContext.unregisterReceiver(ScreenActionReceiver.this);
36 | }
37 | }
38 | }
39 | ```
40 |
41 | ---
42 | - 邮箱 :charon.chui@gmail.com
43 | - Good Luck!
44 |
--------------------------------------------------------------------------------
/BasicKnowledge/零权限上传数据.md:
--------------------------------------------------------------------------------
1 | 零权限上传数据
2 | ===
3 |
4 | 虽然没有权限,但是也可以通过浏览器用`Get`方式来传递自己的数据,由于这样会打开浏览器,为了防止让用户看到,
5 | 所以我们可以再用户锁屏之后开始传递数据,而在用户一解除锁屏我们就回到桌面,这里用`KeyguardManager`来实现,
6 | 即在锁屏的时候键盘不能用,而一旦键盘能用了我们就立马回到桌面
7 | ```java
8 | public void onCreate() {
9 | timer = new Timer();
10 | km = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
11 | random = new Random();
12 | task = new TimerTask() {
13 | @Override
14 | public void run() {
15 | if (km.inKeyguardRestrictedInputMode()) {
16 | Intent intent = new Intent();
17 | intent.setAction(Intent.ACTION_VIEW);
18 | intent.addCategory(Intent.CATEGORY_BROWSABLE);
19 | intent.setData(Uri
20 | .parse("http://192.168.1.254:8080/web/UploadServlet?info="
21 | + random.nextInt()));
22 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
23 | startActivity(intent);
24 | }else{
25 | //回桌面.
26 | Intent intent = new Intent();
27 | intent.setAction("android.intent.action.MAIN");
28 | intent.addCategory("android.intent.category.HOME");
29 | intent.addCategory(Intent.CATEGORY_DEFAULT);
30 | intent.addCategory("android.intent.category.MONKEY");
31 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
32 | startActivity(intent);
33 | }
34 | }
35 | };
36 | timer.schedule(task, 1000, 2000);
37 | super.onCreate();
38 | }
39 | ```
40 |
41 | ---
42 |
43 | - 邮箱 :charon.chui@gmail.com
44 | - Good Luck!
--------------------------------------------------------------------------------
/BasicKnowledge/音量及屏幕亮度调节.md:
--------------------------------------------------------------------------------
1 | 音量及屏幕亮度调节
2 | ===
3 |
4 | 屏幕亮度调节
5 | ---
6 |
7 | ```java
8 | /**
9 | * 滑动改变亮度
10 | * @param percent
11 | */
12 | private void onBrightnessSlide(float percent) {
13 | if (mBrightness < 0) { // mBrightness是当前屏幕的亮度
14 | mBrightness = getWindow().getAttributes().screenBrightness;
15 | if (mBrightness <= 0.00f)
16 | mBrightness = 0.50f;
17 | if (mBrightness < 0.01f)
18 | mBrightness = 0.01f;
19 | // 显示
20 | mOperationBg.setImageResource(R.drawable.video_brightness_bg);
21 | mVolumeBrightnessLayout.setVisibility(View.VISIBLE);
22 | }
23 | WindowManager.LayoutParams lpa = getWindow().getAttributes();
24 | lpa.screenBrightness = mBrightness + percent;
25 | if (lpa.screenBrightness > 1.0f)
26 | lpa.screenBrightness = 1.0f;
27 | else if (lpa.screenBrightness < 0.01f)
28 | lpa.screenBrightness = 0.01f;
29 | getWindow().setAttributes(lpa);
30 |
31 |
32 | ViewGroup.LayoutParams lp = mOperationPercent.getLayoutParams(); //这部分是改变图片上面的当前亮度的进度的
33 | lp.width = (int) (findViewById(R.id.operation_full).getLayoutParams().width * lpa.screenBrightness);
34 | mOperationPercent.setLayoutParams(lp);
35 | }
36 | ```
37 | 音量调节
38 | ---
39 |
40 | ```java
41 | /**
42 | * 音量调节
43 | */
44 | public class MainActivity extends Activity {
45 | private static final String TAG = "MainActivity";
46 | private AudioManager mAudioManager;
47 | private int currentVolume;
48 | @Override
49 | protected void onCreate(Bundle savedInstanceState) {
50 | super.onCreate(savedInstanceState);
51 | setContentView(R.layout.activity_main);
52 | mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
53 | // 最大音量
54 | int maxVolume = mAudioManager
55 | .getStreamMaxVolume(AudioManager.STREAM_MUSIC);
56 | Log.i(TAG, "最大音量:" + maxVolume);
57 | currentVolume = mAudioManager
58 | .getStreamVolume(AudioManager.STREAM_MUSIC);
59 | Log.i(TAG, "当前音量:" + currentVolume);
60 | }
61 | public void up(View view) {
62 | // 音量增大
63 | // AudioManager.STREAM_SYSTEM
64 | mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
65 | AudioManager.ADJUST_RAISE, AudioManager.FX_FOCUS_NAVIGATION_UP);
66 | currentVolume = mAudioManager
67 | .getStreamVolume(AudioManager.STREAM_MUSIC);
68 | Log.i(TAG, "当前音量:" + currentVolume);
69 | }
70 | public void down(View view) {
71 | // 音量减小
72 | mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
73 | AudioManager.ADJUST_LOWER, AudioManager.FX_FOCUS_NAVIGATION_UP);
74 | currentVolume = mAudioManager
75 | .getStreamVolume(AudioManager.STREAM_MUSIC);
76 | Log.i(TAG, "当前音量:" + currentVolume);
77 | }
78 | }
79 | ```
80 |
81 | ---
82 |
83 | - 邮箱 :charon.chui@gmail.com
84 | - Good Luck!
--------------------------------------------------------------------------------
/Dagger2/1.Dagger2简介(一).md:
--------------------------------------------------------------------------------
1 | Dagger2简介(一)
2 | ===
3 |
4 | [Dagger](https://github.com/google/dagger)
5 |
6 | > A fast dependency injector for Android and Java.
7 |
8 | `Dagger`是一个依赖注入(`Dependency Injection`,简称`DI`)框架,`butterknife`也是一个依赖注入框架。但是`Dagger2`比`Butterknife`更强大的多,它的主要作用,就是对象的管理,其目的是为了降低程序耦合。
9 |
10 | 有关注解和`ButterKnife`的解析请看之前的文章:[注解使用](https://github.com/CharonChui/AndroidNote/blob/master/AdavancedPart/%E6%B3%A8%E8%A7%A3%E4%BD%BF%E7%94%A8.md)及[ButterKnife源码解析](https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/butterknife%E6%BA%90%E7%A0%81%E8%AF%A6%E8%A7%A3.md)
11 |
12 | 那么神马是依赖注入,其实我们一直在用:
13 |
14 | - 通过接口注入
15 |
16 | ```java
17 | interface Ib {
18 | void setB(B b)
19 | }
20 | public class A implements Ib {
21 | B b;
22 | @override
23 | void setB(B b) {
24 | this.b = b;
25 | }
26 | }
27 | ```
28 |
29 | - 通过`set`方法注入
30 |
31 | ```java
32 | public class ClassA {
33 | ClassB classB;
34 |
35 | public void setClassB(ClassB b) {
36 | classB = b;
37 | }
38 | }
39 | ```
40 |
41 | - 通过构造方法注入
42 |
43 | ```java
44 | public class ClassA {
45 | ClassB classB;
46 |
47 | public void ClassA(ClassB b) {
48 | classB = b;
49 | }
50 | }
51 | ```
52 |
53 | - 通过注解的方式注入
54 |
55 | ```java
56 | public class ClassA {
57 | //此时并不会完成注入,
58 | //还需要依赖注入框架的支持,如Dagger2
59 | @inject
60 | ClassB classB;
61 | public ClassA() {}
62 | }
63 | ```
64 |
65 | 说了这么久,也不知道到底这货有什么用,这里举个例子,比如有个类`A`,他的构造函数需要传入`B,C`;然后代码里有10个地方实例化了`A`,那如果功能更改,`A`的构造函数改成了只有`B`,这个时候,你是不是要去这10个地方一个一个的改?如果是100个地方,你是不是要吐血?!如果采用`dagger2`,这样的需求只需要改1-2个地方。这是真的吗?听起来好像挺牛逼的样子。
66 |
67 |
68 | 也有人怀疑`Dagger2`利用注解是不是采用了反射,会影响性能,这个问题其实在之前的文章[ButterKnife源码解析](https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/butterknife%E6%BA%90%E7%A0%81%E8%AF%A6%E8%A7%A3.md)就已经介绍过了。`Dagger2`、`ButterKnife`这类依赖注入框架都已经采用了`apt`代码自动生成技术,其注解是停留在编译时,可以不用考虑性能的问题。
69 |
70 |
71 | 下一篇:[2.Dagger2入门demo(二)](https://github.com/CharonChui/AndroidNote/blob/master/Dagger2/2.Dagger2%E5%85%A5%E9%97%A8demo(%E4%BA%8C).md)
72 |
73 | ---
74 |
75 | - 邮箱 :charon.chui@gmail.com
76 | - Good Luck!
--------------------------------------------------------------------------------
/Gradle&Maven/duplicate class冲突解决.md:
--------------------------------------------------------------------------------
1 | duplicate class冲突解决
2 | ===
3 |
4 | ```
5 | Duplicate class com.x.util.Base64Encoder found in modules jetified-b64encode-1.0.8-runtime (com.x.x.x.x:b64encode:1.0.8) and jetified-b64encode_v2_0 (b64encode_v2_0.jar)
6 | ```
7 | 今天在开发过程中遇到了这个错误。提示Base64Encoder在com.x.x.x.x:b64encode:1.0.8和b64encode_v2_0.jar里面重复了,一个是1.0.8版本一个是2.0版本。
8 | 那么这里要做的就是exclude一个就可以。
9 |
10 | 1. 首先需要找到是哪个依赖中有该依赖:
11 | 通常情况下我们会直接查找一下该类就能看到有那几个库中包含,但是这里只能查到jar包的类,所以需要用另一种方式。
12 | Terminal执行:
13 | ```
14 | ./gradlew app:dependencies
15 | ```
16 | 执行后会将所有的依赖列出:
17 | ```
18 | | \--- androidx.annotation:annotation:1.1.0 -> 1.2.0
19 | +--- com.google.android.gms:play-services-tasks:17.2.1
20 | | \--- com.google.android.gms:play-services-basement:17.6.0
21 | | +--- androidx.collection:collection:1.0.0 -> 1.1.0
22 | | | \--- androidx.annotation:annotation:1.1.0 -> 1.2.0
23 | | +--- androidx.core:core:1.2.0 -> 1.5.0
24 | | | +--- androidx.annotation:annotation:1.2.0
25 | | | +--- androidx.lifecycle:lifecycle-runtime:2.0.0 -> 2.3.1
26 | | | | +--- androidx.arch.core:core-runtime:2.1.0
27 | | | | | +--- androidx.annotation:annotation:1.1.0 -> 1.2.0
28 | | | | | \--- androidx.arch.core:core-common:2.1.0
29 | | | | | \--- androidx.annotation:annotation:1.1.0 -> 1.2.0
30 | | | | +--- androidx.lifecycle:lifecycle-common:2.3.1 (*)
31 | | | | +--- androidx.arch.core:core-common:2.1.0 (*)
32 | | | | \--- androidx.annotation:annotation:1.1.0 -> 1.2.0
33 | | | +--- androidx.versionedparcelable:versionedparcelable:1.1.1
34 | | | | +--- androidx.annotation:annotation:1.1.0 -> 1.2.0
35 | | | | \--- androidx.collection:collection:1.0.0 -> 1.1.0 (*)
36 | | | \--- androidx.collection:collection:1.0.0 -> 1.1.0 (*)
37 | | \--- androidx.fragment:fragment:1.0.0 -> 1.3.4
38 | | +--- androidx.annotation:annotation:1.1.0 -> 1.2.0
39 | | +--- androidx.core:core:1.2.0 -> 1.5.0 (*)
40 | | +--- androidx.collection:collection:1.1.0 (*)
41 | ```
42 |
43 | 2. 接下来就是在该列表中搜索,找到后直接在`build.gradle`中使用exclude:
44 | ```
45 | implementation("com.xxx.xxx:pass-sdk-core-router:${PASS_VERSION}") {
46 | // 去除扫码相关功能
47 | exclude group: "com.xxx.passport", module: "pass-module-qrcode"
48 | // 去除人脸登录相关功能
49 | exclude group: "com.xxx.passport", module: "pass-module-face"
50 | }
51 | ```
52 |
53 | 3. 但是有时候会发现有很多个库中都会有该依赖,一个一个的去添加不太适合,这时可以在app的buid.gradle中统一配置:
54 | ```
55 | android {
56 | compileSdkVersion rootProject.android.extCompileSdkVersion
57 | buildToolsVersion rootProject.android.extBuildToolsVersion
58 | useLibrary 'org.apache.http.legacy'
59 | compileOptions {
60 | targetCompatibility JavaVersion.VERSION_1_8
61 | sourceCompatibility JavaVersion.VERSION_1_8
62 | }
63 | configurations {
64 | implementation.exclude group: 'com.xxx.xxx.common.toolbox' , module:'b64encode'
65 | }
66 | }
67 | ```
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | ---
80 |
81 | - 邮箱 :charon.chui@gmail.com
82 | - Good Luck!
--------------------------------------------------------------------------------
/ImageLoaderLibrary/Coil简介.md:
--------------------------------------------------------------------------------
1 | # Coil简介
2 |
3 | Coil作为图片加载库的新秀,和Glide、Picasso这些老牌图片库相比,它们的优缺点是什么以及Coil未来的展望?先来了解一下什么是Coil。
4 |
5 | Coil是基于Kotlin开发的首个图片加载库,来自Instacart团队,来看看官网对它的最新的介绍。
6 |
7 | - R8:Coil下完全兼容R8,所以不需要添加任何与Coil相关的混淆器规则。
8 | - Fast:Coil进行了许多优化,包括内存和磁盘缓存,对内存中的图片进行采样,重新使用位图,自动暂停/取消请求等等。
9 | - Lightweight:Coil为你的APK增加了2000个方法(对于已经使用了OkHttp和协程的应用程序),这与Picasso相当,明显少于Glide和Fresco。
10 | - Easy to use:Coil利用了Kotlin的语言特性减少了样版代码。
11 | - Modern:使用了大量的高级特性,例如协程、OkHttp、和androidX lifecycle跟踪生命周期状态的,Coil是目前唯一支持androidX lifecycle的库。
12 |
13 | Coil作为图片库的新秀,越来越受欢迎了,但是为什么会引起这么多人的关注?在当今主流的图片加载库环境中Coil是一股清流,它是轻量级的,因为它使用了许多Android开发者已经在他们的项目中包含的其他库(协程、Okhttp)。
14 |
15 | 当我第一次看到这个库时,我认为这些都是很好的改进,但是我很想知道和其他主流的图片加载库相比,这个库能带来哪些好处,这篇文章的主要目的是分析一下Coil的性能,接下来我们来对比一下Coil、Glide和Picasso。
16 |
17 | 作者从以下场景对Coil、Glide、Picasso做了全面的测试。
18 |
19 | - 当缓存为空时,从网络中下载图片的平均时间。
20 |
21 | - 从网络中下载图片所用的时间。
22 |
23 | 结果:Glide最快Picasso和Coil几乎相同。
24 |
25 | - 加载完整的图片列表所用的时间,以及平均时间。
26 |
27 | 结果:Glide是最快的,其次是Picasso,Coil是最慢的。
28 |
29 | - 当缓存不为空时,从缓存中加载图片的平均时间。
30 |
31 | - 从缓存中加载图片所用的时间。
32 |
33 | 结果:Glide最快,Coil其次,Picasso最慢。
34 |
35 | - 加载完整的图片列表所用的时间,以及平均时间。
36 |
37 | 结果:Glide和Coil几乎相同,Picasso是最慢的。
38 |
39 | 图片加载库的选择是我们应用程序中最重要的部分之一,根据以上结果,如果你的应用程序中没有大量使用图片的时候,我认为使用Coil更好,原因有以下几点:
40 |
41 | - 与Glide和Fresco类似,Coil支持位图池,位图池是一种重新使用不再使用的位图对象的技术,这可以显著提高内存性能(特别是在oreo之前的设备上),但是它会造成一些API限制。
42 | - Coil是基于Kotlin开发的,为Kotlin使用而设计的,所以代码通常更简洁更干净。
43 | - Kotlin作为Android首选语言,Coil是为Kotlin而设计的,Coil在未来肯定会大方光彩。
44 | - 从Glide、Picasso迁移到Coil是非常的容易,API非常的相似。
45 | - Coil支持androidX lifecycle跟踪生命周期状态,也是是目前唯一支持androidX lifecycle的网络图片加载库。
46 | - Coil支持动态图片采样,假设本地有一个500x500的图片,当从磁盘读取500x500的映像时,我们将使用100x100的映像作为占位符。
47 |
48 | 如果你的是图片类型的应用,应用程序中包含了大量的图片,图片加载的速度是整个应用的核心指标之一,那么现在还不适合使用Coil。
49 |
50 | Coil涵盖了Glide、Picasso等等图片加载库所支持的功能,除此之外Coil还有一个功能动态图片采样。
51 |
52 | ### 动态图片采样
53 |
54 | 更多关于图片采样信息可以访问[Coil](https://coil-kt.github.io/coil/getting_started/) ,这里简单的说明一下,假设本地有一个500x500的图片,当从磁盘读取500x500的图片时,将使用100x100的映像作为占位符,等待加载完成之后才会完全显示。
55 |
56 |
57 |
58 |
59 |
60 | # 参考:
61 |
62 | - [Coil vs Picasso vs Glide: Get Ready… Go!](https://proandroiddev.com/coil-vs-picasso-vs-glide-get-ready-go-774add8cfd40)
63 |
--------------------------------------------------------------------------------
/JavaKnowledge/Base64加密.md:
--------------------------------------------------------------------------------
1 | Base64加密
2 | ===
3 |
4 | 由来
5 | ---
6 |
7 | 为什么会有`Base64`编码呢?因为有些网络传送渠道并不支持所有的字节,例如传统的邮件只支持可见字符的传送,
8 | 像`ASCII`码的控制字符就不能通过邮件传送。这样用途就受到了很大的限制,比如图片二进制流的每个字节不可能全部是可见字符,所以就传送不了。
9 | 最好的方法就是在不改变传统协议的情况下,做一种扩展方案来支持二进制文件的传送。把不可打印的字符也能用可打印字符来表示,问题就解决了。
10 | `Base64`编码应运而生,`Base64`就是一种基于`64`个可打印字符来表示二进制数据的表示方法。
11 |
12 | 原理
13 | ---
14 |
15 | 下面是`Base64`的编码表,字符选用了`A-Z、a-z、0-9、+、/`64个可打印字符。
16 | 数值代表字符的索引,这个是标准`Base64`协议规定的,不能更改。
17 | 64个字符用6个`bit`位就可以全部表示,一个字节有8个bit位,剩下两个`bit`就浪费掉了,这样就不得不牺牲一部分空间了。
18 | 这里需要弄明白的就是一个`Base64`字符是8个`bit`,但是有效部分只有右边的6个`bit`,左边两个永远是0。
19 |
20 | 
21 |
22 | 因为`Base64`是6个`bit`那么,怎么来表示传统字符呢?那就是用4个`Base64`字符来表示3个传统字符。3*8=4*6嘛,这样就正好了。
23 |
24 | 下面来看一个例子:`Man`是三个字符,一共24个`bit`,用4个`Base64`字符来凑齐24个有效位。
25 | 红框表示的是对应的`Base64`,6个有效位转化成相应的索引值再对应`Base64`字符表,
26 | 查出`Man`对应的`Base64`字符是`TWFU`。说到这里有个原则不知道你发现了没有,要转换成Base64的最小单位就是三个字节,
27 | 对一个字符串来说每次都是三个字节三个字节的转换,对应的是`Base64`的四个字节。这个搞清楚了其实就差不多了。
28 |
29 | 
30 |
31 |
32 | 正是因为这,所以想要转换成`Base64`最少要三个字节才可以,转换出来的`Base64`最少是4个字节,但是如果我要转换的字节不够3个怎么办?比如我想对字符`A`进行`Base64`加密。,`A`对应的第二个`Base64`的二进制位只有两个,把后边的四个补0就是了。
33 | 所以`A`对应的`Base64`字符就是QQ。上边已经说过了,原则是`Base64`字符的最小单位是四个字符一组,那这才两个字符,后边补两个"="吧。
34 | 其实不用"="也不耽误解码,之所以用"=",可能是考虑到多段编码后的Base64字符串拼起来也不会引起混淆。
35 | 由此可见 Base64字符串只可能最后出现一个或两个"=",中间是不可能出现"="的。下图中字符"BC"的编码过程也是一样的。
36 |
37 | 
38 |
39 | 总结
40 | ---
41 |
42 | `Base64`编码是从二进制到字符的过程,像一些中文字符用不同的编码转为二进制时,产生的二进制是不一样的,
43 | 所以最终产生的`Base64`字符也不一样。例如上网对应`utf-8`格式的`Base64`编码是`5LiK572R`,对应`GB2312`格式的`Base64`编码是`yc/N+A==`
44 |
45 |
46 |
47 | ----
48 | - 邮箱 :charon.chui@gmail.com
49 | - Good Luck!
50 |
51 |
52 |
--------------------------------------------------------------------------------
/JavaKnowledge/MD5加密.md:
--------------------------------------------------------------------------------
1 | MD5加密
2 | =======
3 |
4 | `MD5`是一种不可逆的加密算法只能将原文加密,不能将密文再还原回去,原来把加密后将这个数组通过`Base64`给变成字符串,
5 | 这样是不严格的业界标准的做法是对其加密之后用每个字节`&15`然后就能得到一个`int`型的值,再将这个`int`型的值变成16进制的字符串.虽然MD5不可逆,
6 | 但是网上出现了将常用的数字用`md5`加密之后通过数据库查询,所以`MD5`简单的情况下仍然可以查出来,一般可以对其多加密几次或者`&15`之后再和别的数运算等,
7 | 这称之为*加盐*.
8 |
9 | ```java
10 | public class MD5Utils {
11 | /**
12 | * md5加密的工具方法
13 | */
14 | public static String encode(String password){
15 | try {
16 | MessageDigest digest = MessageDigest.getInstance("md5");
17 | byte[] result = digest.digest(password.getBytes());
18 | StringBuilder sb = new StringBuilder();//有的数很小还不到10所以得到16进制的字符串有一个
19 | //的情况,这里对于小于10的值前面加上0
20 | //16进制的方式 把结果集byte数组 打印出来
21 | for(byte b :result){
22 | int number = (b&0xff);//加盐.
23 | String str =Integer.toHexString(number);
24 | if(str.length()==1){
25 | sb.append("0");
26 | }
27 | sb.append(str);
28 | }
29 | return sb.toString();
30 | } catch (NoSuchAlgorithmException e) {
31 | e.printStackTrace();
32 | return "";
33 | }
34 | }
35 | }
36 | ```
37 |
38 | ---
39 |
40 | - 邮箱 :charon.chui@gmail.com
41 | - Good Luck!
42 |
--------------------------------------------------------------------------------
/JavaKnowledge/MVC与MVP及MVVM.md:
--------------------------------------------------------------------------------
1 | MVC与MVP及MVVM
2 | ===
3 |
4 | MVC
5 | ---
6 |
7 | `MVC`是一种使用Model View Controller模型-视图-控制器设计创建`Web`应用程序的模式:
8 |
9 | - `Model`(模型)表示应用程序核心(比如数据库记录列表)是应用程序中用于处理应用程序数据逻辑的部分。
10 | - `View`(视图)显示数据(数据库记录)是应用程序中处理数据显示的部分。
11 | - `Controller`(控制器)处理输入(写入数据库记录)应用程序中处理用户交互的部分。
12 |
13 | MVC 分层有助于管理复杂的应用程序,因为您可以在一个时间内专门关注一个方面。例如,您可以在不依赖业务逻辑的情况下专注于视图设计。同时也让应用程序的测试更加容易。
14 | MVC 分层同时也简化了分组开发。不同的开发人员可同时开发视图、控制器逻辑和业务逻辑。
15 |
16 | - 优点
17 | - 耦合性低
18 | - 重用性高
19 | - 可维护性高
20 | - 有利软件工程化管理
21 |
22 | - 缺点
23 | - 没有明确的定义
24 | - 视图与控制器间的过于紧密的连接
25 | - 增加系统结构和实现的复杂性
26 |
27 | 
28 |
29 | MVP
30 | ---
31 |
32 | `MVP`是从经典的模式`MVC`演变而来,它们的基本思想有相通的地方:`Controller/Presenter`负责逻辑的处理,`Model`提供数据,`View`负责显示。
33 | 作为一种新的模式,`MVP`与`MVC`有着一个重大的区别:在`MVP`中`View`并不直接使用`Model`,它们之间的通信是通过`Presenter`(`MVC`中的`Controller`)来进行的,
34 | 所有的交互都发生在`Presenter`内部,而在`MVC`中`View`会直接从`Model`中读取数据而不是通过`Controller`。
35 | 在`MVC`里,`View`是可以直接访问`Model`的!从而,`View`里会包含`Model`信息,不可避免的还要包括一些业务逻辑。
36 | 在`MVC`模型里,更关注的`Model`的不变,而同时有多个对`Model`的不同显示及`View`。所以,在`MVC`模型里,`Model`不依赖于`View`,但是`View`是依赖于`Model`的。
37 | 不仅如此,因为有一些业务逻辑在`View`里实现了,导致要更改`View`也是比较困难的,至少那些业务逻辑是无法重用的。
38 |
39 | 
40 |
41 | 在`MVP`里,`Presenter`完全把`Model`和`View`进行了分离,主要的程序逻辑在`Presenter`里实现。而且`Presenter`与具体的`View`是没有直接关联的,
42 | 而是通过定义好的接口进行交互,从而使得在变更`View`时候可以保持`Presenter`的不变,即重用!
43 | 在MVP里,应用程序的逻辑主要在`Presenter`来实现,其中的`View`是很薄的一层。因此就有人提出了`Presenter First`的设计模式,
44 | 就是根据`User Story`来首先设计和开发`Presenter`。在这个过程中,`View`是很简单的,能够把信息显示清楚就可以了。
45 | 在后面,根据需要再随便更改`View`,而对`Presenter`没有任何的影响了。 如果要实现的`UI`比较复杂,而且相关的显示逻辑还跟`Model`有关系,
46 | 就可以在`View`和`Presenter`之间放置一个`Adapter`。由这个`Adapter`来访问`Model`和`View`,避免两者之间的关联。
47 | 而同时,因为`Adapter`实现了`View`的接口,从而可以保证与`Presenter`之间接口的不变。这样就可以保证`View`和`Presenter`之间接口的简洁,
48 | 又不失去`UI`的灵活性。在`MVP`模式里,`View`只应该有简单的`Set/Get`的方法,用户输入和设置界面显示的内容,除此就不应该有更多的内容,
49 | 绝不容许直接访问`Model`这就是与`MVC`很大的不同之处。
50 |
51 | - 优点
52 | - 模型与视图完全分离,我们可以修改视图而不影响模型
53 | - 可以更高效地使用模型,因为所有的交互都发生在一个地方——`Presenter`内部
54 | - 我们可以将一个`Presenter`用于多个视图,而不需要改变`Presenter`的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁。
55 | - 如果我们把逻辑放在`Presenter`中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)
56 |
57 | - 缺点
58 | 由于对视图的渲染放在了`Presenter`中,所以视图和`Presenter`的交互会过于频繁。还有一点需要明白,如果`Presenter`过多地渲染了视图,
59 | 往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么`Presenter`也需要变更了。
60 | 比如说,原本用来呈现`Html`的`Presenter`现在也需要用于呈现Pdf了,那么视图很有可能也需要变更。
61 |
62 | MVVM
63 | ---
64 |
65 | MVVM是Model-View-ViewModel的简写。
66 |
67 | 
68 |
69 | MVVM模式将Presener改名为View Model,基本上与MVP模式完全一致,同样是以VM为核心,但是不同于MVP,MVVM采用了数据双向绑定的方案,替代了繁琐复杂的DOM操作。
70 | 该模型中,View与VM保持同步,View绑定到VM的属性上,如果VM数据发生变化,通过数据绑定的方式,View会自动更新视图;VM同样也暴露出Model中的数据。
71 |
72 | 看起来MVVM很好的解决了MVC和MVP的不足,但是由于数据和视图的双向绑定,导致出现问题时不太好定位来源,有可能数据问题导致,也有可能业务逻辑中对视图
73 | 属性的修改导致。如果项目中打算用MVVM的话可以考虑使用官方的架构组件ViewModel、LiveData、DataBinding去实现MVVM
74 |
75 |
76 |
77 | - 优点
78 | `MVVM`模式和`MVC`模式一样,主要目的是分离视图`View`和模型`Model`
79 | - 低耦合。
80 | - 可重用性。
81 | - 独立开发。
82 | - 可测试。
83 |
84 | ---
85 | - 邮箱 :charon.chui@gmail.com
86 | - Good Luck!
87 |
88 |
89 |
--------------------------------------------------------------------------------
/JavaKnowledge/RMB大小写转换.md:
--------------------------------------------------------------------------------
1 | RMB大小写转换
2 | =========
3 |
4 | ```java
5 | public class RenMingBi {
6 | private static final char[] data = new char[] { '零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖' };
7 | private static final char[] units = new char[] { '元', '拾', '佰', '仟', '万', '拾', '佰', '仟', '亿' };
8 |
9 | public static void main(String[] args) {
10 | System.out.println(convert(11));
11 | }
12 |
13 | public static String convert(int money) {
14 | StringBuffer sbf = new StringBuffer();
15 | int unit = 0;
16 | while (money != 0) {
17 | sbf.insert(0, units[unit++]);
18 | System.out.println(sbf.toString());
19 | int number = money % 10;
20 | sbf.insert(0, data[number]);
21 | money /= 10;
22 | }
23 | return sbf.toString();
24 | }
25 | }
26 | ```
27 |
28 | ---
29 |
30 | - 邮箱 :charon.chui@gmail.com
31 | - Good Luck!
32 |
--------------------------------------------------------------------------------
/JavaKnowledge/UML类图.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharonChui/AndroidNote/428cb4eed395020e4c6828c1e96ab35a0c17dad6/JavaKnowledge/UML类图.pdf
--------------------------------------------------------------------------------
/JavaKnowledge/Vim使用教程.md:
--------------------------------------------------------------------------------
1 | Vim使用教程
2 | ===
3 |
4 | `Better, Stronger, Faster`
5 |
6 | 首先`Vim`有两种模式:
7 |
8 | - `Normal`
9 | 该模式下不能写入,修改要在该模式进行。在`Insert`模式中可以使用`ESC`键来返回到`Normal`模式。
10 | - `Insert`
11 | 该模式下可以进行写入。在`Normal`模式下使用按`i`键进行`Insert`模式。
12 |
13 | 下面说一下`Normal`状态下的一些命令,所有的命令都要在`Normal`状态下执行:
14 |
15 | 进入`Insert`模式
16 | ---
17 |
18 | - `i` 进入`insert`模式
19 | - `a(append)` 在光标后进行插入,直接进入`insert`模式
20 | - `o(open a line below)` 在当前行后插入一个新行,直接进入`insert`模式
21 | - `O` 大写的O是在光标所在的行下面插入一个新航,直接进入编译模式
22 | - `I` 从该行的最前面开始编辑
23 | - `A` 从该行的最后面开始编辑
24 | - `s` 删除光标后的字符,从光标当前位置插入
25 | - `S` 删除光标所在当前行,从行首插入
26 | - `cw` 替换从光标位置开始到该单词结束位置的所有字符,直接进入`Insert`模式
27 |
28 | 在VIM中,有相当一部分命令可以扩展为3部分:
29 |
30 | - 开头的部分是一个数字,代表重复次数;
31 | - 中间的部分是命令;
32 | - 最后的部分,代表命令的对象。
33 |
34 | 比如,命令3de中,3表示执行3次,d是删除命令,e表示从当前位置到单词的末尾。整条命令的意思就是,从当前位置向后,删除3个单词。类似的,命令3ce表示从当前位置向后,删除三个单词,然后进入编辑模式。
35 |
36 | 可以看到,命令组合的前两个部分比较简单,但第三个部分也就是命令对象,技巧就比较多了。所以接下来,我就与你详细介绍下到底有哪些命令对象可以使用。
37 |
38 | 其实,对命令对象并没有一个完整的分类。但我根据经验,将其总结为光标移动命令和文本对象两种。
39 |
40 | 第一种是光标移动命令。比如,$命令是移动光标到本行末尾,那么d$就表示删除到本行末尾;再比如,4}表示向下移动4个由空行隔开的段落,那么d4}就是删除这4个段落。
41 |
42 | 移动光标
43 | ---
44 |
45 | - `h` 左移
46 | - `j` 移到下一行
47 | - `k` 移到上一行
48 | - `l` 右移
49 | - `gg` 移动到文章的开头
50 | - `G` 移动到当前文章的最后。
51 |
52 | - `$` $光标移动当前行尾
53 | - `0` 数字0光标移动当前行首
54 |
55 | - `e` 向右移动一个单词
56 | - `w` 向右移动一个单词,与e的区别是w是把光标放到下一个单词的开头,而e是把光标放到这一个单词的结尾。
57 | - `b` 移动到单词开始位置
58 | - `:59` 移动到59行
59 | - `#l` 移动光标到该行第#个字的位置,如`5l`
60 | - `ctrl+g` 列出当前光标所在行的行号等信息
61 | - `: #` 如输入: 15会跳到文章的第15行
62 | - `ctrl+b`:向上滚动一屏
63 | - `ctrl+f`:向下滚动一屏
64 | - `ctrl+u`:向上滚动半屏
65 | - `ctrl+d`:向下滚动半屏
66 |
67 | 删除文字
68 | ---
69 |
70 | - `x` 删除光标所在位置的一个字符
71 | - `#x` 删除光标所在位置后的#个字符,如`6x`就是删除后面的6个字符。
72 | - `X` 大写的X为删除光标所在位置前的一个字符
73 | - `#X` 删除光标所在位置前的#个字符
74 | - `dd` 删除当前行,并把删除的行存到剪贴板中
75 | - `#dd` 从光标所在行开始删除#行。如`5dd`就是删除5行
76 | - `v/ctrl+v`: 使用h、j、k、l移动选择内容,然后按d删除。其中v是非列模式,ctrl+v是列模式
77 |
78 | 复制粘贴
79 | ---
80 |
81 | - `yy` 拷贝当前行
82 | - `#yy` 拷贝当前所在行往下的#行文字
83 | - `yw` 复制当前光标所在位置到字尾处的位置
84 | - `#yw` 复制当前光标所在位置往后#个字
85 | - `y$` 拷贝光标至本行结束位置
86 | - `y` 拷贝选中部分,在`Normal`模式下按`v`会进入到可视化模式,这时候可以上下移动进行选中某一部分,然后按`y`就可以复制了。
87 |
88 | - `p` 在光标所在的位置向下开辟一行,粘贴
89 | - `P` 在光标所在的位置向上开辟一行,粘贴
90 | - 剪切: 按dd或者ndd删除,将删除的行保存到剪贴板中,然后按p/P就可以粘贴了。
91 |
92 |
93 | 替换
94 | ---
95 |
96 | - `r` 替换光标所在处的字符
97 | - `R` 替换光标所到之处的字符,直到按下`esc`键为止
98 | - `:%s/old/new/g` 用`new`替换文件中所有的`old`
99 | - `:%s/old/new/gc`,同上,但是每次替换需要用户确认
100 | - `:s/old/new/g` 光标所在行的所有old替换为new
101 | - `:s/old/new/` 光标所在行的第一个old替换为new
102 |
103 | 撤销
104 | ---
105 |
106 | - `u` 撤销、回退
107 | - `ctrl + r` 恢复刚才的撤销操作
108 |
109 | 搜索
110 | ---
111 |
112 | - `/关键字` 先按`/`键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按`n`会往后寻找到您要的关键字为止。
113 |
114 | - `?关键字` 同上,只不过`?`是往上查找
115 |
116 | 缩进缩出
117 | ---
118 |
119 | - `>>` 当前行缩进
120 | - `#>>` 当前光标下n行缩进
121 | - `<<` 当前行缩出
122 | - `#<<` 当前光标下n行缩出
123 |
124 | - `: set nu` 会在文件每一行前面显示行号
125 | - `:wq` 保存并退出
126 | - `:w` 保存
127 | - `:q!` 退出不保存
128 | - `:saveas ` 另存为
129 | - `:e filename` 打开文件
130 | - `:sav filename 保存为某文件名
131 | - ZZ: 命令模式使用大写ZZ直接保存并退出
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | ---
141 | - 邮箱 :charon.chui@gmail.com
142 | - Good Luck!
143 |
144 |
145 |
--------------------------------------------------------------------------------
/JavaKnowledge/hashCode与equals.md:
--------------------------------------------------------------------------------
1 | hashCode与equals
2 | ===
3 |
4 | `HashSet`和`HashMap`一直都是`JDK`中最常用的两个类,`HashSet`要求不能存储相同的对象,`HashMap`要求不能存储相同的键。 那么`Java`运行时
5 | 环境是如何判断`HashSet`中相同对象、`HashMap`中相同键的呢?当存储了相同的东西之后`Java`运行时环境又将如何来维护呢?
6 | 在研究这个问题之前,首先说明一下`JDK`对`equals(Object obj)`和`hashcode()`这两个方法的定义和规范:
7 | 在`Java`中任何一个对象都具备`equals(Object obj)`和`hashcode()`这两个方法,因为他们是在`Object`类中定义的:
8 | - `equals(Object obj)`方法用来判断两个对象是否“相同”,如果“相同”则返回`true`,否则返回`false`。
9 | - `hashcode()`方法返回一个`int`数,在`Object`类中的默认实现是“将该对象的内部地址转换成一个整数返回”。
10 |
11 | 接下来有两个个关于这两个方法的重要规范:
12 | - 若重写`equals(Object obj)`方法,有必要重写`hashcode()`方法,确保通过`equals(Object obj)`方法判断结果为`true`的两个对象具备相等的`hashcode()`返回值。
13 | 说得简单点就是: 如果两个对象相同,那么他们的hashcode应该相等。不过请注意:这个只是规范,如果你非要写一个类让`equals(Object obj)`返回`true`
14 | 而`hashcode()`返回两个不相等的值,编译和运行都是不会报错的。不过这样违反了`Java`规范,程序也就埋下了`BUG`。
15 | - `如果equals(Object obj)`返回`false`,即两个对象“不相同”,并不要求对这两个对象调用`hashcode()`方法得到两个不相同的数。
16 | 说的简单点就是:“如果两个对象不相同,他们的`hashcode`可能相同”。
17 | - 如果两个对象相同,那么它们的`hashCode`值一定要相同;
18 | - 如果两个对象的`hashCode`相同,它们并不一定相同
19 | 上面说的对象相同指的是用`eqauls`方法比较。
20 | 你当然可以不按要求去做了,但你会发现,相同的对象可以出现在`Set`集合中。同时,增加新元素的效率会大大下降。
21 |
22 | ---
23 | - 邮箱 :charon.chui@gmail.com
24 | - Good Luck!
25 |
26 |
27 |
--------------------------------------------------------------------------------
/JavaKnowledge/shell.md:
--------------------------------------------------------------------------------
1 | shell
2 | ---
3 |
4 |
5 |
6 |
7 | ### ls:
8 |
9 | ls x* y*: 过滤文件中x和y开头的文件
10 | ls -F: 区分文件还是目录
11 | ls -l: 显示详细信息
12 |
13 | ### touch: 创建或修改文件时间
14 |
15 |
16 | ### cp: 复制文件
17 |
18 | cp sourceFile destinationFile,但是如果源文件存在会直接被覆盖,也不会提醒,
19 | 如果想要提醒需要加上-i, 例如cp -i 1.txt 2.txt
20 |
21 |
22 | ### mv移动重新命名
23 |
24 | 用法和cp一致
25 |
26 |
27 | ### rm删除
28 |
29 | rm -i xxx: -i会询问是否真的要删除
30 | rm -f xxx: 强制删除,不会询问
31 |
32 | 注意 对于rm命令,-r选项和-R选项的效果是一样的,都可以递归地删除目录中的文件。shell命令很少会对相同的功能使用大小写不同的选项。
33 | 一口气删除目录树的最终解决方案是使用rm -rf命令。该命令不声不响,能够直接删除指定目录及其所有内容。当然,这肯定是一个非常危险的命令,所以务必谨慎使用,并再三检查你要进行的操作是否符合预期。
34 |
35 |
36 | ### mkdir 创建目录
37 |
38 | mkdir命令的-p选项可以根据需要创建缺失的父目录。父目录是包含目录树中下一级目录的目录。
39 |
40 | ### rmdir 删除空目录
41 |
42 | ### file
43 |
44 | file命令是一个方便的小工具,能够探测文件的内部并判断文件类型:
45 | $ file .bashrc
46 | .bashrc: ASCII text
47 |
48 |
49 | ### cat 显示文本文件
50 |
51 | cat fileName
52 |
53 | -n 加上行号
54 |
55 | ### more
56 | cat的缺点是其开始运行后无法控制后续的操作,为了解决这个问题,有了more命令。
57 |
58 | more命令会显示文件内容,但会在显示每页数据之后暂停下来。
59 |
60 | ### ps 显示当前用户进程
61 |
62 | ps -ef显示系统中运行的所有进程
63 |
64 | ### top 实时监测进程
65 |
66 | ps命令虽然在收集系统中运行进程的信息时非常有用,但也存在不足之处:只能显示某个特定时间点的信息。如果想观察那些被频繁换入和换出内存的进程,ps命令就不太方便了。这正是top命令的用武之地。与ps命令相似,top命令也可以显示进程信息,但采用的是实时方式。
67 |
68 |
69 |
70 | ### kill pid, 通过pid发送信号
71 |
72 | ### pkill pname: pkill命令可以使用程序名代替PID来终止进程。除此之外,pkill命令也允许使用通配符。
73 |
74 |
75 | ### grep数据搜索
76 |
77 | 经常需要在大文件中搜索
78 |
79 | ### gzip压缩
80 |
81 | gzip: 压缩
82 | gzcat: 查看压缩过的文本文件内容
83 | gunzip:解压
84 |
85 | ### tar归档
86 |
87 | ar命令最开始是用于将文件写入磁带设备以作归档,但它也可以将输出写入文件,这种用法成了在Linux中归档数据的普遍做法。tar命令的格式如下
88 |
89 | // 该命令创建了一个名为test.tar的归档文件,包含目录test和test2的内容。
90 | tar -cvf test.tar test/ test2/
91 |
92 |
--------------------------------------------------------------------------------
/JavaKnowledge/单例的最佳实现方式.md:
--------------------------------------------------------------------------------
1 | 单例的最佳实现方式
2 | ===
3 |
4 | ```java
5 | public class Singleton {
6 | // Private constructor prevents instantiation from other classes
7 | private Singleton() { }
8 |
9 | /**
10 | * SingletonHolder is loaded on the first execution of Singleton.getInstance()
11 | * or the first access to SingletonHolder.INSTANCE, not before.
12 | */
13 | private static class SingletonHolder {
14 | public static final Singleton INSTANCE = new Singleton();
15 | }
16 |
17 | public static Singleton getInstance() {
18 | return SingletonHolder.INSTANCE;
19 | }
20 | }
21 | ```
22 |
23 | ---
24 |
25 | - 邮箱 :charon.chui@gmail.com
26 | - Good Luck!
--------------------------------------------------------------------------------
/JavaKnowledge/数据加密及解密.md:
--------------------------------------------------------------------------------
1 | 数据加密及解密
2 | ===
3 |
4 | 加密`incode`:对明文(`plaintext`可读懂的信息)进行翻译,使用不同的算法对明文以代码形式(密码)实施加密转换成密文(`ciphertext`)。
5 | 该过程的逆过程称为解密(`descode`),即将该编码信息转化为明文的过程。
6 |
7 | 对称加密(`Symmetric Cryptography`)
8 | ---
9 |
10 | 对称加密是最快速、最简单的一种加密方式,加密(`encryption`与解密(`decryption`)用的是同样的密钥(`secret key`),
11 | 这种方法在密码学中叫做对称加密算法。
12 | 对称加密有很多种算法,由于它效率很高,所以被广泛使用在很多加密协议的核心当中。
13 | 对称加密通常使用的是相对较小的密钥,一般小于256`bit`。因为密钥越大,加密越强,但加密与解密的过程越慢。
14 | 如果你只用1`bit`来做这个密钥,那黑客们可以先试着用0来解密,不行的话就再用1解;
15 | 但如果你的密钥有1`MB`大,黑客们可能永远也无法破解,但加密和解密的过程要花费很长的时间。
16 | 密钥的大小既要照顾到安全性,也要照顾到效率,是一个`trade-off`。
17 |
18 |
19 |
20 | 对称加密算法介绍:
21 |
22 | - DES
23 |
24 | DES`全称为`Data Encryption Standard`,即数据加密标准,是一种使用密钥加密的块算法,
25 | 1976年被美国联邦政府的国家标准局确定为联邦资料处理标准(`FIPS`),随后在国际上广泛流传开来。`
26 |
27 | `DES`算法的入口参数有三个:`Key`、`Data`、`Mode`。其中`Key`为8个字节共64位,是`DES`算法的工作密钥;`Data`也为8个字节64位,
28 | 是要被加密或被解密的数据;`Mode`为`DES`的工作方式,有两种:加密或解密。
29 | `DES`算法把64位的明文输入块变为64位的密文输出块,它所使用的密钥也是64位。
30 |
31 | - AES
32 | `AES`全程为`Advanced Encryption Standard`,即高级加密标准,在密码学中又称`Rijndael`加密法,
33 | 是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的`DES`,已经被多方分析且广为全世界所使用。
34 | `AES`加密数据块分组长度必须为128比特,密钥长度可以是128比特、192比特、256比特中的任意一个(如果数据块及密钥长度不足时,会补齐)
35 |
36 |
37 |
38 | 非对称加密(`Asymmetric Cryptography`)
39 | ---
40 |
41 | 1976年,美国学者`Dime`和`Henman`为解决信息公开传送和密钥管理问题,提出一种新的密钥交换协议,允许在不安全的媒体上的通讯双方交换信息,
42 | 安全地达成一致的密钥,这就是“公开密钥系统”。相对于“对称加密算法”这种方法也叫做“非对称加密算法”。
43 | 非对称加密为数据的加密与解密提供了一个非常安全的方法,它使用了一对密钥,公钥(`public key`)和私钥(`private key`)。
44 | 私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。
45 | 比如,你向银行请求公钥,银行将公钥发给你,你使用公钥对消息加密,那么只有私钥的持有人--银行才能对你的消息解密。
46 | 与对称加密不同的是,银行不需要将私钥通过网络发送出去,因此安全性大大提高。
47 | 目前最常用的非对称加密算法是`RSA`算法,是`Rivest`, `Shamir`和`Adleman`于1978年发明。
48 |
49 | 虽然非对称加密很安全,但是和对称加密比起来,它非常的慢,所以我们还是要用对称加密来传送消息,
50 | 但对称加密所使用的密钥我们可以通过非对称加密的方式发送出去。
51 |
52 |
53 | 总结
54 | ---
55 |
56 | - 对称加密加密与解密使用的是同样的密钥,所以速度快,但由于需要将密钥在网络传输,所以安全性不高。
57 | - 非对称加密使用了一对密钥,公钥与私钥,所以安全性高,但加密与解密速度慢。
58 | - 解决的办法是将对称加密的密钥使用非对称加密的公钥进行加密,然后发送出去,接收方使用私钥进行解密得到对称加密的密钥,
59 | 然后双方可以使用对称加密来进行沟通。
60 |
61 | ----
62 | - 邮箱 :charon.chui@gmail.com
63 | - Good Luck!
64 |
65 |
--------------------------------------------------------------------------------
/JavaKnowledge/死锁.md:
--------------------------------------------------------------------------------
1 | 死锁
2 | ===
3 |
4 | ```java
5 | /**
6 | * 死锁的原因就是同步的嵌套
7 | */
8 | public class DeadLockTest {
9 | public static void main(String[] args) {
10 | Thread t1 = new Thread(new PrintRunnable(true));
11 | Thread t2 = new Thread(new PrintRunnable(false));
12 | t1.start();
13 | t2.start();
14 | }
15 | }
16 |
17 | class MyLock {
18 | static Object locka = new Object();
19 | static Object lockb = new Object();
20 | }
21 |
22 | class PrintRunnable implements Runnable {
23 | private boolean flag;
24 |
25 | PrintRunnable(boolean flag) {
26 | this.flag = flag;
27 | }
28 |
29 | public void run() {
30 | if (flag) {
31 | while (true) {
32 | synchronized (MyLock.locka) {
33 | System.out.println(Thread.currentThread().getName()
34 | + "...if locka ");
35 | synchronized (MyLock.lockb) {
36 | System.out.println(Thread.currentThread().getName()
37 | + "..if lockb");
38 | }
39 | }
40 | }
41 | } else {
42 | while (true) {
43 | synchronized (MyLock.lockb) {
44 | System.out.println(Thread.currentThread().getName()
45 | + "..else lockb");
46 | synchronized (MyLock.locka) {
47 | System.out.println(Thread.currentThread().getName()
48 | + ".....else locka");
49 | }
50 | }
51 | }
52 | }
53 | }
54 | }
55 | ```
56 |
57 | ---
58 |
59 | - 邮箱 :charon.chui@gmail.com
60 | - Good Luck!
61 |
--------------------------------------------------------------------------------
/JavaKnowledge/获取今后多少天后的日期.md:
--------------------------------------------------------------------------------
1 | 获取今后多少天后的日期
2 | ===
3 |
4 | ```java
5 | /**
6 | * Get the date some days later.
7 | * @param year the year
8 | * @param month month of the year
9 | * @param day day of the month
10 | * @return if the parameter is illegal this will return null
11 | */
12 | @SuppressLint("SimpleDateFormat")
13 | private static String getClosingDate(int year, int month, int day) {
14 | final int internalDay = 31;
15 | final String pattern = "yyyy-MM-dd";
16 | DateFormat dateFormat = new SimpleDateFormat(pattern);
17 | Date closingDate;
18 | try {
19 | Calendar thisDay = Calendar.getInstance();
20 | thisDay.set(Calendar.YEAR, year);
21 | thisDay.set(Calendar.MONTH, month - 1);// the first month of the year is 0.
22 | thisDay.set(Calendar.DAY_OF_MONTH, day);
23 | thisDay.add(Calendar.DAY_OF_MONTH, internalDay);
24 | closingDate = thisDay.getTime();
25 | } catch (Exception e) {
26 | e.printStackTrace();
27 | return null;
28 | }
29 | return dateFormat.format(closingDate);
30 | }
31 | ```
32 | ---
33 |
34 | - 邮箱 :charon.chui@gmail.com
35 | - Good Luck!
--------------------------------------------------------------------------------
/Jetpack/architecture/13.Jetpack MVVM简介.md:
--------------------------------------------------------------------------------
1 | # 13.Jetpack MVVM简介
2 |
3 | 项目地址:[android-architecture](https://github.com/googlesamples/android-architecture)
4 | `Google`将该项目命名为`Android`的架构蓝图,我想从名字上已可以看穿一切。
5 |
6 | 在它的官方介绍中是这样说的:
7 |
8 | > The Android framework offers a lot of flexibility when it comes to defining how to organize and architect an Android app. This freedom, whilst very valuable, can also result in apps with large classes, inconsistent naming and architectures (or lack of) that can make testing, maintaining and extending difficult.
9 |
10 | > Android Architecture Blueprints is meant to demonstrate possible ways to help with these common problems. In this project we offer the same application implemented using different architectural concepts and tools.
11 |
12 | > You can use these samples as a reference or as a starting point for creating your own apps. The focus here is on code structure, architecture, testing and maintainability. However, bear in mind that there are many ways to build apps with these architectures and tools, depending on your priorities, so these shouldn't be considered canonical examples. The UI is deliberately kept simple.
13 |
14 | Jetpack MVVM 是 MVVM 模式在 Android 开发中的一个具体实现,是 Android中 Google 官方提供并推荐的 MVVM实现方式。
15 | 不仅通过数据驱动完成彻底解耦,还兼顾了 Android 页面开发中其他不可预期的错误,例如Lifecycle 能在妥善处理 页面生命周期 避免view空指针问题,ViewModel使得UI发生重建时 无需重新向后台请求数据,节省了开销,让视图重建时更快展示数据。
16 | 首先,请查看下图,该图显示了所有模块应如何彼此交互:
17 |
18 | 各模块对应MVVM架构:
19 |
20 | View层:Activity/Fragment
21 | ViewModel层:Jetpack ViewModel + Jetpack LivaData
22 | Model层:Repository仓库,包含 本地持久性数据 和 服务端数据
23 |
24 | View层 包含了我们平时写的Activity/Fragment/布局文件等与界面相关的东西。
25 | ViewModel层 用于持有和UI元素相关的数据,以保证这些数据在屏幕旋转时不会丢失,并且还要提供接口给View层调用以及和仓库层进行通信。
26 | 仓库层 要做的主要工作是判断调用方请求的数据应该是从本地数据源中获取还是从网络数据源中获取,并将获取到的数据返回给调用方。本地数据源可以使用数据库、SharedPreferences等持久化技术来实现,而网络数据源则通常使用Retrofit访问服务器提供的Webservice接口来实现。
27 | 另外,图中所有的箭头都是单向的,例如View层指向了ViewModel层,表示View层会持有ViewModel层的引用,但是反过来ViewModel层却不能持有View层的引用。除此之外,引用也不能跨层持有,比如View层不能持有仓库层的引用,谨记每一层的组件都只能与它相邻层的组件进行交互。
28 | 这种设计打造了一致且愉快的用户体验。无论用户上次使用应用是在几分钟前还是几天之前,现在回到应用时都会立即看到应用在本地保留的数据。如果此数据已过期,则应用的Repository将开始在后台更新数据。
29 |
30 | 有人可能会有疑惑:怎么完全没有提 DataBinding、双向绑定?
31 | 实际上,这也是我之前的疑惑。 没有提 是因为:
32 |
33 | 我不想让读者 一提到 MVVM 就和DataBinding联系起来
34 | 我想让读者 抓住 MVVM 数据驱动 的本质。
35 | 而DataBinding提供的双向绑定,是用来完善Jetpack MVVM 的工具,其本身在业界又非常具有争议性。
36 | 掌握本篇内容,已经是Google推荐的开发架构,就已经实现 MVVM 模式。在Google官方的 应用架构指南 中 也同样丝毫没有提到 DataBinding。
37 |
38 |
39 | ## 参考
40 | - [“终于懂了“系列:Jetpack AAC完整解析(四)MVVM - Android架构探索!](https://juejin.cn/post/6921321173661777933)
41 |
42 |
43 | - [上一篇:12.Navigation简介](https://github.com/CharonChui/AndroidNote/blob/master/Jetpack/architecture/12.Navigation%E7%AE%80%E4%BB%8B.md)
44 | - [下一篇:14.findViewById的过去及未来](https://github.com/CharonChui/AndroidNote/blob/master/Jetpack/architecture/14.findViewById%E7%9A%84%E8%BF%87%E5%8E%BB%E5%8F%8A%E6%9C%AA%E6%9D%A5.md)
45 |
46 |
47 | ---
48 |
49 | - 邮箱 :charon.chui@gmail.com
50 | - Good Luck! `
--------------------------------------------------------------------------------
/Jetpack/architecture/14.findViewById的过去及未来.md:
--------------------------------------------------------------------------------
1 | # 14.findViewById的过去及未来
2 |
3 | We have lots of alternatives for this, and you may wonder why do we need another solution. Let’s compare the different solutions based on these criteria: null-safety, compile-time safety, and speed.
4 |
5 | | Column 1 | **[ButterKnife](https://github.com/JakeWharton/butterknife)** | [**Kotlin Synthetics**](https://developer.android.com/kotlin/ktx) | [**Data Binding**](https://developer.android.com/topic/libraries/data-binding) | [**findViewById**](https://developer.android.com/reference/android/app/Activity#findViewById(int)) | [View Binding](https://developer.android.com/topic/libraries/view-binding) |
6 | | --------------------- | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: |
7 | | **Fast** | ❌ * | ✅ | ❌ * | ✅ | ✅ |
8 | | **Null-safe** | ❌ | ❌ | ✅ | ❌ | ✅ |
9 | | **Compile-time safe** | ❌ | ❌ | ✅ | ✅ ** | ✅ |
10 |
11 | \* ButterKnife and Data Binding solutions are slower because they use an annotation-based approach
12 | ** `findViewById()` is compile-time safe since API 26 because we don’t need to cast the type of view anymore.
13 |
14 | https://juejin.cn/post/6905942568467759111
15 |
16 | https://medium.com/mobile-app-development-publication/how-android-access-view-item-the-past-to-the-future-bb003ae84527
17 |
18 |
19 |
20 | ## 参考
21 | - [Kotlin 插件的落幕,ViewBinding 的崛起](https://juejin.cn/post/6905942568467759111)
22 | - [How Android Access View Item: The Past to the Future](https://medium.com/mobile-app-development-publication/how-android-access-view-item-the-past-to-the-future-bb003ae84527)
23 |
24 |
25 |
26 | - [上一篇:13.Jetpack MVVM简介](https://github.com/CharonChui/AndroidNote/blob/master/Jetpack/architecture/13.Jetpack%20MVVM%E7%AE%80%E4%BB%8B.md)
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Jetpack/architecture/6.DataBinding简介.md:
--------------------------------------------------------------------------------
1 | # 6.DataBinding简介
2 |
3 |
4 |
5 | https://developer.android.com/topic/libraries/data-binding
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | - [上一篇:5.LiveData简介](https://github.com/CharonChui/AndroidNote/blob/master/Jetpack/architecture/5.LiveData%E7%AE%80%E4%BB%8B.md)
15 | - [下一篇:7.Room简介](https://github.com/CharonChui/AndroidNote/blob/master/Jetpack/architecture/7.Room%E7%AE%80%E4%BB%8B.md)
16 |
17 |
18 |
19 |
20 |
21 | ---
22 |
23 | - 邮箱 :charon.chui@gmail.com
24 | - Good Luck!
--------------------------------------------------------------------------------
/Jetpack/architecture/8.PagingLibrary简介.md:
--------------------------------------------------------------------------------
1 | 8.PagingLibrary简介
2 | ===
3 |
4 |
5 |
6 |
7 |
8 | - [上一篇:7.Room简介](https://github.com/CharonChui/AndroidNote/blob/master/Jetpack/architecture/7.Room%E7%AE%80%E4%BB%8B.md)
9 | - [下一篇:9.App Startup简介](https://github.com/CharonChui/AndroidNote/blob/master/Jetpack/architecture/9.App%20Startup%E7%AE%80%E4%BB%8B.md)
10 |
11 | ---
12 |
13 | - 邮箱 :charon.chui@gmail.com
14 | - Good Luck! `
--------------------------------------------------------------------------------
/Jetpack/behavior/1.简介.md:
--------------------------------------------------------------------------------
1 | # 1.简介
2 |
3 |
4 | ### Behavior(行为组件)
5 | 行为组件可帮助开发者的应用与标准Android服务(如通知、权限、分享和Google助理)相集成。它包含如下组件库:
6 | - CameraX:帮助开发者简化相机应用的开发工作。它提供一致且易于使用的 API 界面,适用于大多数 Android 设备,并可向后兼容至 Android 5.0(API 级别 21)。
7 | - DownloadManager下载管理器:可处理长时间运行的HTTP下载,并在出现故障或在连接更改和系统重新启动后重试下载。
8 | - Media & playback(媒体&播放):用于媒体播放和路由(包括 Google Cast)的向后兼容 API。
9 | - Notifications(通知):提供向后兼容的通知 API,支持 Wear 和 Auto。
10 | - Permissions(权限):用于检查和请求应用权限的兼容性 API。
11 | - Preferences(偏好设置):提供了用户能够改变应用的功能和行为能力。
12 | - Sharing(共享):提供适合应用操作栏的共享操作。
13 | - Slices(切片):创建可在应用外部显示应用数据的灵活界面元素。
14 |
15 |
--------------------------------------------------------------------------------
/Jetpack/foundation/1.简介.md:
--------------------------------------------------------------------------------
1 | # 1.简介
2 |
3 |
4 | ### Foundation(基础组件):
5 | 基础组件提供了横向功能,例如向后兼容性、测试以及Kotlin语言的支持。它包含如下组件库:
6 | - Android KTX:Android KTX 是一组 Kotlin 扩展程序,它优化了供Kotlin使用的Jetpack和Android平台的API。以更简洁、更愉悦、更惯用的方式使用Kotlin进行Android开发。
7 | - AppCompat:提供了一系列以AppCompat开头的API,以便兼容低版本的Android开发。Jetpack基础中的AppCompat库包含v7库中的所有组件([支持库软件包](https://developer.android.com/topic/libraries/support-library/packages#v7-appcompat))。 其中包括AppCompat,Cardview,GridLayout,MediaRouter,Palette,RecyclerView,Renderscript,Preferences,Leanback,Vector Drawable,Design,Custom选项卡等。此外,该库为材质设计用户界面提供了实现支持,这使得AppCompat对 开发人员。 以下是android应用程序的一些关键领域,这些领域很难构建,但是可以使用AppCompat库轻松进行设计: 一般都是为了兼容 Android L以下版本,来提供Material Design的效果:
8 | - Toolbar
9 | - ContextCompat
10 | - AppCompatDialog
11 | - annotation:注解,提升代码可读性,内置了Android中常用的注解
12 | - Multidex(多Dex处理):为方法数超过 64K 的应用启用多 dex 文件。Security(安全):按照安全最佳做法读写加密文件和共享偏好设置。
13 | - Test(测试):用于单元和运行时界面测试的 Android 测试框架。
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Jetpack/ui/Jetpack Compose组件.md:
--------------------------------------------------------------------------------
1 | # Jetpack Compose组件
2 |
3 | ### Text组件
4 |
5 | ```kotlin
6 | Text(text = "Hello World")
7 | Text(
8 | text = stringResource(id = R.string.next),
9 | style = TextStyle(
10 | fontSize = 25.sp,
11 | fontWeight = FontWeight.Bold,
12 | lineHeight = 35.sp,
13 | color = Color.Red,
14 | textDecoration = TextDecoration.LineThrough,
15 | fontStyle = FontStyle.Italic
16 | )
17 | )
18 | ```
19 |
20 | #### AnnotatedString多样式文字
21 |
22 | 在很多应用场景中,我们需要在一段文字中对局部内容应用特别格式以示突出,比如一个超链接或者一个电话号码等,此时需要用到AnnotatedString。AnnotatedString是一个数据类,除了文本值,它还包含了一个SpanStyle和ParagraphStyle的Range列表。SpanStyle用于描述在文本中子串的文字样式,ParagraphStyle则用于描述文本中子串的段落样式,Range确定子串的范围。
23 |
24 | ```kotlin
25 | val annotedText = buildAnnotatedString {
26 | withStyle(style = SpanStyle(fontSize = 23.sp)) {
27 | pushStringAnnotation(tag = "url", annotation = "https://www.baidu.com")
28 | append("haha")
29 | }
30 |
31 | withStyle(SpanStyle(fontSize = 30.sp)) {
32 | append("A")
33 | }
34 | }
35 |
36 | ClickableText(
37 | text = annotedText,
38 | onClick = { offset ->
39 | annotedText.getStringAnnotations(tag = "url", start = offset, end = offset)
40 | .firstOrNull()?.let {
41 | Log.e("@@@", it.item)
42 | }
43 | }
44 | )
45 | ```
46 |
47 | Compose提供了一种可点击文本组件ClickedText,可以响应我们对文字的点击,并返回点击位置。可以让AnnotatdString子串在相应的ClickedText中点击后,做出不同的动作。
48 |
49 |
50 |
51 |
52 |
53 | ### SelectionContainer
54 |
55 | 选中文字Text自身默认是不能被长按选择的,否则在Button中使用时,又会出现那种“可粘贴的Button”的例子。
56 | Compose提供了专门的SelectionContainer组件,对包裹的Text进行选中。可见Compose在组件设计上,将关注点分离的原则发挥到了极致。
57 |
58 |
59 | ```kotlin
60 | SelectionContainer {
61 | Text("hahah")
62 | }
63 | ```
64 |
65 |
66 |
67 | ### TextField输入框
68 |
69 |
70 | TextField有两种风格,一种是默认的,也就是filled,另一种是OutlinedTextField。
71 | ```kotlin
72 | var text by remember { mutableStateOf("") }
73 |
74 | TextField(value = text, onValueChange = {
75 | text = it
76 | }, label = { Text("请输入用户名") })
77 | ```
78 |
79 | 这个text是一个可以变化的文本,用来显示TextField输入框中当前输入的文本内容。 在onValueChange回调中可以获取来自软键盘的最新输入,我们利用这个信息来更新可变状态text,驱动界面刷新显示最新的输入文本。
80 |
81 |
82 | ***来自软键盘的输入内容不会直接更新TextField, TextField需要通过观察额外的状态更新自身,这也体现了声明式UI中“状态驱动UI”的基本理念。***
83 |
84 |
85 |
86 |
87 |
88 | ### Column组件
89 |
90 | 很多产品中都有展示一组数据的需求场景,如果数据数量是可以枚举的,则仅需通过Column组件来枚举列出。
91 |
92 |
93 | 然而很多时候,列表中的项目会非常多,例如通讯录、短信、音乐列表等,我们需要滑动列表来查看所有的内容,可以通过Column的Modifier添加verticalScroll()方法来让列表实现滑动。
94 |
95 |
96 | #### LazyComposables
97 |
98 | 给Column的Modifier添加verticalScroll()方法可以让列表实现滑动。
99 |
100 | 但是如果列表过长,众多的内容会占用大量的内存。然而更多的内容对于用户其实都是不可见的,没必要记载到内存。
101 |
102 | 所以Compose提供了专门用于处理长列表的组件,这些组件指挥在我们能看到的列表部分进行重组和布局,它们分别是LazyColumn和LazyRow。其作用类似于传统视图中的ListView或者RecyclerView。
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/Jetpack/ui/material/7.Snackbar简介.md:
--------------------------------------------------------------------------------
1 | # 7.Snackbar简介
2 |
3 | Snackbar显示在所有屏幕其它元素之上(屏幕最顶层),同一时间只能显示一个snackbar。Snackbar的基本使用很简单,与Toast类似。
4 |
5 | ```java
6 | Snackbar.make(view, message_text, duration)
7 | .setAction(action_text, click_listener)
8 | .show();
9 | ```
10 |
11 | make()方法是生成Snackbar的。Snackbar需要一个控件容器view用来容纳,官方推荐使用CoordinatorLayout来确保Snackbar和其他组件的交互,比如滑动取消Snackbar、Snackbar出现时FloatingActionButton上移。显示时间duration有三种类型LENGTH_SHORT、LENGTH_LONG和LENGTH_INDEFINITE。
12 |
13 | setAction()方法可设置Snackbar右侧按钮,增加进行交互事件。如果不使用setAction()则只显示左侧message。
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | - [上一篇:6.CollapsingToolbarLayout简介](https://github.com/CharonChui/AndroidNote/blob/master/Jetpack/ui/material/6.CollapsingToolbarLayout%E7%AE%80%E4%BB%8B.md)
37 | - [下一篇:8.TabLayout简介](https://github.com/CharonChui/AndroidNote/blob/master/Jetpack/ui/material/8.TabLayout%E7%AE%80%E4%BB%8B.md)
38 |
--------------------------------------------------------------------------------
/Jetpack/ui/material/8.TabLayout简介.md:
--------------------------------------------------------------------------------
1 | # 8.TabLayout简介
2 |
3 | ```
4 | public class TabLayout extends HorizontalScrollView {
5 | ```
6 |
7 | 所有的 Tab 选项卡实例化都是通过 TabLayout.Tab 完成的。你可以通过 TabLayout.newTab()来创建 Tab 对象。你可以通过更改Tab 的setText()、setIcon()分别设置选项卡的文字和 Icon。要显示选项卡 Tab,你必须通过一个方法 addTab(tab)方法将其添加到布局。例如:
8 |
9 | 如果你的 ViewPager 和这个布局用在一起,你可以调用 setupWithVIewPager(ViewPager)两个链接在一起,这种布局将会自动填充 PagerAdapter 的页面标题
10 |
11 | 你也可以把这种用法当成 ViewPager 的装饰,并且可以这样写布局资源直接添加到 ViewPager 当中:
12 |
13 | ```xml
14 |
17 |
18 |
22 |
23 |
24 | ```
25 |
26 | - app:tabSelectedTextColor:选中字体的颜色
27 | - app:tabTextColor:未选中字体的颜色
28 | - app:tabIndicatorColor:指示器下标的颜色
29 | - app:tabIndicatorHeight:指示器下标的高度
30 | - app:tabGravity:tab中布局位置,有两种模式center和fill
31 | - app:tabTextAppearance:字体大小
32 | - app:tabBackground:设置背景
33 | - app:tabMode:设置tablayout的排列模式,有两种scrollable和fixed;默认是fixed。fixed是固定的,适用于标签较少,scrollable是可滚动的,适用于标签较多的情况下
34 |
35 |
36 | - [上一篇:7.Snackbar简介](https://github.com/CharonChui/AndroidNote/blob/master/Jetpack/ui/material/7.Snackbar%E7%AE%80%E4%BB%8B.md)
--------------------------------------------------------------------------------
/Jetpack/ui/material/9.BottomNavigation简介.md:
--------------------------------------------------------------------------------
1 | # 9.BottomNavigation简介
2 |
3 |
4 |
5 |
6 | - [上一篇:7.Snackbar简介](https://github.com/CharonChui/AndroidNote/blob/master/Jetpack/ui/material/7.Snackbar%E7%AE%80%E4%BB%8B.md)
--------------------------------------------------------------------------------
/MobileAIModel/TensorFlow Lite:
--------------------------------------------------------------------------------
1 | TensorFlow Lite
2 | ---
3 |
4 | [TensorFlow Lite](https://tensorflow.google.cn/lite?hl=zh-cn) 是一个移动端库,可用于在移动设备、微控制器和其他边缘设备上部署模型。
5 |
6 |
7 | [TensorFlow Lite](https://tensorflow.google.cn/lite?hl=zh-cn) 是一组工具,可帮助开发者在移动设备、嵌入式设备和 loT 设备上运行模型,以便实现设备端机器学习。
8 |
9 |
10 | TensorFlow Lite是TensorFlow在手机和嵌入设备上的一个轻量级框架。
11 |
12 | 主要特性:
13 |
14 | - 通过解决以下 5 项约束条件,针对设备端机器学习进行了优化:延时(数据无需往返服务器)、隐私(没有任何个人数据离开设备)、连接性(无需连接互联网)、大小(缩减了模型和二进制文件的大小)和功耗(高效推断,且无需网络连接)。
15 | - 支持多种平台,涵盖 Android 和 iOS 设备、嵌入式 Linux 和微控制器。
16 | - 支持多种语言,包括 Java、Swift、Objective-C、C++ 和 Python。
17 | 高性能,支持硬件加速和模型优化。
18 | - 提供多种平台上的常见机器学习任务的端到端示例,例如图像分类、对象检测、姿势估计、问题回答、文本分类等。
19 |
20 |
21 | 1. 创建 TensorFlow Lite 模型
22 | TensorFlow Lite 模型以名为 FlatBuffer 的专用高效可移植格式(由“.tflite”文件扩展名标识)表示。
23 |
24 | 与 TensorFlow 的协议缓冲区模型格式相比,这种格式具有多种优势,例如可缩减大小(代码占用的空间较小)以及提高推断速度(可直接访问数据,无需执行额外的解析/解压缩步骤),这样一来,TensorFlow Lite 即可在计算和内存资源有限的设备上高效地运行。
25 |
26 | TensorFlow Lite 模型可以选择包含元数据,并在元数据中添加人类可读的模型说明和机器可读的数据,以便在设备推断过程中自动生成处理前和处理后流水线。如需了解详情,请参阅添加元数据。
27 |
28 | 您可以通过以下方式生成 TensorFlow Lite 模型:
29 |
30 | 使用现有的 TensorFlow Lite 模型:若要选择现有模型,请参阅 TensorFlow Lite 示例。模型可能包含元数据,也可能不含元数据。
31 |
32 | 创建 TensorFlow Lite 模型:使用 TensorFlow Lite Model Maker,利用您自己的自定义数据集创建模型。默认情况下,所有模型都包含元数据。
33 |
34 | 将 TensorFlow 模型转换为 TensorFlow Lite 模型:使用 TensorFlow Lite Converter 将 TensorFlow 模型转换为 TensorFlow Lite 模型。在转换过程中,您可以应用量化等优化措施,以缩减模型大小和缩短延时,并最大限度降低或完全避免准确率损失。默认情况下,所有模型都不含元数据。
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/OperatingSystem/4.调度.md:
--------------------------------------------------------------------------------
1 | # 4.调度
2 |
3 |
4 |
5 | 在多道程序设计系统中,内存中有多个进程,每个进程要么正在处理器上运行,要么正在等待某些事件的发生,比如I/O完成。处理器调度的目的是:以满足系统目标(如响应时间、吞吐率、处理器效率)的方式,把进程分配到一个或多个处理器上执行。处理器(或处理器组)通过执行某个进程而保持忙状态,而此时其他进程处于堵塞状态。典型的调度有四种:
6 |
7 | ### 长程调度
8 |
9 | 决定哪个程序可以进入系统中处理,因此它控制了系统的并发度。一旦允许进入,作业或用户程序就成为进程,并添加到供短程调度程序使用的队列中,等待调度。
10 |
11 |
12 |
13 | ### 中程调度
14 |
15 | 决定加入部分或全部位于内存中的进程集合。它是交换功能的一部分。典型情况下,换入(swapping-in)决定取决于管理系统并发度的需求。
16 |
17 |
18 |
19 | ### 短程调度
20 |
21 | 决定处理器执行哪个可运行进程。
22 |
23 | 长程调度程序执行的频率相对较低,并且只是大致决定是否接受新进程和接受哪个新进程。要进行交换决定,中程调度程序需要执行的稍频繁一些。短程调度程序,也成为分派程序(dispatcher),执行的最频繁,它精确的决定下次执行哪个进程。导致当前进程堵塞或抢占当前运行进程的事发生时,调用短程调度程序。这类事件包括:
24 |
25 | - 时钟中断
26 | - I/O中断
27 | - 操作系统调用
28 | - 信号(如信号量)
29 |
30 |
31 |
32 | ### I/O调度
33 |
34 | 决定可用I/O设备处理哪个进程挂起的I/O请求
35 |
36 |
37 |
38 | ## 多处理器调度
39 |
40 |
41 |
42 | 多处理器系统分为以下几类:
43 |
44 | - 松耦合、分布式多处理器、集群:由一系列相对自治的系统组成,每个处理器都有自身的内存和I/O通道。
45 | - 专用处理器:I/O处理器是一个典型的例子。此时,有一个通用的主处理器,专用处理器由主处理器控制,并为主处理器提供服务。
46 | - 紧耦合多处理器:由一些列共享同一个内存并受操作系统完全控制的处理器组成。
47 |
48 |
49 |
50 | 多处理器中的调度涉及三个相互关联的问题:
51 |
52 | - 把进程分配到处理器
53 |
54 | 假设多处理器的结构是统一的,即没有哪个处理器在访问内核和I/O设备时具有物理上的特别优势,那么最简单的调度方法是把处理器视为一个资源池,并按照要求把进程分配到相应的处理器。但是这样就牵扯到静态还是动态的问题。如果一个进程从被激活到完成,一直被分配给同一个处理器,那么就需要为每个处理器维护一个专门的短程队列。这种方法的优点是调度的开销较小,因为相对于所有进程,关于处理器的分配只进行一次。静态分配的缺点就是一个处理器可能处于空闲状态,这时其队列为空,而另一个处理器却积压了许多工作。为了防止这种情况,需要使用一个公共队列。所有进程都进入一个全局队列,然后调度到任何一个可用的处理器中。这样,在一个进程的生命周期中,它可以在不同的时间于不同的处理器上执行。另一种分配策略是动态负载平衡,在该策略中,线程能在不同处理器所对应的队列之间转移。Linux采用的就是这种动态分配策略。
55 |
56 |
57 |
58 | - 在单处理器上使用多道程序设计
59 |
60 | 单处理器能够在许多进程间切换,以达到较高的利用率和更好的性能。
61 |
62 | - 一个进程的实际分派
63 |
64 | 与多处理器调度相关的最后一个设计问题是选择哪个进程运行。在多道程序单处理器上,与简单的先来先服务策略相比,使用优先级或基于使用历史的高级调度算法可以提高性能。考虑多处理器时,这些复杂性可能是不必要的,甚至可能起到相反的效果,而相对比较简单的方法可能会更有效,而且开销比较低。
65 |
66 |
67 |
68 |
69 |
70 | ### 线程调度
71 |
72 | 在多处理器线程调度和处理器分配的各种方案中,有四个比较突出的方法:
73 |
74 | - 负载分配:进程不分配到某个特定的处理器。系统维护一个就绪线程的全局队列,每个处理器只要空闲就从队列中选择一个线程。
75 | - 组调度:一组相关的的线程基于一对一的原则,同时调度到一组处理器上运行。
76 | - 专用处理器分配:这种方法与负载分配方法正好相反,它通过把线程指定到处理器来定义隐式的调度。每个程序在其执行过程中,都分配给一组处理器,处理器的数量与程序中线程的数量相等。程序终止时,处理器返回总处理器池,以便分配给另一个程序。
77 | - 动态调度:在执行期间,进程中线程的数据可以改变,允许动态地改变进程中的线程数量,这就使得操作系统可以通过调整负载情况来提高利用率。
78 |
79 |
80 |
81 | ---
82 |
83 |
84 |
85 | - [上一篇:3.内存管理](https://github.com/CharonChui/AndroidNote/blob/master/OperatingSystem/3.%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86.md)
86 | - [下一篇:5.I/O](https://github.com/CharonChui/AndroidNote/blob/master/OperatingSystem/5.I:O.md)
87 |
88 |
89 | ---
90 |
91 | - 邮箱 :charon.chui@gmail.com
92 | - Good Luck!
93 |
--------------------------------------------------------------------------------
/OperatingSystem/7.嵌入式系统.md:
--------------------------------------------------------------------------------
1 | # 7.嵌入式系统
2 |
3 |
4 |
5 | 为完成某个特定功能而设计的,或许有附加机制或其他部分的计算机硬件和软件结合体。在许多情况下,嵌入式系统是一个更大系统或产品中的一部分,如汽车中的防抱死系统。
6 |
7 |
8 |
9 | ## 嵌入式Linux
10 |
11 | 嵌入式Linux是指运行在嵌入式系统中的Linux。嵌入式Linux是Linux的一个版本,是基于嵌入式设备的大小和硬件限制而定制的,它同时包括一些软件包,用于支持设备商运行的服务和应用。因此嵌入式Linux的内核比普通Linux的内核要小得多。
12 |
13 | 台式机/服务器Linux与嵌入式Linux的一个关键区别是: 台式机/服务器软件通常是在运行平台上编译的,而嵌入式Linux通常在一个平台上编译,但运行于另一个平台,后者称为交叉编译。
14 |
15 |
16 |
17 | Android是基于Linux内核的一个嵌入式系统,因此我们可以认为Android是嵌入式Linux的一个例子。但是,很多嵌入式Linux开发人员不认为Android系统是嵌入式Linux的实例。他们认为,传统的嵌入式系统拥有固定的功能,而且在出厂时就已确定。Adnroid能支持各种的应用,因此要比普通平台性操作系统强大得多。而且,Android是垂直一体化的系统,包括针对Linux内核的特定修改。
18 |
19 |
20 |
21 | ---
22 |
23 | - [上一篇:6.文件管理](https://github.com/CharonChui/AndroidNote/blob/master/OperatingSystem/6.%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86.md)
24 | - [下一篇:8.虚拟机](https://github.com/CharonChui/AndroidNote/blob/master/OperatingSystem/8.%E8%99%9A%E6%8B%9F%E6%9C%BA.md)
25 |
26 |
27 | ---
28 |
29 | - 邮箱 :charon.chui@gmail.com
30 | - Good Luck!
31 |
--------------------------------------------------------------------------------
/OperatingSystem/AndroidKernal/9.PackageManagerService简介.md:
--------------------------------------------------------------------------------
1 | # 9.PackageManagerService简介
2 |
3 | 程序包管理主要包含三部分内容:
4 |
5 | - 提供一个能够根据intent匹配到具体的Activity、Provider、Service。即当应用程序调用startActivity(intent)时,能够把参数中指定的intent转换成一个具体的包含了程序包名称及具体Component名称的信息,以便Java类加载器加载具体的Component。
6 | - 进行权限检查。即当应用程序调用某个需要一定权限的函数调用时,系统能够判断调用者是否具备该权限,从而保证系统的安全。
7 | - 提供安装、删除应用程序接口。
8 |
9 |
10 |
11 | 
12 |
13 | 该框架可以分为三层:
14 |
15 | - 应用程序层
16 |
17 | 应用程序需要使用包管理服务时,调用ContextImpl类的getPackageManager()函数返回一个PackageManager对象,然后调用该对象所提供的各种API接口。
18 |
19 | - PmS服务层
20 |
21 | 和AmS、WmS等其他系统服务一样,包管理服务运行于SystemServer进程。PmS服务运行时,使用了两个目录下的XML文件保存相关的包管理信息。
22 |
23 | - 第一个目录是system/etc/permissions:该目录下的所有xml文件用于permission的管理,具体包含两个事件。第一个是定义系统中都包含了哪些feature,应用程序可以在AndroidManifest.xml中使用use-feature标签声明程序都需要哪些feature。
24 | - 第二个目录是/data/system/packages.xml,该文件保存了所有安装程序的基本包信息,有点像系统的注册表,比如程序的包名称是什么,安装包路径在哪里,程序都是用了哪些系统权限。
25 |
26 | PmS在启动时,会从这两个目录中解析相关的XML文件,从而建立一个庞大的包信息树,应用程序可以间接从这个信息树中查询所有所需的程序包信息。
27 |
28 | 除了PmS服务外,还有两个辅助系统服务用于程序安装。 一个是DefaultContainerService,该服务主要用于把安装程序复制到程序目录中。另一个是Installer服务,该服务实际上并不是一个Binder,而是一个Socket客户端,PmS直接和该Socket客户端交互。Socket的服务端主要完成程序文件的解压工作及数据目录创建,比如从APK文件中提取dex文件,删除dalvik-cache(会把每个apk的dex文件放到该目录,方便提升执行速度)目录下的dex文件,创建程序专属的程序目录等。
29 |
30 | - 数据文件层
31 |
32 | 就像所有操作系统一样,Android中的程序也由相关的程序文件组成,这些程序文件可以分为三个部分:
33 |
34 | - 程序文件
35 |
36 | 所有的系统程序保存在/system/app目录下,所有的第三方应用程序保存在/data/app目录下,该目录中的APK与原始的APK文件的唯一区别是文件的名称不同,原始文件可以任意命名,而该目录下的文件名称是以包名进行命名,并自动增加一个"-x"后缀,比如com.android.haii.debugjar-1.apk。当同样一个程序第二次安装时,后面的数字1会变成数字2,而当第三次再安装时,又会变成数字1,有点像“乒乓”机制。。/data/dalvik-cache目录保存了程序中的执行代码。一个APK实际上是一个Jar压缩类型的文件,压缩包中包含了各种资源文件、资源索引文件、AndroidManifest文件及程序文件,当应用程序运行前,PmS会从APK文件中提取出代码文件,也就是所谓的dex文件,并将该文件存储在该目录下,以便以后能够快速运行该程序。比如: data@app@com.android.xxx-1.apk@classes.dex
37 |
38 | - framework库文件
39 |
40 | 这些库文件存在于/system/framework目录下,库文件类型是APK或者Jar,系统开机后,dalvik虚拟机会加载这些库文件,而在PmS启动时,如果这些Jar或者APK文件还没有被转换为dex文件,则PmS会将这些库文件转换为dex文件,并保存到/data/dalvik-cache目录下。
41 |
42 | - 应用程序所使用的数据文件
43 |
44 | 应用程序可以使用三种数据保存方式,分为参数存储、数据库存储、文件存储。这三种存储方式对应的数据文件一般都保存到/data/data/xxx目录下,xxx代表程序的包名。
45 |
46 |
47 |
48 | 安装及卸载程序的操作都是由PmS完成,安装程序的过程包括在程序目录下创建以包名称命名的程序文件、创建程序数据目录,以及把程序信息保存到相关的配置文件packages.xml中,卸载则是一个相反的操作。
49 |
50 |
51 |
52 |
53 |
54 | ### aipalign优化APK内部存储
55 |
56 | 所谓的内部存储优化是指,为了提高APK程序的加载速度,从而对APK中相关的数据进行边界对齐。因为从底层NAND Flash的角度来讲,读取NAND时,是以一个扇区进行读取的,因此,如果相关的数据能够在同一个扇区中,肯定会提高读取速度。zipalign的作用正是将APK包中的不同类型的数据文件进行边界对齐。
57 |
58 |
59 |
60 |
61 |
62 | ---
63 |
64 | - [上一篇:8.WindowManagerService简介](https://github.com/CharonChui/AndroidNote/blob/master/OperatingSystem/AndroidKernal/8.WindowManagerService%E7%AE%80%E4%BB%8B.md)
65 |
66 |
67 |
68 |
69 | ---
70 |
71 | - 邮箱 :charon.chui@gmail.com
72 | - Good Luck!
73 |
74 |
--------------------------------------------------------------------------------
/RxJavaPart/7.RxJava系列全家桶.md:
--------------------------------------------------------------------------------
1 | `RxJava Android`开发全系列
2 | ===
3 |
4 | 有关`RxJava`的介绍请看[RxJava详解系列][1]
5 |
6 | 要说16年`android`开发中要说那个应用最流行,那就是`RxJava`了,现在越来越多的`android`项目都会用到`RxJava`,下面就介绍一些`RxJava`必备的扩张库。
7 |
8 | `RxAndroid`
9 | ---
10 |
11 | [RxAndroid](https://github.com/ReactiveX/RxAndroid)
12 |
13 | > Android specific bindings for RxJava.
14 |
15 | > This module adds the minimum classes to RxJava that make writing reactive components in Android applications easy and hassle-free. More specifically, it provides a Scheduler that schedules on the main thread or any given Looper.
16 |
17 | `Android`中使用`RxJava`的必备类库,虽然里面提供的内容并不多只有`AndroidSchedulers`、`HandlerScheduler`、`LooperScheduler`,但是这些确是`Android`开发中的精髓。
18 |
19 | `RxLifecycle`
20 | ---
21 |
22 | [RxLifecycle](https://github.com/trello/RxLifecycle)
23 |
24 | > The utilities provided here allow for automatic completion of sequences based on Activity or Fragment lifecycle events. This capability is useful in Android, where incomplete subscriptions can cause memory leaks.
25 |
26 | `RxLifecycle`提供了一些配合`Activity`、`Fragment`生命周期使用的订阅管理的相关功能。例如使用`RxJava`执行一些耗时的操作,但是在执行过程中,用户退出了当前`Activity`,这时如果`Observable`未取消订阅就会导致内存泄漏,而`RxLifecycle`就是为了接着这些问题的。在`Activity`销毁的时候`RxLifecycle`会自动取消订阅。
27 |
28 | `RxBinding`
29 | ---
30 |
31 | [RxBinding](https://github.com/JakeWharton/RxBinding)
32 |
33 | > RxJava binding APIs for Android UI widgets from the platform and support libraries.
34 |
35 | `RxBinding`是把`Android`中`UI`事件转换为`RxJava`的方式,例如点击时间,每次点击后`Observable`的订阅者`Observer`都会通过`onNext()`回调得知。
36 |
37 | `Retrofit`
38 | ---
39 |
40 | [Retrofit](https://github.com/square/retrofit)
41 |
42 | > Type-safe HTTP client for Android and Java by Square, Inc.
43 |
44 | 良心企业`Square`出品的一个基于`OkHttp`的网络请求类库,完美支持`RxJava`。
45 |
46 | `SqlBrite`
47 | ---
48 |
49 | [SQLBrite](https://github.com/square/sqlbrite)
50 |
51 | > A lightweight wrapper around SQLiteOpenHelper and ContentResolver which introduces reactive stream semantics to queries.
52 |
53 | 良心企业`Square`出品的一个支持`RxJava`的`Sqlite`数据库的操作库。
54 |
55 | `RxPermissions`
56 |
57 | [RxPermissions)](https://github.com/tbruyelle/RxPermissions)
58 |
59 | > This library allows the usage of RxJava with the new Android M permission model.
60 |
61 | `Android M`上动态权限申请的类库。
62 |
63 |
64 | [1]: https://github.com/CharonChui/AndroidNote/tree/master/RxJavaPart "RxJava详解系列"
65 |
66 | - 邮箱 :charon.chui@gmail.com
67 | - Good Luck!
68 |
--------------------------------------------------------------------------------
/SourceAnalysis/ARouter解析.md:
--------------------------------------------------------------------------------
1 | # ARouter解析
2 |
3 | [ARouter](https://github.com/alibaba/ARouter)
4 |
5 | > 一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦
6 |
7 | 简单的说: 它是一个路由系统 ——— 给无依赖的组件提供通信和路由的能力。
8 |
9 | 举个例子:
10 | 你过节了你想写一个明信片递给远方的朋友,那你就需要通过邮局(ARoter)来把明信片派送给你的朋友(你和你的朋友相当于两个组件)。
11 |
12 | 使用ARouter在进行Activity跳转非常简单:
13 |
14 | - 初始化ARouter `ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化`
15 | - 添加注解@Route
16 | ```java
17 | // 在支持路由的页面上添加注解(必选)
18 | // 这里的路径需要注意的是至少需要有两级,/xx/xx
19 | @Route(path = "/test/activity")
20 | public class YourActivity extend Activity {
21 | ...
22 | }
23 | ```
24 | - 发起路由 `ARouter.getInstance().build("/test/activity").navigation();`
25 |
26 |
27 |
28 | ARouter框架能将多个服务提供者隔离,减少相互之间的依赖。其实现的流程和我们平常的快递物流管理很类似,每一个具体的快递包裹就是一个独立的服务提供者(IProvider),每一个快递信息单就是一个RouteMeta对象,客户端就是快递的接收方,而使用@Route注解中的path就是快递单号。在初始化流程中,主要完成的工作就是将所有注册的快递信息表都在物流中心(LogisticsCenter)注册,并将数据存储到数据仓库中(Warehouse)。
29 |
30 | 作者:魔焰之
31 | 链接:https://www.jianshu.com/p/11006054f156
32 | 来源:简书
33 | 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | ---
53 |
54 | - 邮箱 :charon.chui@gmail.com
55 | - Good Luck!
56 |
--------------------------------------------------------------------------------
/SourceAnalysis/Netowork/HttpURLConnection与HttpClient.md:
--------------------------------------------------------------------------------
1 | HttpURLConnection与HttpClient
2 | ===
3 |
4 | - `Java`的`HttpURLConnection`
5 | 请求默认带`Gzip`压缩。
6 | - `Apache`的`HttpClient`
7 | 请求默认不带`Gzip`压缩。
8 |
9 | 一般对于`API`请求返回的数据大多是`Json`类的字符串,`Gzip`压缩可以使数据大小大幅降低。
10 | `Retrofit`及`Volley`框架默认在`Android Gingerbread(API 9)`及以上都是用`HttpURLConnection`,9以下用`HttpClient`。
11 |
12 | ---
13 |
14 | - 邮箱 :charon.chui@gmail.com
15 | - Good Luck!
--------------------------------------------------------------------------------
/SourceAnalysis/Netowork/volley-retrofit-okhttp之我们该如何选择网路框架.md:
--------------------------------------------------------------------------------
1 | volley-retrofit-okhttp之我们该如何选择网路框架
2 | ===
3 |
4 | 说起`Volley`、`Retrofit`、`OkHttp`相信基本没有人不知道。当然这里把`OkHttp`放进来可能有些不恰当。
5 | 因为`OkHttp`的官方介绍是`An HTTP+HTTP/2 client for Android and Java applications`。
6 | 也就是说`OkHttp`是基于`http`协议封装的一套请求客户端。它是真正的网络请求部分,
7 | 与`HttpClient`、`HttpUrlConnection`是一样的,
8 | 但是显然它的效率非常高(说到这里顺便提一嘴,从`Android 4.4`开始`HttpUrlConnection`内部默认使用的也是`OkHttp`,
9 | 具体请参考之前的文章[HttpUrlConnection详解][1]
10 | 而`Volley`、`Retrofit`是控制请求的队列、切换、解析、缓存等逻辑。所以`Volley`和`Retrofit`都可以结合`OkHttp`来使用。
11 |
12 |
13 | 在`Android`开发中有很多网络请求框架,但是比较过来比较过去,最后最倾向的就是这两个:
14 |
15 | - `Volley`:`Google`发布的网络请求框架,专门为移动设备定制,小而美。
16 | - `Retrofit`:良心企业 `Square`由大神`JakeWharton`主导的开源项目,是基于`OkHttp`封装的一套`Resetful`网络请求框架。`Type-safe HTTP client for Android and Java by Square, Inc.`
17 |
18 |
19 | 有关`Volley`的介绍请看之前发布的文章[Volley源码分析][2]
20 |
21 |
22 | 这里就不分别介绍他俩了,直接说各自的优缺点:
23 |
24 | - `Retrofit`使用起来更简单。而`Volley`配置起来会稍微麻烦,因为`Volley`可以使用`HttpClient`、`HttpUrlConnection`、`OkHttp`我们需要根据自己的需求去配置。而`Retrofit`只能结合`OkHttp`使用。
25 |
26 | - `Retrofit`依赖于`OkHttp`,从而会导致它的包大小会比`Volley`的大。
27 |
28 | - `Volley`有很好的内存缓存管理,它在解析之前会将整个相应部分都加载到内存中,所以它对于小的网络请求非常合适,但是不支持`post`大数据,所以不适合上传文件。而`Retrofit`使用的是硬盘缓存,所以相比起从缓存这块来讲`Retrofit`可能会更慢一些。
29 |
30 | - `Retrofit`依赖于`OkHttp`,而`OkHttp`自身会避免同时两次请求同一个请求。所以`Retrofit`同样会和`Volley`一样去避免重复的请求,只不过它是在网络层来处理的。
31 |
32 | - `Volley`在网络请求部分默认依赖于`Apache HttpClient`。而`Apache HttpClient`从`API 23`开始已经在`Android`中被移除并废弃了。这就是为什么很多开发者会认为`Volley`已经过时了,因为`Volley`并没有迁移到新的未废弃的代码。
33 |
34 | - 默认情况下`Volley`会在`DefaultRetryPolicy`中会将读取和连接的超时时间设置为`2.5s`,并且对每次请求失败或者超时都有一次自动重试。 所以对于一些服务器响应可能会超过`2s`的请求,开发者需要格外的小心下。`Retrofit`的默认超时时间是`10s`,而且它对失败或者超时的操作不会自动重试。
35 | - 很多开发者都会说`Retrofit`会比`Volley`更快。因为有人专门去测试过,其实这里是不严谨的。因为`Volley`可以结合使用`HttpUrlConnection`、`HttpClient`、`OkHttp`等来使用,而`Retrofit`是用`OkHttp`一起,所以如果你让`Volley`结合`OkHttp`之后再来测试你就会发现总体来说其实他们不相上下。
36 |
37 |
38 | - `Volley`实现了很完善的`Activity`声明周期管理。
39 |
40 | 虽然`Volley`之前也有一些问题,但是它们也都被各个大神修复。
41 |
42 |
43 | 所以综合起来说使用`Volley+OKHttp`的组合是非常不错的,既可以保证速度又可以满足对缓存、重试等的处理。但是如果你是`RxJava`的使用者那你可能会更偏向于使用`Retrofit`,因为`Retrofit`可以无缝结合`RxJava`使用。目前主流的一套框架就是`Retrofit + OkHttp + RxJava + Dagger2 `,但是对使用者的要求也相对要高些。
44 |
45 |
46 | [1]: https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/Netowork/HttpURLConnection%E8%AF%A6%E8%A7%A3.md "HttpUrlConnection详解"
47 | [2]: https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/Netowork/Volley%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md "Volley源码分析"
48 |
49 | ---
50 |
51 | - 邮箱 :charon.chui@gmail.com
52 | - Good Luck!
--------------------------------------------------------------------------------
/Tools&Library/Github个人主页绑定域名.md:
--------------------------------------------------------------------------------
1 | Github个人主页绑定域名
2 | ===
3 |
4 | `Github`虽然很好,可毕竟是免费的,还是有不少限制的。写到这里,特意去看了下`Github`对免费用户究竟有什么限制。发现除了300M的空间限制(还是所谓软限制),没有其他限制。所以用它来作为博客平台,真是再理想不过了。
5 |
6 | 创建步骤
7 | ---
8 |
9 | 1. 建立一个博客`repository`
10 | 建立一个命名为`username.github.io`的`repository`, `username`就是你在`Github`上的用户名或机构名
11 |
12 | 2. 增加主页
13 | `clone`该`repository`到本地,增加`index.html`
14 |
15 | 3. 提交
16 | `commit`并且`push`该次修改。
17 |
18 | 4. OK
19 | 打开浏览器输入 `username.github.io` 即可。注意提交之后可能需要一小段时间的延迟。
20 |
21 |
22 | 绑定域名
23 | ---
24 |
25 | 1. 在`repository`根目录新建`CNAME`文件, 内容为`xxx.com`(要绑定的域名),然后`commit`、`push`.
26 | 2. 在自己的域名管理页面中,进入域名解析.
27 | 
28 | **注意记录值 `username.github.io.` (最后面有一个.)**
29 |
30 | ---
31 |
32 | - 邮箱 :charon.chui@gmail.com
33 | - Good Luck!
--------------------------------------------------------------------------------
/Tools&Library/Icon制作.md:
--------------------------------------------------------------------------------
1 | Icon制作
2 | ---
3 |
4 |
5 | 子日:工欲善其事必先利其器
6 |
7 | [logoko在线制作](https://www.logoko.com.cn)
8 |
9 | [图标工厂,一键生成各种分辨率图片](http://icon.wuruihong.com/)
10 |
11 |
12 | ---
13 |
14 | - 邮箱 :charon.chui@gmail.com
15 | -
16 | Good Luck!
17 |
--------------------------------------------------------------------------------
/Tools&Library/MAT内存分析.md:
--------------------------------------------------------------------------------
1 | MAT内存分析
2 | ===
3 |
4 | `Eclipse Memory Analyzer(MAT)`是著名的跨平台集成开发环境`Eclipse Galileo`版本的33个组成项目中之一,它是一个功能丰富的`JAVA` 堆转储文件分析工具,可以帮助你发现内存漏洞和减少内存消耗。
5 |
6 | [内存泄露介绍][1]
7 | [MAT(Memory Analyzer)官网]{http://www.eclipse.org/mat/}
8 | - 安装:
9 | - 单机版,解压后直接使用
10 | - `Eclipse`插件,直接装一个插件,然后`open perspective`打开 `Memory Analysis`
11 |
12 | - 使用
13 | - DDMS
14 | 进入`DDMS`页面,选择要分析的进程,然后点击`Update Heap`按钮。然后在右侧`Heap`页面点击一下`Cause GC`按钮,点击`Cause GC`按钮就是手动触发`Java`垃圾回收。
15 | 
16 | 如果想要看某个`Activity`是否发生内存泄露,可以反复多次进入和退出该页面, 然后点击几次`Cause GC`触发垃圾回收,
17 | 看一下上图中`data object`这一栏中的`Total Size`的大小是保持稳定还是有明显的变大趋势,如果有明显的变大趋势就说明这个页面存在内存泄露的问题,需要进行具体分析。
18 |
19 |
20 | 很长时间之前学习的,一直想记录出来,总是忙,到现在才开始整理,但是现在已经很少用`MAT`了,因为它太费劲了。
21 | 自从良心企业发布了`leakCanary`后,都已经转投到大神门下。
22 |
23 |
24 | [1]: https://github.com/CharonChui/AndroidNote/blob/master/BasicKnowledge/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F.md "内存泄露介绍"
25 |
26 | ---
27 |
28 | - 邮箱 :charon.chui@gmail.com
29 | - Good Luck!
30 |
--------------------------------------------------------------------------------
/Tools&Library/Markdown学习手册.md:
--------------------------------------------------------------------------------
1 | Markdown学习手册
2 | ===
3 |
4 | #### 一. 简单功能
5 | 功能 | 效果 | Markdown代码 | 备注
6 | ---|---|---|---
7 | 粗体 | **粗体** | `**粗体**` | 两边加**
8 | 斜体 | _斜体_ | `_斜体_` | 两边加_
9 | 中划线 | ~~中划线~~ | `~~中划线~~` | 两边加~~
10 | 单行代码 | `Log.i("Hello World!")` | \`Log.i("Hello World!")\` | 两边加`
11 | 插入图片 | | `` | [] 中间为占位符,() 中间为图片链接
12 | 链接 | [Visit Github](http://www.github.com) | `[Visit Github](http://www.github.com)` | [] 中间为显示文字,() 中间为链接
13 |
14 |
15 | #### 二. 其他功能
16 | ##### 1. 换行
17 | 在需要换行的地方敲击两次空格和一个回车键即可,如:
18 |
19 | ```
20 | 这是第一行(空格)(空格)(回车)
21 | 这是第二行
22 | ```
23 |
24 | Tip: 多条新行都会被视为一行
25 | ##### 2. 标题
26 | Markdown 提供了六种规格的标题,分别对应 Html 标签中的``-``,通过添加不同数量的`#`字符可以实现不同大小的标题,如:
27 | ```
28 | # 最大的标题(相当于一个标签)
29 | ## 次大的标题(相当于一个标签)
30 | ...
31 | ###### 最小的标题(相当于一个标签)
32 | ```
33 | Tip: \# 越多,标题越小
34 |
35 | 或者利用 = (最高阶标题)和 - (第二阶标题),任何数量的 = 和 - 都可以有效果。例如:
36 | ```
37 | H1
38 | ===
39 |
40 | H2
41 | ---
42 | ```
43 | 效果为:
44 | H1
45 | ===
46 |
47 | H2
48 | ---
49 |
50 | ##### 3. 列表
51 | ###### 3.1 有序列表
52 | 数字 + . + 空格即可,以下代码:
53 | ```
54 | 1. 项目1
55 | 2. 项目2
56 | 3. 项目3
57 | ```
58 | 效果为:
59 | 1. 项目1
60 | 2. 项目2
61 | 3. 项目3
62 |
63 | ###### 3.2 无序列表
64 | 以 * 和空格开头即可,以下代码:
65 | ```
66 | * 项目1
67 | * 项目2
68 | * 项目3
69 | ```
70 | 效果为:
71 | * 项目1
72 | * 项目2
73 | * 项目3
74 |
75 | ###### 3.3 子项目表示:
76 | 子项目缩进一个 tab 并加 * 和 空格表示,以下代码:
77 | ```
78 | * 项目1
79 | * 子项目1
80 | * 子项目2
81 | * 项目2
82 | * 子项目1
83 | ```
84 | 效果为:
85 | * 项目1
86 | * 子项目1
87 | * 子项目2
88 | * 项目2
89 | * 子项目1
90 |
91 | ##### 4. 代码块
92 | 以 \`\`\` 和 \`\`\` 包含,以下代码:
93 |
94 | ```
95 | def hello():
96 | print 'Hello World!'
97 | ```
98 |
99 | 效果为:
100 | ```
101 | def hello():
102 | print 'Hello World!'
103 | ```
104 |
105 | GFM 扩展了 Markdown 的代码块功能,通过在上面第一个 \`\`\` 后添加语言名称,使不同语言展现不同的代码高亮风格,以下代码:
106 |
107 | ```java
108 | public static void main(String[] args){
109 | System.out.println("Hello World!");
110 | }
111 | ```
112 |
113 | 效果为:
114 | ```java
115 | public static void main(String[] args){
116 | System.out.println("Hello World!");
117 | }
118 | ```
119 |
120 | 以下代码:
121 |
122 | ```python
123 | def hello():
124 | print 'Hello World!'
125 | ```
126 |
127 |
128 | 效果为:
129 | ```python
130 | def hello():
131 | print 'Hello World!'
132 | ```
133 |
134 | ##### 5. 表格
135 | 标准的 Markdown 中并不支持表格,但 GFM 可以,表头前空一行,以 `---` 做为表头分隔,以 `|` 做为列分隔,以下代码:
136 | ```
137 | 表头1|表头2
138 | ---|---
139 | 单元格1|单元格2
140 | 单元格3|单元格4
141 | ```
142 | 效果为:
143 |
144 | 表头1|表头2
145 | ---|---
146 | 单元格1|单元格2
147 | 单元格3|单元格4
148 |
149 | ##### 6. 特殊字符转义
150 | 如果需要在正文里使用特殊字符的话,可以用 `\` 来转义
151 |
152 |
153 | ---
154 |
155 | - 邮箱 :charon.chui@gmail.com
156 | - Good Luck!
--------------------------------------------------------------------------------
/Tools&Library/目前流行的开发组合.md:
--------------------------------------------------------------------------------
1 | 目前流行的开发组合
2 | ===
3 |
4 |
5 | 目前主流的一套框架就是`Retrofit + RxJava + RxBinding + Dagger2`
6 |
7 | 架构上面目前是`MVP`居多。
8 |
9 |
10 |
11 | ---
12 |
13 | - 邮箱 :charon.chui@gmail.com
14 | - Good Luck!
--------------------------------------------------------------------------------
/VideoDevelopment/Android音视频开发/11.播放组件封装.md:
--------------------------------------------------------------------------------
1 | 11.播放组件封装
2 | ===
3 |
4 | 通常播放器的开发都设计成一定程度的分层,将视频帧的显示、进度条、控制键、音量调节、预览图、字幕、弹幕、频道列表、后续播放推荐等截面功能与音视频播放进行剥离,以使代码模块化,架构清晰。
5 |
6 | 为连接播放器截面和音视频播放,通常需设计一套状态机机制,音视频播放层需要负责包括解码器在内的软硬件初始化,搭建Pipeline以及进行播放控制。
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | ---
43 |
44 | - 邮箱 :charon.chui@gmail.com
45 | - Good Luck!
--------------------------------------------------------------------------------
/VideoDevelopment/Android音视频开发/2.系统播放器MediaPlayer.md:
--------------------------------------------------------------------------------
1 | 2.系统播放器MediaPlayer
2 | ===
3 |
4 |
5 |
6 | 
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 相关知识:
22 |
23 | - [封装格式](https://en.wikipedia.org/wiki/Comparison_of_video_container_formats)
24 | - [视频编码方式](https://en.wikipedia.org/wiki/Comparison_of_video_codecs)
25 | - [音频编码方式](https://en.wikipedia.org/wiki/Comparison_of_audio_coding_formats)
26 |
27 | ---
28 |
29 | - 邮箱 :charon.chui@gmail.com
30 | - Good Luck!
--------------------------------------------------------------------------------
/VideoDevelopment/Android音视频开发/AudioTrack简介.md:
--------------------------------------------------------------------------------
1 | AudioTrack简介
2 | ---
3 |
4 | Android系统提供了三种播放音频文件的方式:
5 |
6 | - SoundPoll:适合播放短促且对反应速度要求比较高的情况(如游戏音效、按键声等)
7 | - AudioTrack:只支持播放解码后的PCM流,如果是文件的话只支持WAV格式的音频文件,因为WAV格式的音频文件大部分都是PCM流,AudioTrack不创建解码器,所以只能播放不需要解码的WAV文件,但是CPU占用率低,内存消耗也比较小。因此如果是播放比较短时间的WAV音频文件,建议使用AudioTrack。
8 | - MediaPlayer:适合比较长且时间要求不那么高的情况,支持多种文件格式,如MP3、WAV、AAC等。其实MediaPlayer是基于AudioTrack的封装,内部也是使用AudioTrack,MediaPlayer在framework层也实例化了AudioTrack,MediaPlayer在framework层进行解码后,生成PCM流,然后代理委托给AudioTrack,最后AudioTrack传递给AudioFlinger进行混音,然后才传递给硬件播放。
9 |
10 |
11 | AudioTrack播放声音时不能直接把WAV文件传递给AudioTrack进行播放,必须传递buffer,通过write函数把需要播放的缓冲区buffer传递给AudioTrack,然后才能播放。
12 |
13 |
14 | 上面一直说PCM,那PCM究竟是啥?
15 |
16 | 简单的说,PCM是一种数据编码格式,CD唱片上刻录的就是直接用PCM格式编码的数据文件,WAV是一种声音文件格式,WAV里面包含的声音数据可以是采用PCM格式编码的声音数据,也可以是采用其他格式编码的声音数据,但是目前一般采用PCM编码的声音数据两者之间的区别就是:PCM是一个通信上的概念,脉冲编码调制。WAV是媒体概念,体现的是封装。WAV文件可以封装PCM编码信息,也可以封装其他编码格式,例如MP3等。
17 |
18 |
19 |
20 | AudioTrack类可以完成Android平台上音频数据的输出任务。AudioTrack有两种数据加载模式:
21 |
22 | - MODE_STREAM:通过write一次次把音频数据写到AudioTrack中。和平时通过write系统调用往文件中写数据类似,但是这种工作方式每次都需要把数据从用户提供的Buffer中拷贝到AudioTrack中的Buffer,这样会在一定程度上引入延迟,为了解决这个问题,AudioTrack引入了第二种方式。
23 | - MODE_STATIC:这种模式下,在play之前只需要把所有数据通过一次write调用传递到AudioTrack中的内部缓冲区,后续就不必再传递数据。这种模式适用于铃声这种内存占用小,延时要求高的文件,但是它也有一个缺点,就是一次write的数据不能太多,否则系统无法分配足够的内存来存储全部数据。
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/VideoDevelopment/Android音视频开发/CameraX结合OpenGL.md:
--------------------------------------------------------------------------------
1 | CameraX结合OpenGL
2 | ===
3 |
4 |
5 | CameraX 是一个 Jetpack 库,旨在帮助您更轻松地开发相机应用。如果您要开发新应用,我们建议您从 CameraX 开始。它提供了一个一致且易于使用的 API,该 API 适用于绝大多数 Android 设备,并向后兼容 Android 5.0(API 级别 21)。
6 |
7 |
8 | CameraX 着重于用例,使您可以专注于需要完成的任务,而无需花时间处理不同设备之间的细微差别。CameraX 支持大多数常见的相机用例:
9 |
10 | 预览:在屏幕上查看图片。接受用于显示预览的 Surface,例如 PreviewView。
11 | 图片分析:无缝访问缓冲区中的图片以便在算法中使用,例如将其传递到机器学习套件。为分析(例如机器学习)提供 CPU 可访问的缓冲区。
12 | 图片拍摄:保存图片。拍摄并保存照片。
13 | 视频拍摄:保存视频和音频。通过 VideoCapture 拍摄视频和音频
14 |
15 |
16 |
17 | 版本 1.4.0
18 | 2024 年 10 月 30 日
19 |
20 | 发布了 androidx.camera:camera-*:1.4.0。版本 1.4.0 包含这些提交内容。
21 |
22 | 自 1.3.0 以来的重要变更
23 |
24 | CameraX 1.4.0 包含众多精彩更新!下面是摘要:
25 |
26 | 主打功能:10 位 HDR:
27 |
28 | 轻松拍摄出令人惊艳的 HDR 照片和视频。
29 | 支持 HLG 和 10 位 HEVC 编码。
30 | 享受 10 位 HDR 预览,并查询设备功能。
31 | 可在越来越多的设备上与 UltraHDR 图片和 HDR 视频搭配使用。
32 | 其他酷炫功能:
33 |
34 | Kotlin 扩展:添加了 takePicture 和 awaitInstance 挂起函数。
35 | 实时特效:应用水印和对象突出显示等特效。
36 | CameraController API:新增了视频拍摄配置控件。
37 | 预览防抖:查询设备功能并启用防抖。
38 | 增强了 VideoCapture 功能:可更精细地控制画质,并支持更高分辨率。
39 | CameraX 扩展程序集成:与 VideoCapture 和新的 ImageCapture 功能无缝集成。
40 | Shutter Sound API:轻松查看各个地区的快门提示音要求。
41 | 屏幕闪光灯:改进了前置摄像头在弱光环境下的拍照效果。
42 | Camera Extensions Metadata API:支持在 ExtensionMode#AUTO 中调整扩展程序强度和获取当前扩展程序模式通知的 API。如需了解更多 bug 修复,请参阅我们的Beta 版和RC 版公告。
43 |
44 |
45 |
46 |
47 | 同时选择多个摄像头
48 | 从 CameraX 1.3 开始,您还可以同时选择多个摄像头。 例如,您可以对前置和后置摄像头进行绑定,以便从两个视角同时拍摄照片或录制视频。
49 |
50 | 使用并发摄像头功能时,设备可以同时运行两个不同镜头方向的摄像头,或同时运行两个后置摄像头。以下代码块展示了如何在调用 bindToLifecycle 时设置两个摄像头,以及如何从返回的 ConcurrentCamera 对象中获取两个 Camera 对象。
51 |
52 | Kotlin
53 | Java
54 |
55 | // Build ConcurrentCameraConfig
56 | val primary = ConcurrentCamera.SingleCameraConfig(
57 | primaryCameraSelector,
58 | useCaseGroup,
59 | lifecycleOwner
60 | )
61 |
62 | val secondary = ConcurrentCamera.SingleCameraConfig(
63 | secondaryCameraSelector,
64 | useCaseGroup,
65 | lifecycleOwner
66 | )
67 |
68 | val concurrentCamera = cameraProvider.bindToLifecycle(
69 | listOf(primary, secondary)
70 | )
71 |
72 | val primaryCamera = concurrentCamera.cameras[0]
73 | val secondaryCamera = concurrentCamera.cameras[1]
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | ---
82 |
83 | - 邮箱 :charon.chui@gmail.com
84 | - Good Luck!
--------------------------------------------------------------------------------
/VideoDevelopment/Android音视频开发/MediaExtractor、MediaCodec、MediaMuxer.md:
--------------------------------------------------------------------------------
1 | # MediaExtractor、MediaCodec、MediaMuxer
2 |
3 |
4 | ## MediaExtractor
5 |
6 | MediaExtractor是一个Android系统用于从多媒体文件中提取音频和视频数据的类。
7 |
8 | 它可以从本地文件或网络流中读取音频和视频数据,并将其解码为原始的音频和视频帧。它的主要功能就是解封装,也就是从媒体文件中提取出原始的音频和视频数据流。这些数据流可以被送入解码器进行解码,然后进行播放或者其他处理。
9 |
10 | MediaExtractor可以用于开发音视频播放器、视频编辑器、音频处理器等应用程序。
11 |
12 |
13 | ## MediaMuxter
14 |
15 | MediaMuxter是Android系统提供的一个用于混合音频和视频数据的API。
16 |
17 | 它可以将音频和视频的原始数据流混合封装成媒体文件,例如MP4、WebM等。
18 |
19 | MediaMuxter通常与MediaExtractor一起使用,MediaExtractor用于从媒体文件中提取音频和视频数据,MediaMuxter用于将这些数据混合成新的媒体文件。
20 |
21 | 简单说就是: MediaExtractor提供了解封装的能力,而MediaMuxer提供了视频封装的能力。
22 |
23 |
24 | ## MediaCodec
25 |
26 | MediaCodec是Android提供的用于对音视频进行编码的类,是Android Media基础框架的一部分,一般和MediaExtractor、MediaMuxer、Surface和AudioTrack一起使用。
27 |
28 |
29 | MediaCodec可以处理原始的音视频数据,包括编码(将原始数据转换为压缩格式)和解码(将压缩格式转换为原始数据)。
30 |
31 |
32 |
33 | MediaExtractor仅仅是解封装数据,不会对数据进行解码。要对媒体数据进行解码,需要使用MediaCodec类。
34 |
35 | 而且MediaExtractor只能解封装媒体文件中的音视频等媒体轨道,而不能解析整个媒体文件的结构。如果需要解析整个媒体文件的结构,需要使用其他库或框架。
36 |
37 |
38 |
--------------------------------------------------------------------------------
/VideoDevelopment/Android音视频开发/MediaMetadataRetriever.md:
--------------------------------------------------------------------------------
1 | # MediaMetadataRetriever
2 |
3 | MediaMetadataRetriever是Android中用于从媒体文件中提取元数据的类,可以获取音频、视频和图片文件的各种信息,例如时长、标题、封面等。
4 |
5 | ```java
6 | MediaMetadataRetriever mRetriever = new MediaMetadataRetriever();
7 |
8 | mRetriever.setDataSource(mContext, mVideoUri);
9 | ```
10 |
11 | ## 获取元数据
12 |
13 | // 获取歌曲标题
14 | `mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);`
15 |
16 | 根据key的不同还可以是时长、帧率、分辨率等。
17 |
18 | ## 获取缩略图
19 |
20 | `public Bitmap getFrameAtTime(long timeUs, int option)`
21 | 用于获取音视频文件中的一帧画面,可以用于实现缩略图、视频预览等功能。
22 |
23 | - timeUs:是获取画面对应的时间戳,单位为微妙
24 | - option:可以设置是获取离指定时间戳最近的一帧画面或者事最近的关键帧画面等。
25 |
26 | ## 获取专辑封面图
27 |
28 | ```java
29 | byte[] bytes = mRetriever.getEmbeddedPicture();
30 | Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
31 | ```
32 | 需要注意的是,MediaMetadataRetriever只能用于读取已经完成的音视频文件,无法用于实时处理音视频数据流。
33 |
34 |
--------------------------------------------------------------------------------
/VideoDevelopment/Android音视频开发/视频解码之软解与硬解.md:
--------------------------------------------------------------------------------
1 | 视频解码之软解与硬解
2 | ===
3 |
4 | **硬解**:从字面意思上理解就是用硬件来进行解码,通过显卡的视频加速功能对高清视频进行解码,很明显就是一个专门的电路板(这样好理解...)来进行视频的解码,是依靠显卡`GPU`的。
5 |
6 | **软解**:字面上理解就是用软件进行解码,这样理解也对,但是实际最总还是要硬件来支持的,这个硬件就是`CPU`。
7 |
8 | 既然有这两种不同的解码方式,我们在开发中该如何进行选择?哪个更好?
9 |
10 | - 硬解优缺点:
11 | 显卡核心GPU拥有独特的计算方法,解码效率非常高,而且充当解码核心的模块成本并不高。这样不但能够减轻CPU的负担,还有着低功耗、发热少等特点。但是由于硬解码起步比较晚,
12 | 软件和驱动对其的支持度低。硬解码内置有什么样的模块就能够解码什么样的视频,面对网络上杂乱无章的视频编码格式,不可能做到完全兼容同。此外,硬解码的滤镜、字幕、画质增强方面都做的十分不足。
13 |
14 | 优点:低功耗、发热少、效率高。
15 | 缺点:视频兼容性差、支持度低。
16 |
17 | - 软解优缺点:
18 | 软解码技术的解码过程中,需要对大量的视频信息进行运算,对CPU性能的要求非常高。尤其是对高清晰度大码率的视频来说,巨大的运算量就会造成转换效率低、发热量大等问题。
19 | 但是由于软解码的过程中不需要复杂的硬件支持,兼容性非常高。即使是新出的视频编码格式,只要安装好相应的解码器文件,就能顺利播放。而且软解码拥有丰富的滤镜、字幕、画面处理优化等效果,
20 | 如果`CPU`足够强悍的话,能够实现更加出色的画面效果。
21 |
22 | 优点: 兼容强、全解码、效果好
23 | 缺点: 对`CPU`要求高、效率低、发热大
24 |
25 |
26 | 关于软解与硬解究竟哪个更好的问题一直是争论的热点,其实我倒是感觉没有好坏之分,各自有各自的优缺点和使用条件,根据需要去选择才是最合适的。
27 | 播放码率比较大的视频,硬解可能流畅的播放,但是软解可能会出现演示、画面和声音卡顿不同步的问题。但是硬解播放出的视频大多都不允许在解码之后进行软件后处理,比如进行一些降噪锐化之类的后期滤镜,
28 | 这样可能会让人感觉画质不太好的。当然上面的这种情况也是和`CPU`及`GPU`能力的不同而不同的。 总的来说,还是各有春秋,适合你的才是最好的。
29 |
30 |
31 | ---
32 |
33 | - 邮箱 :charon.chui@gmail.com
34 | - Good Luck!
35 |
--------------------------------------------------------------------------------
/VideoDevelopment/Android音视频开发/音视频同步原理.md:
--------------------------------------------------------------------------------
1 | 音视频同步原理
2 | ===
3 |
4 |
5 | 为什么需要音视频同步?
6 |
7 | 媒体数据经过解复用流程后,音频/视频解码便是独立的,也是独立播放的。而在音频流和视频流中,其播放速度都是有相关信息指定的:
8 | - 视频:帧率,表示视频一秒显示的帧数。
9 | - 音频:采样率,表示音频一秒播放的样本的个数。
10 |
11 | 从帧率及采样率,即可知道视频/音频播放速度。声卡和显卡均是以一帧数据来作为播放单位,如果单纯依赖帧率及采样率来进行播放,在理想条件下,应该是同步的,不会出现偏差。
12 |
13 | 以一个44.1KHz的AAC音频流和24FPS的视频流为例:
14 |
15 | 一个AAC音频frame每个声道包含1024个采样点,则一个frame的播放时长(duration)为:(1024/44100)×1000ms = 23.22ms;一个视频frame播放时长(duration)为:1000ms/24 = 41.67ms。理想情况下,音视频完全同步。
16 | 但实际情况下,如果用上面那种简单的方式,慢慢的就会出现音视频不同步的情况,要不是视频播放快了,要么是音频播放快了。可能的原因如下:
17 |
18 | 一帧的播放时间,难以精准控制。音视频解码及渲染的耗时不同,可能造成每一帧输出有一点细微差距,长久累计,不同步便越来越明显。(例如受限于性能,42ms才能输出一帧)
19 |
20 | 音频输出是线性的,而视频输出可能是非线性,从而导致有偏差。
21 |
22 | 媒体流本身音视频有差距。(特别是TS实时流,音视频能播放的第一个帧起点不同)
23 |
24 | 所以,解决音视频同步问题,引入了时间戳:
25 |
26 | 首先选择一个参考时钟(要求参考时钟上的时间是线性递增的);
27 |
28 | 编码时依据参考时钟上的给每个音视频数据块都打上时间戳;
29 |
30 | 播放时,根据音视频时间戳及参考时钟,来调整播放。
31 |
32 | 所以,视频和音频的同步实际上是一个动态的过程,同步是暂时的,不同步则是常态。以参考时钟为标准,放快了就减慢播放速度;播放快了就加快播放的速度。
33 |
34 | ### DTS、PTS
35 |
36 | - DTS(Decoding Time Stamp):即解码时间戳,这个时间戳的意义在于告诉播放器该在什么时候解码这一帧的数据。
37 |
38 | - PTS(Presentation Time Stamp):即显示时间戳,这个时间戳用来告诉播放器该在什么时候显示这一帧的数据。
39 |
40 | 当视频流中没有 B 帧时,通常 DTS 和 PTS 的顺序是一致的。但如果有 B 帧时,就回到了我们前面说的问题:解码顺序和播放顺序不一致了,即视频输出是非线性的。
41 |
42 | 比如一个视频中,帧的显示顺序是:I B B P,因为B帧解码需要依赖P帧,因此这几帧在视频流中的顺序可能是:I P B B,这时候就体现出每帧都有 DTS 和 PTS 的作用了。DTS 告诉我们该按什么顺序解码这几帧图像,PTS 告诉我们该按什么顺序显示这几帧图像。流中P帧在B帧之前,但显示确实在B帧之后。
43 |
44 |
45 | 实现音视频同步,在播放时,需要选定一个参考时钟,读取帧上的时间戳,同时根据的参考时钟来动态调节播放。现在已经知道时间戳就是PTS,那么参考时钟的选择一般来说有以下三种:
46 |
47 | - 将视频同步到音频上:就是以音频的播放速度为基准来同步视频。
48 | - 将音频同步到视频上:就是以视频的播放速度为基准来同步音频。
49 | - 将视频和音频同步外部的时钟上:选择一个外部时钟为基准,视频和音频的播放速度都以该时钟为标准。
50 |
51 | 当播放源比参考时钟慢,则加快其播放速度,或者丢弃;快了,则延迟播放。
52 | 这三种是最基本的策略,考虑到人对声音的敏感度要强于视频,频繁调节音频会带来较差的观感体验,且音频的播放时钟为线性增长,所以一般会以音频时钟为参考时钟,视频同步到音频上。
53 |
54 | 调整策略可以尽量采用渐进的方式,因为音视频同步是一个动态调节的过程,一次调整让音视频PTS完全一致,没有必要,且可能导致播放异常较为明显。
55 |
56 | 调整策略仅仅对早到的或晚到的数据块进行延迟或加快处理,有时候是不够的。如果想要更加主动并且有效地调节播放性能,需要引入一个反馈机制,也就是要将当前数据流速度太快或太慢的状态反馈给“源”,让源去放慢或加快数据流的速度。
57 |
58 |
59 |
60 | ---
61 |
62 | - 邮箱 :charon.chui@gmail.com
63 | - Good Luck!
--------------------------------------------------------------------------------
/VideoDevelopment/Android音视频开发/音视频场景.md:
--------------------------------------------------------------------------------
1 | 音视频场景
2 | ===
3 |
4 | #### 视频点播
5 |
6 | 视频点播(Video On Demand, VOD)是个人用户最常用的功能之一,也是许多媒体平台的支柱业务,如国外的Netflix、Hulu和国内的爱奇艺、优酷、腾讯视频、B站等。视频点播的核心在于将媒体传输内容的选择权交给用户,将用户选择的音视频媒体内容通过网络传输到用户的播放器进行播放。视频点播的内容可能来源于专业生产内容(PGC)、用户生产内容(UGC)或直播回放内容等,在通过云端服务器转码处理后保存。当用户向平台网站请求某个音视频节目时,媒体流信息通过内容分发网络(CDN)加速后发送至用户的播放客户端。
7 | 一个典型的视频点播系统结构如下:
8 | 
9 |
10 |
11 | #### 视频直播
12 |
13 | 视频直播产生的历史实际上比视频点播更加久远,在早期的有线电视中,几乎所有的节目都只能通过直播的方式呈现给观众。随着网络流媒体的兴起,各种直播平台经历了如“千播大战”的爆发式增长,最后兼并整合为几大巨头平台,如国内的斗鱼、虎牙和B站,以及国外的Twich等。视频直播的整体结构与视频点播有一定的相似性,如都依赖音视频转码服务和内容分发网络进行数据的标准化和加速传输等,它们之间最主要的区别在于,视频直播的内容来自主播端通过采集端实时获取的数据,而视频点播的内容来自内容发布方预先制作的节目内容。
14 | 一个典型的视频直播系统结构如下:
15 | 
16 |
17 |
18 | #### 安防监控
19 |
20 | 安防监控是音视频领域的重要应用场景,也是最具商业价值的业务之一。在一个典型的安防监控系统中,通过监控摄像机采集的视频流信息会经由网络视频录像服务器进行录制存储或转发。客户端通过管理服务器控制媒体流转发或录制的逻辑,并可以请求某一路实时流或录像文件的播放。
21 | 一个典型的安防监控系统结构如下:
22 | 
23 |
24 | #### 视频会议
25 |
26 | 视频会议是近年来蓬勃发展的新兴领域之一。2020年,在许多行业均因新冠肺炎疫情遭到重创的情况下,视频会议逆流而上,创造了自诞生以来最为迅速的增长。许多基于公网的视频会议系统都以WebRTC为基础,以尽可能低的延迟提供高质量的音频和视频实时通信服务。
27 | 一个典型的视频会议系统结构如下:
28 | 
29 |
30 |
31 |
32 | ### 常用分辨率
33 | - 4CIF/SD:
34 | 图像分辨率为720像素×576像素,也称为标准清晰度(Standard Definition,SD)视频,常用于标清数字电视广播和数字视盘(DVD)。
35 | - HD/720P:
36 | 图像分辨率为1280像素×720像素,也称为高清晰度(High Definition,HD)视频,常用于高清晰度数字电视广播和蓝光数字视盘(蓝光DVD)。
37 | - FHD/1080P:
38 | 图像分辨率为1920像素×1080像素,也称为全高清晰度(Full High Definition,FHD)视频,与HD视频一样,也常用于高清晰度数字电视广播和蓝光DVD视盘。
39 | - UHD:
40 | 分辨率比FHD视频更高的视频格式,也称为超高清(Ultra High Definition,UHD)视频。常用格式有4K和8K等,分辨率分别为3840像素×2160像素(4K)和7680像素×4320像素(8K),常用于超高清数字电视和高端数字娱乐系统。
41 |
42 | ### 视频压缩编码的基本原理
43 |
44 | #### 视频数据中的冗余信息
45 | 像素格式的视频数据之所以能被压缩,其根本原因在于视频中存在冗余信息。我们可以通过多种不同的算法去除冗余信息,从而对数据进行压缩。视频数据中的冗余信息主要有:
46 | - 时间冗余:视频中相邻两帧之间的内容相似,存在运动关系。
47 | - 空间冗余:视频中某一帧内部的相邻像素存在相似性。
48 | - 编码冗余:视频中不同数据出现的概率不同。
49 | - 视觉冗余:观众的视觉系统对视频中的不同部分敏感度不同。
50 |
51 | 针对不同类型的冗余信息,在各视频编码的标准算法中都有专门的技术应对,以通过不同的角度提高压缩比率。
52 |
53 | #### 预测编码
54 |
55 |
56 | 预测编码是数据压缩中最常用的方法之一,例如,在脉冲编码调制(Differential Pulse Code Modulation,DPCM)中,就是用当前采样值与预测采样值的差进行编码的,即通过这种方式减少输出数据的体积。在视频压缩中,预测编码作为最核心的算法之一起到了重要作用。在视频编码中,预测编码主要有两种方法:
57 | - 帧内预测:帧内预测是根据当前帧已编码的数据进行预测,利用图像内相邻像素之间的相关性去除视频中的空间冗余。
58 | - 帧间预测:帧间预测是将部分已编码的图像作为参考帧,利用前后帧之间的时间相关性去除视频中的时间冗余。
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | ---
96 |
97 | - 邮箱 :charon.chui@gmail.com
98 | - Good Luck!
--------------------------------------------------------------------------------
/VideoDevelopment/Danmaku/Android弹幕实现.md:
--------------------------------------------------------------------------------
1 | 弹幕
2 |
3 | ---
4 |
5 |
6 |
7 | [OpenDanmaku](https://github.com/linsea/OpenDanmaku)
8 |
9 | [bilibili Android开源弹幕引擎.烈焰弹幕使](https://github.com/Bilibili/DanmakuFlameMaster)
10 |
11 |
12 |
13 | 
14 |
15 | ### 弹幕目前使用场景
16 |
17 |
18 |
19 | 1. 直播:通过直播间im可以实时的去显示用户新发的弹幕
20 | 2. 短视频:目前没有im,可以通过单独的接口来请求弹幕,实时性有缺陷
21 |
22 |
23 |
24 | ### 播放器上弹幕实现原理
25 |
26 | 布局上分为: 最下层的播放器视图、中间层的弹幕视图层、最上层的操作视图层。
27 |
28 | - 自定义View显示弹幕:VideoView上面盖上一层全透明的DanmakuView,该DanmakuView去不断的渲染显示弹幕。可能会有卡顿。
29 |
30 | - SurfaceView显示弹幕:内存及耗电量低、绘制及时,但是不支持动画和截图。
31 |
32 | 打个比方可以是两个SurfaceView,下面的一个SurfaceView显示视频画面,上面的一层透明SurfaceView用于绘制弹幕。
33 |
34 | - TextureView显示弹幕: 内存和耗电稍高、绘制会有1-3帧的延迟、但是支持动画和截图。
35 |
36 | 一般的Activity包含的多个View会组成View hierachy的树形结构,只有最顶层的DectorView才是对WMS可见的,这个DecorView在WMS中有一个对应的WindowState,再SurfaceFlinger中有对应的Layer,而SurfaceView正因为它有自己的Surface,有自己的Window,它在WMS中有对应的WindowState,在SurfaceFlinger中有Layer。虽然在App端它仍在View hierachy中,但在Server端(WMS和SurfaceFlinger)中,它与宿主窗口是分离的。这样的好处是对这个Surface的渲染可以放到单独的线程中去做,渲染时可以有自己的GL context。因为它不会影响主线程对时间的响应。所以它的优点就是可以在独立的线程中绘制,不影响主线程,而且使用双缓冲机制,播放视频时画面更顺畅。但是这也有缺点,因为这个Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移、缩放等动画,它也不能放在其它ViewGroup中,SurfaceView不能嵌套使用,而且不能使用某些View的特性,例如View.setAlpha()。
37 |
38 | ***从Android7.0开始,SurfaceView的窗口位置与其他View渲染同步更新。 这意味着在屏幕上平移和缩放SurfaceView不会导致渲染失真。***
39 |
40 |
41 |
42 | ### 需要考虑的问题
43 |
44 |
45 |
46 | 1. 弹幕的大小及位置是否可以用户随意调整
47 |
48 | 2. 弹幕内移动的item出现的角度是否可按类型不同控制
49 |
50 | 3. 弹幕能否支持图文以及标签
51 |
52 | 4. 弹幕内移动的item出现的位置是否可随机
53 |
54 | 5. 弹幕有没有交互功能,例如点击弹幕中的头像进入用户页面
55 |
56 | 6. 弹幕过于密集集中是的展示策略(是否舍弃)
57 |
58 | 7. 小屏和全屏切换后的展示策略(展示内容的空间大小不同)
59 |
60 | 8. 性能的影响
61 |
62 | 9. 弹幕太多挡住视频中的人物画面
63 |
64 | B站移动端的视频能够明显的看到人物边缘的锯齿.
65 |
66 | 
67 |
68 | 对观看还是有一定的体验影响,感觉可以从上面第四条的角度去出发,对于密集的弹幕做一个展示策略,例如短时间内大量的重复或者相近的弹幕可以舍弃(只有发弹幕的本人可以看到)或者是合并。
69 |
70 | - 抠图:任务在弹幕渲染的层级之上渲染。
71 | - 视频预处理:服务端做离线的视频人脸监测,生成蒙层,,弹幕遇到人脸的位置就消失,除了人脸就显示。
72 |
73 | ### 实现思考
74 |
75 |
76 |
77 | #### 自定义View
78 |
79 |
80 |
81 | #### 自定义SurfaceView
82 |
83 |
84 |
85 | #### 自定义TextureView
86 |
87 | #### OpenGL实现
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/VideoDevelopment/ExoPlayer/4. ExoPlayer源码分析之prepare序列图.md:
--------------------------------------------------------------------------------
1 | 4.ExoPlayer源码分析之prepare序列图
2 | ---
3 |
4 |
5 | 上面源码分析完了后,发现一脸懵逼,只是简单的知道了怎么调用的,都是啥,但是并不知道每个类具体是干啥的,以及他们之间的关联关系
6 |
7 |
8 | ```sequence
9 | SimpleExoPlayer->ExoPlayerImpl: player.prepare(mediaSource)
10 | ExoPlayerImpl->ExoPlayerImplInternal: internalPlayer.prepare(mediaSource)
11 | ExoPlayerImplInternal->ExoPlayerImplInternal: MSG_PREPARE
12 | ExoPlayerImplInternal->ExoPlayerImplInternal: prepareInternal
13 | ExoPlayerImplInternal->MediaSource: mediaSource.prepareSource()
14 | ExoPlayerImplInternal->ExoPlayerImplInternal: DO_SOME_WORK
15 | ExoPlayerImplInternal->MediaPeriodQueue: doSomeWork()
16 | ExoPlayerImplInternal->Renderer: renderer.render()
17 | MediaPeriodQueue->MediaPeriodHolder: queue.getPlayingPeriod()
18 | MediaPeriodHolder->MediaPeriod: createMediaPeriod()
19 | ExoPlayerImplInternal->MediaPeriod: doSomeWork(updatePeriods())
20 | MediaPeriod->ExtractingLoadable: prepare()
21 | ExtractingLoadable->DataSource: load()
22 | ExtractingLoadable->Extracotr(Mp4Extractor): load()
23 | Extracotr(Mp4Extractor)->Extracotr(Mp4Extractor): readAtomHeader() processAtomEnded()processMoovAtom
24 | Extracotr(Mp4Extractor)->ProgressiveMediaPeriod: endTracks()
25 | ProgressiveMediaPeriod->ExoPlayerImplInternal: onPrepared(MediaPeriod source)
26 | ExoPlayerImplInternal->ExoPlayerImplInternal: handlePeriodPrepared
27 | ExoPlayerImplInternal->Renderer: enableRenderer
28 | Renderer->Renderer: if (isPlaying) {renderer.start()}
29 | ```
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | [上一篇: 3. ExoPlayer源码分析之prepare方法](https://github.com/CharonChui/AndroidNote/blob/master/VideoDevelopment/ExoPlayer/3.%20ExoPlayer%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8Bprepare%E6%96%B9%E6%B3%95.md)
39 | [下一篇: 5. ExoPlayer源码分析之PlayerView](https://github.com/CharonChui/AndroidNote/blob/master/VideoDevelopment/ExoPlayer/5.%20ExoPlayer%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8BPlayerView.md)
40 |
41 | ---
42 |
43 | - 邮箱 :charon.chui@gmail.com
44 | - Good Luck!
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/VideoDevelopment/FFmpeg/1.FFmpeg简介.md:
--------------------------------------------------------------------------------
1 | 1.FFmpeg简介
2 | ===
3 |
4 | [FFmpeg](https://ffmpeg.org/)是一个开源免费跨平台的视频和音频流方案,属于自由软件,采用LGPL或GPL许可证(依据你选择的组件)。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多codec都是从头开发的。
5 |
6 | FFmpeg框架的基本组成包含:
7 | - AVFormat
8 | AVFormat中实现了目前多媒体领域中的绝大多数媒体封装格式,包括封装和解封装,如MP4、FLV、KV、TS等文件封装格式,RTMP、RTSP、MMS、HLS等网络协议封装格式。FFmpeg是否支持某种媒体封装格式,取决于编译时是否包含了该格式的封装库。根据实际需求,可进行媒体封装格式的扩展,增加自己定制的封装格式,即在AVFormat中增加自己的封装处理模块。
9 | - AVCodec
10 | AVCodec中实现了目前多媒体领域绝大多数常用的编解码格式,既支持编码,也支持解码。AVCodec除了支持MPEG4、AAC、MJPEG等自带的媒体编解码格式之外,还支持第三方的编解码器,如H.264(AVC)编码,需要使用x264编码器;H.265(HEVC)编码,需要使用x265编码器;MP3(mp3lame)编码,需要使用libmp3lame编码器。如果希望增加自己的编码格式,或者硬件编解码,则需要在AVCodec中增加相应的编解码模块
11 | - AVFilter
12 | AVFilter库提供了一个通用的音频、视频、字幕等滤镜处理框架。在AVFilter中,滤镜框架可以有多个输入和多个输出。
13 | - AVDevice
14 | 读取电脑(或者其他设备上)的多媒体设备的数据 或者输出数据到指定的多媒体设备上;
15 | - AVUtil
16 | 包含一些公共的工具函数,包括随机数生成、数据结构、核心多媒体工具等;
17 | - swscale
18 | swscale模块提供了高级别的图像转换API,例如它允许进行图像缩放和像素格式转换,常见于将图像从1080p转换成720p或者480p等的缩放,或者将图像数据从YUV420P转换成YUYV,或者YUV转RGB等图像格式转换。
19 | - swresample
20 | 用于音频采样采样数据(PCM)转换的库,提供了高级别的音频重采样API。
21 |
22 |
23 |
24 | 主要的工具集:
25 |
26 | - ffmpeg:一个命令行工具,可用于格式转换、解码、编码等;
27 | - ffsever:一个 HTTP 、RTSP的实时广播流媒体服务器;
28 | - ffplay:是一个简单的播放器,使用ffmpeg 库解析和解码,通过SDL显示;
29 | - ffprobe : 一个多媒体流分析工具。 它从多媒体流中收集信息 并且以人类和机器可读的形式打印出来。
30 |
31 |
32 | ffmpeg的主要工作流程相对比较简单,具体如下:
33 | - 解封装(Demuxing)
34 | - 解码(Decoding)
35 | - 编码(Encoding)
36 | - 封装(Muxing)
37 |
38 | 其中需要经过6个步骤,具体如下:
39 | - 读取输入源
40 | - 进行音视频的解封装
41 | - 解码每一帧音视频数据
42 | - 编码每一帧音视频数据
43 | - 进行音视频的重新封装
44 | - 输出到目标
45 |
46 |
47 | ffmpeg整体处理的工作流程与步骤为:
48 | 读取文件 → 解封装 → 解码 → 转换参数 → 新编码 → 封装 → 写入文件
49 | ffmpeg首先读取输入源;
50 | 然后通过Demuxer将音视频包进行解封装,这个动作通过调用libavformat中的接口即可实现;
51 | 接下来通过Decoder进行解码,将音视频通过Decoder解包成为YVU或者PCM这样的数据,Decoder通过libavcodec中的接口即可实现;
52 | 然后通过Encoder将对应的数据进行编码,编码可以通过libavcodec中的接口来实现; 接下来将编码后的音视频数据包通过Muxer进行封装,Muxer封装通过libavformat中的接口即可实现,输出成为输出流。
53 |
54 |
55 |
56 |
57 | ---
58 |
59 | - 邮箱 :charon.chui@gmail.com
60 | - Good Luck!
61 |
--------------------------------------------------------------------------------
/VideoDevelopment/FFmpeg/3.FFmpeg切片.md:
--------------------------------------------------------------------------------
1 | 3.FFmpeg切片
2 | ===
3 |
4 |
5 |
6 |
7 |
8 |
9 | ---
10 |
11 | - 邮箱 :charon.chui@gmail.com
12 | - Good Luck!
--------------------------------------------------------------------------------
/VideoDevelopment/FFmpeg/6.视频播放简介.md:
--------------------------------------------------------------------------------
1 | ## 视频播放简介
2 |
3 |
4 | 解封装是指将输入的封装格式的数据,分离成为音频流压缩编码数据和视频流压缩编码数据。封装格式种类有很多,例如MP4、MKV、RMVB、TS、FLV、AVI等,其作用就是将已经压缩编码的视频数据和音频数据按照一定的格式放到一起。例如FLV格式的数据,经过解封装操作后,输出H.264编码的视频码流和AAC编码的音频码流。
5 |
6 |
7 |
8 | ### 1.解协议
9 | 解协议是指将流媒体协议的数据,解析为标准的封装格式数据。音视频在网络上传播的时候,常采用各种流媒体协议,例如HTTP、RTMP、RTMP、MMS等。这些协议在传输音视频数据的同时,也会传输一些信令数据。这些信令数据包括对播放的控制(播放、暂停、停止),或者对网络状态的描述等。在解协议的过程中会去除信令数据而只保留音视频数据。例如采用RTMP协议传输的数据,经过解协议操作后,输出FLV格式的数据。
10 | 注意:“文件”本身也是一种“协议”,常见的流媒体协议有HTTP、RTSP、RTMP等。
11 |
12 | ### 2.解封装
13 |
14 | 解封装是指将输入的封装格式的数据,分离成为音频流压缩编码数据和视频流压缩编码数据。封装格式种类有很多,例如MP4、MKV、RMVB、TS、FLV、AVI等,其作用就是将已经压缩编码的视频数据和音频数据按照一定的格式放到一起。例如FLV格式的数据,经过解封装操作后,输出H.264编码的视频码流和AAC编码的音频码流。
15 |
16 | ### 3.解码
17 |
18 | 解码是指将视频/音频压缩编码数据,解码成为非压缩的视频/音频原始数据。音频的压缩编码标准包含AAC、MP3、AC-3等,视频的压缩编码标准则包含H.264、MPEG-2、VC-1等。解码是整个系统中最重要也是最复杂的一个环节。通过解码,压缩编码的视频数据输出成为非压缩的颜色数据,例如YUV420P、RGB等。压缩编码的音频数据输出成为非压缩的音频抽样数据,例如PCM数据。
19 |
20 | ### 4.音视频同步
21 | 根据解封装模块在处理过程中获取的参数信息,同步解码出来的视频和音频数据,被送至系统的显卡和声卡播放出来。为什么需要音视频同步呢?媒体数据经过解复用流程后,音频/视频解码便是独立的,也是独立播放的,而在音频流和视频流中,其播放速度是由相关信息指定的,例如视频是根据帧率,音频是根据采样率。从帧率及采样率即可知道视频/音频播放速度。声卡和显卡均是以一帧数据来作为播放单位,如果单纯依赖帧率及采样率进行播放,在理想条件下,应该是同步的,不会出现偏差。
22 |
23 | 下面以一个44.1kHz的AAC音频流和24f/s的视频流为例来说明。如果一个AAC音频frame每个声道包含1024个采样点,则一个frame的播放时长为(1024/44 100)×1000ms≈23.27ms,而一个视频frame播放时长为1000ms/24≈41.67ms。理想情况下,音视频完全同步,但实际情况下,如果用上面那种简单的方式,慢慢地就会出现音视频不同步的情况,要么是视频播放快了,要么是音频播放快了。可能的原因包括:一帧的播放时间难以精准控制;音视频解码及渲染的耗时不同,可能造成每一帧输出有一点细微差距,长久累计,不同步便越来越明显;音频输出是线性的,而视频输出可能是非线性的,从而导致有偏差;媒体流本身音视频有差距(特别是TS实时流,音视频能播放的第1个帧起点不同),所以解决音视频同步问题引入了时间戳,它包括几个特点:首先选择一个参考时钟(要求参考时钟上的时间是线性递增的),编码时依据参考时钟给每个音视频数据块都打上时间戳。播放时,根据音视频时间戳及参考时钟来调整播放,所以视频和音频的同步实际上是一个动态的过程,同步是暂时的,不同步则是常态。
24 |
25 | ffplay是使用FFmpeg API开发的功能完善的开源播放器。在ffplay中各个线程如图5-45所示,扮演角色如下:read_thread线程扮演着图中Demuxer的角色;video_thread线程扮演着图中VideoDecoder的角色;audio_thread线程扮演着图中Audio Decoder的角色。主线程中的event_loop函数循环调用refresh_loop_wait_event则扮演着视频渲染的角色。回调函数sdl_audio_callback扮演着图中音频播放的角色。VideoState结构体变量则扮演着各个线程之间的信使。
26 |
27 | - (1)read_thread线程负责读取文件内容,将video和audio内容分离出来后生成packet,将packet输出到packet队列中,包括Video Packet Queue和Audio Packet Queue,不考虑subtitle。
28 | - (2)video_thread线程负责读取Video PacketsQueue队列,将video packet解码得到VideoFrame,将Video Frame输出到Video FrameQueue队列中。
29 | - (3)audio_thread线程负责读取Audio PacketsQueue队列,将audio packet解码得到AudioFrame,将Audio Frame输出到Audio FrameQueue队列中。
30 | - (4)主线程→event_loop→refresh_loop_wait_event负责读取Video Frame Queue中的video frame,调用SDL进行显示,其中包括了音视频同步控制的相关操作。
31 | - (5)SDL的回调函数sdl_audio_callback负责读取Audio Frame Queue中的audio frame,对其进行处理后,将数据返回给SDL,然后SDL进行音频播放。
32 |
33 | FFmpeg解码流程图:
34 |
35 |
36 | - (1)注册所有容器格式和CODEC,使用av_register_all,最新版本中无须调用该函数。
37 | - (2)打开文件av_open_input_file,最新版本为avformat_open_input。
38 | - (3)从文件中提取流信息av_find_stream_info。
39 | - (4)枚举所有流,查找的种类为CODEC_TYPE_VIDEO。
40 | - (5)查找对应的解码器avcodec_find_decoder。
41 | - (6)打开编解码器avcodec_open。
42 | - (7)为解码帧分配内存avcodec_alloc_frame。
43 | - (8)不停地从码流中提取帧数据av_read_frame。
44 | - (9)判断帧的类型,对于视频帧则调用avcodec_decode_video。
45 | - (10)解码完后,释放解码器avcodec_close。
46 | - (11)关闭输入文件av_close_input_file。
47 |
48 | 注意:该流程图为FFmpeg 2.x的版本,最新的FFmpeg 4.x系列的流程图略有改动。
49 |
50 |
51 |
--------------------------------------------------------------------------------
/VideoDevelopment/OpenCV/2.绘制图形.md:
--------------------------------------------------------------------------------
1 | 2.卷积
2 | ===
3 |
4 |
5 | ### 数字信号处理中卷积
6 |
7 | 卷积一词最开始出现在信号与线性系统中,信号与线性系统中讨论的就是信号经过一个
8 | 线性系统以后发生的变化。由于现实情况中常常是一个信号前一时刻的输出影响着这一时刻
9 | 的输出,所在一般利用系统的单位响应与系统的输入求卷积,以求得系统的输出信号(当然
10 | 要求这个系统是线性时不变的)。
11 | 卷积的定义:
12 | 卷积是两个变量在某范围内相乘后求和的结果。
13 |
14 | ### 数字图像处理中卷积
15 |
16 | 数字图像是一个二维的离散信号,对数字图像做卷积操作其实就是利用卷积核(卷积模
17 | 板)在图像上滑动,将图像点上的像素灰度值与对应的卷积核上的数值相乘,然后将所有
18 | 相乘后的值相加作为卷积核中间像素对应的图像上像素的灰度值,并最终滑动完所有图像的
19 | 过程。
20 |
21 |
22 | 我们经常能看到的,平滑,模糊,去燥,锐化,边缘提取等等工作,其实都可以通过
23 | 卷积操作来完成
24 |
25 | ### 模糊原理:
26 |
27 | 1. 是拿一个矩阵(3X3, 5X5)等,和原图从左向右从上到下分别进行卷积,将卷
28 | 积值最后赋值个当前卷积的中心像素。
29 | 2. 那么其最关键的参数,也就在于矩阵的大小和矩阵的值,我们通常称矩阵为卷积核。
30 | 3. 模糊操作的重要原因之一也是为了给图像预处理时降低噪声。
31 |
32 |
33 | ### 均值模糊:
34 | 也称为均值滤波,相当于卷积核的矩阵值全部为1/(卷积SIZE),
35 | 本次要记录的是图像的三种基本模糊操作:均值模糊、高斯模糊和中值模糊。
36 |
37 | 均值模糊的原理是通过一个卷积核,在图像上进行移动,对每次覆盖的像素范围进行求
38 | 平均值,然后将求得的平均值赋值给当前卷积核中心位置处的像素点。经过卷积核对一整幅
39 | 图像的多次平移,就得到了一幅均值模糊过后的图像,该图像上的每个像素点的值,都是其
40 | 邻域内所有像素点的平均值。均值模糊能够抹除图像的边缘和细节,对噪声也有一定的抑
41 | 制作用,但是它作为一种最最最基础的模糊操作,实现简单的同时,其效果也是麻麻嘚。
42 |
43 | 高斯模糊和均值模糊其原理上的唯一区别只是在于卷积核的值不同罢了,高斯卷积核矩
44 | 阵值服从二维高斯函数,也就是说一个图像与服从二维高斯分布的函数做卷积,由于高斯函
45 | 数是连续的,所有要将二维高斯函数进行采样和离散化,最后得到服从高斯函数规律的卷积
46 | 核矩阵。
47 |
48 | 高斯模糊也是一种基本的模糊操作,和均值模糊不同的是,高斯模糊是对某一像素点的邻域
49 | 内所有像素点进行加权求和,能够比较好的保留中心像素点对图像的影响。高斯模糊的效果
50 | 相比起均值模糊,要更好一些,而且在抑制噪声方面的能力也更强,尤其是对于高斯噪声的
51 | 抑制效果比较好。
52 |
53 |
54 |
55 |
56 |
57 | ### 锐化的概念
58 | 图像锐化的目的是使模糊的图像变得清晰起来,主要用于增强图像的灰度跳变部分,
59 | 这一点与图像平滑对灰度跳变的抑制正好相反。而且从算子可以看出来,平滑是基于对图像
60 | 邻域的加权求和或者说积分运算的,而锐化则是通过其逆运算导数(梯度)或者说有限差分
61 | 来实现的。
62 | 图像的卷积计算除了可以完成我们前面介绍的模糊去噪、边缘检测等任务外,还可以
63 | 实现图像锐化/增强的功能。
64 | 图像锐化是一种突出和加强图像中景物的边缘和轮廓的技术。
65 | 在图像中,边缘可以看作是位于一阶导数较大的像素位置,因此可以通过求图像的一阶
66 | 导数来加强图像的边缘。同样我们也可以通过求图像的二阶导数来完成图像锐化。
67 | 一般也通过Laplacian 滤波加原图权重像素叠加,
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | ---
84 |
85 | - 邮箱 :charon.chui@gmail.com
86 | - Good Luck!
87 |
--------------------------------------------------------------------------------
/VideoDevelopment/OpenGL/14.实例化.md:
--------------------------------------------------------------------------------
1 | ## 13.实例化
2 |
3 | 实例化(instancing)提供了一种机制,可以只用一个C++/OpenGL调用就告诉显卡渲染一个对象的多个副本。这可以带来显著的性能优势,特别是在绘制有数千甚至数百万个对象时,例如渲染在场地中的许多花朵。
4 |
5 | 实例化最常见的应用就是做一些粒子效果,例如一些烟花的效果。。
6 |
7 |
8 | 在渲染多个对象时,OpenGL使用Z-buffer算法来进行隐藏面消除。通常情况下,通过选择最接近相机的相应片段的颜色作为像素的颜色,这种方法可决定哪些物体的曲面可见并呈现到屏幕,而位于其他物体后面的曲面不应该被渲染。
9 | 然而,有时候场景中的两个物体表面重叠并位于重合的平面中,这使得深度缓冲区算法难以确定应该渲染两个表面中的哪一个(因为两者都不“最接近”相机)。发生这种情况时,浮点舍入误差可能会导致渲染表面的某些部分使用其中一个对象的颜色,而其他部分则使用另一个对象的颜色。这种不自然的伪影称为Z冲突(Z-fighting)或深度冲突(depth-fighting),是渲染的片段在深度缓冲区中相互对应的像素条目上“斗争”的结果。
10 |
11 |
12 | 
13 |
14 |
15 |
16 | 提高渲染效率的另一种方法是利用OpenGL的背面剔除能力。当3D模型完全“闭合”时,意味着内部永远不可见(例如对于立方体和四棱锥),那么外表面的那些与观察者背离且呈一定角度的部分将始终被同一模型的其他部分遮挡。也就是说,那些背离观察者的三角形不可能被看到(无论如何它们都会在隐藏面消除的过程中被覆盖),因此没有理由栅格化或渲染它们。
17 | 我们可以使用命令glEnable(GL_CULL_FACE)要求OpenGL识别并“剔除”(不渲染)背向的三角形。我们还可以使用glDisable(GL_CULL_FACE)禁用背面剔除。默认情况下,背面剔除是关闭的,因此如果你希望OpenGL剔除背向三角形,必须手动启用它。
18 |
19 |
20 |
21 |
22 | ---
23 | [上一篇: 13.LUT滤镜](https://github.com/CharonChui/AndroidNote/blob/master/VideoDevelopment/OpenGL/13.LUT%E6%BB%A4%E9%95%9C.md)
24 |
25 | ---
26 |
27 | - 邮箱 :charon.chui@gmail.com
28 | - Good Luck!
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/VideoDevelopment/OpenGL/15.美颜滤镜.md:
--------------------------------------------------------------------------------
1 | 美颜滤镜
2 | ---
3 |
4 | 美颜的基本原理就是深度学习加计算机图形学。
5 |
6 | - 深度学习用来人脸检测和人脸关键点检测。
7 | - 计算机图形学用来磨皮、瘦脸和化妆容。
8 | - 一般在Android上使用OpenGL ES,iOS使用Metal。
9 |
10 | ## 美颜算法的原理
11 |
12 |
13 | 美颜算法主要是基于图像处理技术,包括人脸检测、皮肤平滑、肤色调整、瘦脸大眼瞪。
14 | 其中,人脸检测是美颜的第一步,通过算法识别出照片中的人脸区域,然后针对该区域进行后续的美化处理。
15 |
16 | ### 关键技术点
17 |
18 | - 人脸检测:利用OpenCV(强大的计算机视觉库,支持人脸检测等)、Dlib等库实现。
19 | - 皮肤平滑:通过高斯模糊、双边滤波等技术减少皮肤瑕疵。
20 | - 肤色调整:调整肤色饱和度、亮度等,使肤色更加自然。
21 | - 瘦脸大眼:基于人脸关键点定位,通过变形算法实现。
22 |
23 |
24 |
25 | ### 人脸检测
26 |
27 | - 人脸检测指的是对图片或者视频流中的人脸进行检测,并定位到图片中的人脸。
28 | - 人脸关键点检测是对人脸中五官和脸的轮廓进行关键点定位,一般情况下它紧接在人脸检测后。
29 |
30 |
31 | - 加载OpenCV库,在项目中集成OpenCV库,并加载本地人脸检测模型。
32 | - 实时处理:在相机预览的回调中,对每一帧图像进行人脸检测,并应用美颜算法。
33 |
34 |
35 |
36 |
37 |
38 | https://zhuanlan.zhihu.com/p/163604590
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/VideoDevelopment/OpenGL/16.色温、色调、饱和度.md:
--------------------------------------------------------------------------------
1 |
2 | - 色温: 图片的色温指的是图片中呈现出来的整体颜色偏暖或偏冷的程度。
3 |
4 | 色温可以影响图像的分为和情感表达。简单理解就是色彩的温度,越低越冷如蓝色,越高越暖如红色。
5 |
6 | - 色调: 色调是指图片色彩的整体质感,包括主要色调的选择和组合。
7 |
8 | 通过调整色调,可以改变图像的整体色彩效果,营造特定的视觉风格或情绪。简单理解就是色彩倾向,倾向于红橙还是黄绿。
9 |
10 | - 亮度: 亮度描述了图片中各个部分的明暗程度。增加亮度将使整体图像看起来更明亮,减小亮度则会使图片变得更暗。
11 |
12 | 亮度的调整可以影响图像的整体视觉效果和光线感。
13 | 增加就是给图片所有色彩增加白色,减少黑色。注意是只加黑白两种颜色,不然容易跟纯度弄混。
14 |
15 | - 对比度: 对比度是描述图像中不同区域之间亮度差异的程度。
16 |
17 | 提高对比度会使图像中的明暗部分更加突出,增强图像的清晰度和视觉冲击力。
18 | 适当的对比度可以让图像更具有立体感和层次感。
19 | 增加就是让白的更白,黑的更黑。减少就是白的不那么白,黑的不那么黑。
20 |
21 | - 饱和度: 饱和度表示图片中颜色的纯度和鲜艳程度。
22 |
23 | 增加饱和度会使颜色更加丰富饱满,减少饱和度会使颜色变得更加灰暗。
24 | 调整饱和度可以改变图像的整体色彩和视觉吸引力。
25 |
26 | 简单理解就是增加图片各种颜色的纯度。
27 | 比如蓝色,增加纯度就是在蓝色上加蓝色,降低纯度就是键入蓝色的对比色,让它变灰色或黑色。
28 |
29 | - 高光: 高光是指图片中最亮的部分,通常实在光线照射下产生的明亮区域。
30 |
31 | 调整高光可以改变图像中亮部的强度和反射效果,从而影响图像的细节和光影效果。
32 | 增加就是给图片白色的部分再加点白色,减少就是减少点白色。
33 |
34 |
35 |
36 |
37 |
38 | ### RGB颜色模型
39 |
40 | RGB颜色模型是一种用于创建各种颜色的方法,它基于红色、绿色、蓝色三种颜色的组合。
41 |
42 | 通过调节这三种颜色的强度和比例,可以生成多种不同的颜色。
43 |
44 | ### HSV颜色模型
45 |
46 | HSV代表色相(Hue)、饱和度(Saturation)、明度(Value),或色相(Hue)、饱和度(Saturation)、亮度(Brightness)。
47 |
48 | 在HSV模型中,色相同样表示颜色本身,饱和度表示颜色的纯度或浓淡程度,而明度或亮度则表示颜色的亮度程度。
49 |
50 | HSV模型有时也被称为HSB(色相、饱和度、亮度)模型。
51 |
52 |
53 | HSV(HSB)颜色模型根据下图对应说明可以得知:
54 |
55 | - H(Hue):色调,用角度度量,取值范围为0°~360° ,从红色开始按逆时针方向计算
56 | - S(Saturation):饱和度,表示颜色接近光谱色的程度.一种颜色,可以看成是某种光谱色不白色混合的结果.通常取值范围为0%~100%,值越大,颜色越饱和.光谱色的白光成分为0,饱和度达到最高.
57 | - V(Value或Brightness):明度,表示颜色明亮的程度.
58 |
59 |
60 |
61 | ### HSL颜色模型
62 |
63 | HSL代表色相(Hue)、饱和度(Stauration)、亮度(Lightness)。
64 |
65 | 在HSL模型中,色相表示颜色本身,饱和度表示颜色纯度或浓淡程度,亮度则表示颜色的明暗程度。
66 |
67 | - H: 色相,使用不水平轴之间的角度来表示,范围是从(0 ~ 360)度。从蓝色开始。
68 | - S: 饱和度,说明颜色的相对浓度。
69 | - L: 亮度,在L=0处为黑色,在L=1处为白色。灰色沿着L轴分布。
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/VideoDevelopment/OpenGL/18.其他.md:
--------------------------------------------------------------------------------
1 | 18.其他
2 | ---
3 |
4 |
5 | 视觉暂留: 人眼在观察景物时,光信号传入大脑神经,需经过一段短暂的时间,光的作用结束后,视觉形象并不立即消失,这种残留的视觉称“后像”,视觉的这一现象则被称为“视觉暂留”。
6 |
7 | 物体在快速运动时,当人眼所看到的影像消失后,人眼仍能继续保留其影像0.1 ~ 0.4秒左右的图像,所以当图像的刷新帧率高于每秒16帧时,在人眼看来就是不间断的显示。
8 |
9 |
10 | #### 光栅扫描
11 |
12 |
13 | 光栅扫描显示是目前的主流。
14 |
15 | 在光栅扫描显示中,电子束横向扫描屏幕,一次一行,从顶到底依次进行。
16 |
17 | 电子束在屏幕上逐点移动时,从被称为帧缓存的存储区取出每个像素点的强度值控制其显示状态。
18 | 这种实现原理决定了光栅扫描显示的刷新频率和图像复杂度无关,刷新频率可以是恒定的。
19 |
20 |
21 | 光栅扫描显示对屏幕上的每个点都有存储强度信息的能力(除颜色信息外,其他的像素信息也存在帧缓存中),从而使得适用于包含阴影和彩色模式的逼真场景。
22 |
23 |
24 | GPU的渲染流程:
25 |
26 | GPU进行渲染时,会把数据先存储在帧缓冲区里,然后视频控制器读取帧缓冲区里的数据,完成数模转化,逐行扫描显示画面。
27 |
28 | 理论上,完美情况时,每扫描一张图,就显示一张,然后下一张也扫描完成等待刷新。
29 |
30 | 一次反复,屏幕就会保持流畅,那为什么有时候屏幕还会出现不流畅的现象?
31 |
32 |
33 | - 撕裂:
34 |
35 | 当视频控制器读取完一帧画面显示后,会去帧缓存读取下一帧,如果帧缓存仍然保持着上一帧的数据没有被刷新,视频控制器照常工作读取帧缓存中的数据。
36 |
37 | 当寄存器工作到某一行时,帧缓存的数据被更新为下一帧,视频控制器就读取到新的数据,而此时已经显示的画面是上一帧的数据,后续将要显示的是下一帧的数据,撕裂也就产生了。
38 |
39 | 所以,撕裂的产生是因为帧缓存的刷新频率没有跟上屏幕的刷新频率,根本原因就是CPU或GPU工作超时。
40 |
41 |
42 | 为了解决撕裂的问题,会使用垂直同步Vsync + 双缓冲的机制。
43 |
44 |
45 |
46 | 在VSync信号到来后,会在CPU中计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。
47 |
48 | 然后CPU会将计算好的数据提交到GPU,由GPU进行变换、合成、渲染。然后GPU会把渲染结果提交到帧缓存,等待下一次VSync信号到来时显示到屏幕上。
49 |
50 | 由于垂直同步的机制,如果在一个VSync时间内,CPU或者GPU没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时屏幕会刷新显示上一帧的内容。
51 |
52 |
53 |
54 | 通过这种机制,就解决了撕裂的问题,但同时引入了新的问题 -- 掉帧。
55 |
56 |
57 |
58 | 当CPU或GPU渲染流⽔线耗时过⻓,前后显示同一帧时,也就出现了掉帧现象(所以掉帧不是不渲染数据,而是重复渲染同一帧数据)。
59 |
60 |
61 |
62 | 至于掉帧无法被完全解决,因为CPU或GPU的工作是不可预估的。只能尽量合理的使用CPU或GPU减少掉帧次数。
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/VideoDevelopment/P2P技术/P2P原理_NAT穿透.md:
--------------------------------------------------------------------------------
1 | P2P原理_NAT穿透
2 | ===
3 |
4 | ### NAT
5 |
6 | NAT(Network Address Translation,网络地址转换),也叫做网络掩蔽或者IP掩蔽。NAT是一种网络地址翻译技术,主要是将内部的私有IP地址(private IP)转换成可以在公网使用的公网IP(public IP)。
7 |
8 | 网络地址转换,就是替换IP报文头部的地址信息。NAT通常部署在一个组织的网络出口位置,通过将内部网络IP地址替换为出口的IP地址提供公网可达性和上层协议的连接能力。那么,什么是内部网络IP地址?
9 |
10 | [RFC1918](https://datatracker.ietf.org/doc/rfc1918/)规定了三个保留地址段落:10.0.0.0-10.255.255.255;172.16.0.0-172.31.255.255;192.168.0.0-192.168.255.255。这三个范围分别处于A,B,C类的地址段,不向特定的用户分配,被IANA作为私有地址保留。这些地址可以在任何组织或企业内部使用,和其他Internet地址的区别就是,仅能在内部使用,不能作为全球路由地址。这就是说,出了组织的管理范围这些地址就不再有意义,无论是作为源地址,还是目的地址。对于一个封闭的组织,如果其网络不连接到Internet,就可以使用这些地址而不用向IANA提出申请,而在内部的路由管理和报文传递方式与其他网络没有差异。
11 |
12 | 对于有Internet访问需求而内部又使用私有地址的网络,就要在组织的出口位置部署NAT网关,在报文离开私网进入Internet时,将源IP替换为公网地址,通常是出口设备的接口地址。一个对外的访问请求在到达目标以后,表现为由本组织出口设备发起,因此被请求的服务端可将响应由Internet发回出口网关。出口网关再将目的地址替换为私网的源主机地址,发回内部。这样一次由私网主机向公网服务端的请求和响应就在通信两端均无感知的情况下完成了。依据这种模型,数量庞大的内网主机就不再需要公有IP地址了。
13 |
14 | 那么,NAT与此同时也带来一些弊端:首先是,NAT设备会对数据包进行编辑修改,这样就降低了发送数据的效率;此外,各种协议的应用各有不同,有的协议是无法通过NAT的(不能通过NAT的协议还是蛮多的),这就需要通过穿透技术来解决。我们后面会重点讨论穿透技术。
15 |
16 | ### NAT原理
17 | 内网主机向外网主机发送的网络包,在经过NAT时,IP和PORT会被替换为NAT为该主机分配的外网IP/PORT,也就是该内网主机在NAT上的出口IP/PORT,外网主机收到该网络包后,会视该网络包是从NAT发送的。
18 | 外网主机只能通过NAT为该内网主机分配的外网IP/PORT,向它发送网络包,内网主机的本地地址对外界不可见,网络包在经过NAT的时候,会被NAT做外网IP/PORT到内网IP/PORT的转换。
19 | 可见,NAT维护内网主机内网地址和在NAT上为它分配的外网地址之间的映射关系,需要维护一张关联表。
20 | NAT在两个传输方向上做两次地址转化,出方向做源(src)信息转换,入方向做目的(dst)信息替换,内外网地址转换是在NAT上自动完成的。NAT网关的存在对通信双方是透明的。
21 |
22 | 那NAT是符合环节IPv4地址枯竭的问题的呢?
23 | 答案是端口多路复用,通过PAT(Port Address Translation),让NAT背后的多台内网主机共享一个外网IP,最大限度的节省外网IP资源。
24 |
25 |
26 |
27 | **虽然实际过程远比这个复杂,但上面的描述概括了NAT处理报文的几个关键特点:**
28 |
29 |
30 |
31 | - 1)网络被分为私网和公网两个部分,NAT网关设置在私网到公网的路由出口位置,双向流量必须都要经过NAT网关;
32 | - 2)网络访问只能先由私网侧发起,公网无法主动访问私网主机;
33 | - 3)NAT网关在两个访问方向上完成两次地址的转换或翻译,出方向做源信息替换,入方向做目的信息替换;
34 | - 4)NAT网关的存在对通信双方是保持透明的;
35 | - 5)NAT网关为了实现双向翻译的功能,需要维护一张关联表,把会话的信息保存下来。
36 |
37 |
38 |
39 | 简单的背景了解过后,下面介绍下NAT实现的主要方式,以及NAT都有哪些类型。
40 |
41 | ### NAT的实现方式
42 | - 静态NAT: 就是静态地址转换。是指一个公网IP对应一个私有IP,是一对一的转换,同时注意,这里只进行了IP转换,而没有进行端口的转换。
43 |
44 | - NAPT: 端口多路复用技术。与静态NAT的差别是,NAPT不但要转换IP地址,还要进行传输层的端口转换。具体的表现形式就是,对外只有一个公网IP,通过端口来区别不同私有IP主机的数据。
45 |
46 | ### NAT映射
47 |
48 | 表内记录了私有ip和公有ip的对应关系,例如:
49 | ```
50 | 192.168.1.35:8000 <--> 123.24.56.78:10000
51 | ....
52 | ```
53 |
54 | ### NAT打洞
55 |
56 | 正常两个人发送数据,例如通过QQ聊天,目前的步骤是:
57 | - A使用私有ip经过NAT映射转换为公有IP后将数据发送给腾讯
58 | - 腾讯将数据转发给B用户
59 | 那后续能不能直接A和B进行链接,这样可以提高效率:
60 | - 直接通讯存在一个问题,路由器内部有一个保护支持,对于陌生的IP第一次发的数据包,路由器会丢弃或屏蔽掉,以此来防止陌生网络进行攻击。
61 | - 所以要想能够让B的路由器直接接受到A发送的数据,那就必须保证B的路由器要熟悉A的IP地址,这样B才能够进行接收。而A和B首次登录QQ的时候腾讯已经将自己的ip返回给A和B了,所以A和B的路由中都认识腾讯的IP了,这样A和B都能接受腾讯发送的数据包。
62 | - 那如何让A和B进行直接通信,这里就是腾讯所有的事情,腾讯就通过腾讯的公网IP对AB之间进行了打洞的操作,将对方的IP都下发给对应的AB用户,这样就把他俩形成了一个私有的通路,这个操作就是打洞。
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | ---
84 |
85 | - 邮箱 :charon.chui@gmail.com
86 | - Good Luck!
87 |
--------------------------------------------------------------------------------
/VideoDevelopment/WebRTC.md:
--------------------------------------------------------------------------------
1 | WebRTC
2 | ===
3 |
4 | WebRTC,名称源自网页实时通信(Web Real-Time Communication)的缩写,是一个支
5 | 持网页浏览器进行实时语音通话或视频聊天的技术,是谷歌2010 年以6820 万美元收购
6 | Global IP Solutions 公司而获得的一项技术。
7 | WebRTC 提供了实时音视频的核心技术,包括音视频的采集、编解码、网络传输、显示
8 | 等功能,并且还支持跨平台:windows,linux,mac,android。
9 | 虽然WebRTC 的目标是实现跨平台的Web 端实时音视频通讯,但因为核心层代码的
10 | Native、高品质和内聚性,开发者很容易进行除Web 平台外的移殖和应用。很长一段时间内
11 | WebRTC 是业界能免费得到的唯一高品质实时音视频通讯技术。
12 |
13 | WebRTC 是一项在浏览器内部进行实时视频和音频通信的技术,是谷歌于2010 年以
14 | wwwwww..hheelllloottoonnggttoonngg..ccoom ©®
15 | 6820 万美元收购VoIP 软件开发商Global IT Solutions 公司而获得一项技术,谷歌于2011 年
16 | 6 月3 日开源该项目。
17 | 谷歌在官方博客中称:“我们希望让浏览器成为实时通信的创新地所在,到目前为止,
18 | 实时通信需要使用受版权保护的信号处理技术,并通过插件或下载客户端才能实现,而
19 | WebRTC 则允许开发人员使用HTML 和JavaScript API 来创建实时应用。”
20 |
21 |
22 | 1.webrtc 是什么
23 | 浏览器为音视频获取传输提供的接口
24 | 2.webrtc 可以做什么
25 | 浏览器端到端的进行音视频聊天、直播、内容传输
26 | 3.数据传输需要些什么
27 | IP、端口、协议
28 | 客户端、服务端
29 |
30 |
31 | 
32 | 1.绿色部分是WebRTC 核心部分(核心库)
33 | 2.紫色部分是JS 提供的API(应用层)
34 | 整体是应用层调用核心层。
35 | 核心层,第一层C++ API
36 | 提供给外面的接口。最主要的是(PeerConnedtion 对等连接)。
37 | 核心层,第二层Session
38 |
39 | 上下文管理层(音视频)。
40 | 核心层,第三层[最重要的部分]
41 | 音视频引擎:编解码;音频缓冲BUFFER 防止音频网络抖动NetEQ;回音消除;降噪;静音检
42 | 测;
43 | 视频引擎:编解码;jitter buffer 防止视频网络抖动;图像处理增强;
44 | 传输:SRTP 加密后的RTP;多路复用;P2P(STUN+TURN+ICE)
45 | 核心层,第四层,硬件相关层
46 | 音视频采集; 网络IO
47 |
48 |
49 | WebRTC 实现了基于网页的视频会议,标准是WHATWG 协议,目的是通过浏览器提供
50 | 简单的javascript 就可以达到实时通讯(Real-Time Communications (RTC))能力。
51 | WebRTC(Web Real-Time Communication)项目的最终目的主要是让Web 开发者能够基
52 | 于浏览器(Chrome/FireFox/...)轻易快捷开发出丰富的实时多媒体应用,而无需下载安装任
53 | 何插件,Web 开发者也无需关注多媒体的数字信号处理过程,只需编写简单的Javascript 程
54 | 序即可实现,W3C 等组织正在制定Javascript 标准API,目前是WebRTC 1.0 版本,Draft 状
55 | 态;另外WebRTC 还希望能够建立一个多互联网浏览器间健壮的实时通信的平台,形成开发
56 | 者与浏览器厂商良好的生态环境。同时,Google 也希望和致力于让WebRTC 的技术成为
57 | HTML5 标准之一,可见Google 布局之深远。
58 | WebRTC 提供了视频会议的核心技术,包括音视频的采集、编解码、网络传输、显示等
59 | 功能,并且还支持跨平台:windows,linux,mac,android。
60 |
61 |
62 | - 邮箱 :charon.chui@gmail.com
63 | - Good Luck!
64 |
--------------------------------------------------------------------------------
/VideoDevelopment/搭建nginx+rtmp服务器.md:
--------------------------------------------------------------------------------
1 | 搭建nginx+rtmp服务器
2 | ===
3 |
4 | 为了更好的进行直播功能的开发,我们需要本地搭建`nginx+rtmp`服务器,下面就是介绍如何在`Mac`上的搭建步骤:
5 |
6 | 1. 安装`Homebrew`(有关`Homebrew`的介绍请参考[Homebrew介绍](http://www.cnblogs.com/lzrabbit/p/4032515.html)
7 |
8 | 打开终端, 查看是否已经安装了`Homebrew`, 直接终端输入命令`man brew`
9 | 如果`Mac`已经安装了, 会显示一些命令的帮助信息. 此时输入`Q`退出即可, 直接进入第二步. 反之, 如果没有安装,执行命令
10 | `ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
11 | 如果安装后, 想要卸载
12 | `ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)"`
13 |
14 | 2. 安装`nginx`
15 |
16 | - 先执行命令`brew tap homebrew/nginx`来`clone nginx`项目到本地
17 | - 通过命令`brew install nginx-full --with-rtmp-module`执行安装
18 |
19 | 此时, `nginx`和`rtmp`模块就安装好了,输入命令`nginx`然后在浏览器里打开`http://localhost:8080`,如果出现欢迎界面就说明大功告成了。
20 |
21 | 3. 配置`nginx`和`ramp`
22 |
23 | 首先通过命令`brew info nginx-full`查看安装目录, 然后找到`nginx.conf`文件所在位置, 例如`/usr/local/etc/nginx/nginx.conf`。
24 | 
25 |
26 | 打开`nginx.confg`文件后在文件的最后一行空白处添加如下代码。
27 |
28 | ```java
29 | rtmp {
30 | server {
31 | listen 1935;
32 | application rtmplive {
33 | live on;
34 | record off;
35 | }
36 | }
37 | }
38 | ```
39 | 然后通过命令`[path] -s reload`重启`nginx`,我的是`/usr/local/opt/nginx-full/bin/nginx -s reload`。
40 | 
41 |
42 | 4. 安装`ffmpeg`
43 |
44 | 执行命令`brew install ffmpeg`安装`ffmpeg`。
45 |
46 | 5. 安装`VLC`播放器
47 |
48 | [VLC](http://www.videolan.org/)下载安装,可用于播放测试`rtmp`协议的流媒体。
49 |
50 | 6. `Over`.
51 |
52 |
53 |
54 | 参考:
55 |
56 | - [https://github.com/arut/nginx-rtmp-module/wiki](https://github.com/arut/nginx-rtmp-module/wiki)
57 |
58 | ---
59 |
60 | - 邮箱 :charon.chui@gmail.com
61 | - Good Luck!
62 |
--------------------------------------------------------------------------------
/VideoDevelopment/流媒体协议/HTTP FLV.md:
--------------------------------------------------------------------------------
1 | HTTP FLV
2 | ===
3 |
4 | 在说HTTP-FLV之前,我们有必要对FLV adobe 官方标准有个认识,因为HTTP-FLV协议中封装格式使用的是FLV。FLV文件格式标准是写F4V/FLV fileformat spec v10.1的附录E里面的FLVFile Format。
5 |
6 |
7 |
8 | FLV(Flash Video)是Adobe公司设计开发的一种流行的流媒体格式,其格式相对简单轻量,不需要很大的媒体头部信息。整个FLV由Header和Body以及其他Tag组成。因此加载速度极快。它是基于HTTP/80传输,可以避免被防火墙拦截的问题,除此之外,它可以通过 HTTP 302 跳转灵活调度/负载均衡,支持使用 HTTPS 加密传输,也能够兼容支持 Android,iOS 的移动端。但是由于它的传输特性,会让流媒体资源缓存在本地客户端,在保密性方面不够好,因为网络流量较大,它也不适合做拉流协议。此外,FLV可以使用Flash Player进行播放,而Flash Player插件已经安装在绝大部分浏览器上,这使得通过网页播放FLV视频十分容易。FLV封装格式的文件后缀通常为“.flv”。
9 |
10 | HttpFlv 就是 http+flv ,将音视频数据封装成FLV格式,然后通过 HTTP 协议传输给客户端。
11 |
12 | 当下的直播平台中大部分的主线路使用的都是HTTP-FLV协议,备线路多为RTMP。小编随便在Safari中打开几个直播平台房间,一抓包就不难发现使用HTTP-FLV协议的身影:熊猫、斗鱼、虎牙、B站。
13 |
14 |
15 |
16 | HTTP-FLV
17 |
18 | 我们这里说的HTTP-FLV,主要是说的是HTTP-FLV流,而不是基于HTTP的FLV视频文件点播,也不是能够随意SEEK的HTTP FLV伪流。
19 |
20 | FLV渐进式下载:通过HTTP协议将FLV下载到播放器中播放,无法直接拉到中间去播放。
21 |
22 | HTTP FLV伪流:支持SEEK,可从未下载的部分开始播放。
23 |
24 | HTTP-FLV流:拥有和流式协议RTMP一样的特征,长连接,流式数据。
25 |
26 | #### 4 优点:
27 |
28 | - 服务器兼容性好:基于 HTTP 协议。
29 | - 低延迟:直接传输 FLV 流,而且基于 HTTP 长链接。
30 |
31 | #### 5 缺点:
32 |
33 | - 播放端兼容性不好:需要 Flash 支持,不支持多音视频流,不便于 Seek。
34 |
35 | ▣ HTTP-FLV技术实现
36 |
37 | HTTP协议中有个content-length字段的约定,即http的body部分的长度。服务器回复http请求时如果有这个字段,客户端就接收这个长度的数据然后认为数据传输完成了,开始播放。
38 |
39 | 如果服务器回复http请求中没有这个字段,客户端就一直保持长连接接收数据,直到服务器跟客户端的socket断开。
40 |
41 | HTTP-FLV流就利用了上述的第二个原理,服务器回复客户端请求的时候不加content-length字段,在回复了http内容之后,进行持续的数据发送,客户端就一直接收数据,以此实现了HTTP-FLV流直播。
42 |
43 | 数据传输依然是之前讲过的内容,每一个音视频数据都被封装成包含时间戳信息头的数据包,封装格式采用FLV,传输协议采用http。
44 |
45 |
46 |
47 |
48 |
49 | ❸ RTMP和HTTP-FLV的比较:
50 |
51 | RTMP和HTTP-FLV延迟上保持一致,在这二者的区别如下:
52 |
53 | ▲ 穿墙:很多防火墙会墙掉RTMP,但是不会墙HTTP,因此HTTPFLV更不容易出问题。
54 |
55 | ▲ 调度:虽然RTMP也能支持302,单实现起来较麻烦。HTTP FLV本身就支持302,更方便CDN进行重定向以便更精准的调度。
56 |
57 | ▲ 容错: HTTP-FLV回源时也可以回多个源,能做到和RTMP一样,支持多级热备。
58 |
59 | ▲ 简单:FLV是最简单的流媒体封装,HTTP是最广泛的协议,这两个组合在一起维护性更高,比RTMP简单。
60 |
61 | ▲ 友好:HTTP-FLV代码量更小,集成SDK也更轻便。使用起来也更简单。
62 |
63 | 综上,HTTP-FLV协议具有RTMP的延迟优势,又继承了HTTP所有优势,是流媒体直播首选的分发协议。
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | https://blog.csdn.net/luzubodfgs/article/details/78155117
74 |
75 |
76 |
77 | https://blog.csdn.net/qq_37382077/article/details/103386289
78 |
79 |
80 |
81 | ---
82 |
83 | - 邮箱 :charon.chui@gmail.com
84 | - Good Luck!
85 |
--------------------------------------------------------------------------------
/VideoDevelopment/视频封装格式/AVI.md:
--------------------------------------------------------------------------------
1 | ## AVI
2 |
3 | 音频视频交错(Audio Video Interleaved,AVI)格式,是一门成熟的老技术,尽管国际学术界公认AVI已经属于被淘汰的技术,但是简单易懂的开发API,还在被广泛使用。AVI符合RIFF(ResourceInterchange File Format)文件规范,使用四字符码(Four-Character Code,FOURCC)表征数据类型。AVI的文件结构分为头部、主体和索引3部分。主体中图像数据和声音数据是交互存放的,从尾部的索引可以索引到想放的位置。AVI本身只提供了这么一个框架,内部的图像数据和声音数据格式可以是任意的编码形式。因为索引放在了文件尾部,所以在播放网络流媒体时已力不从心。例如从网络上下载AVI文件,如果没有下载完成,则很难正常播放出来。
4 |
5 |
6 | AVI中有两种最基本的数据单元,一个是Chunks,另一个是Lists,结构体如下:
7 |
8 | ```c++
9 | // Chunks
10 | typedef struct {
11 | DWORD dwFourcc;
12 | DWORD dwSize; // data
13 | BYTE data[dwSize]; // contains headers or audio/video data
14 | } CHUNK;
15 |
16 | // Lists
17 | typedef struct {
18 | DWORD dwList;
19 | DWORD dwSize;
20 | DWORD dwFourCC;
21 | BYTE data[dwSize - 4];
22 | } LIST;
23 | ```
24 |
25 | 由如上代码可知,Chunks数据块由一个四字符码、4B的data size(指下面的数据大小)及数据组成。Lists由4部分组成,包括4B的四字符码、4B的数据大小(指后面列的两部分数据大小)、4B的list类型及数据组成。与Chunks数据块不同的是,Lists数据内容可以包含字块(Chunks或Lists)。
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/VideoDevelopment/视频封装格式/fMP4格式详解.md:
--------------------------------------------------------------------------------
1 | fMP4格式详解
2 | ===
3 |
4 | MP4文件的基本单元是“box”,这些box既可以包括data,也可以包括metadata。MP4文件标准允许多种方式来组织data box和metadata box。将metadata放在data之前,客户端应用程序可以在播放video/audio之前获得更多的关于video/audio的信息,因此这种方式在大多数的多媒体应用场景都是比较有用的。但是,在流媒体应用场景,不可能预先保存关于整个流数据的metadata信息,因为不可能提前完全知道。而且,预先保存的metadata越少就意味着越少的开销,因此也可以缩短启动时间。
5 |
6 | MP4 ISO Base Media文件格式标准允许以fragmented方式组织box,这也就意味着MP4文件可以组织成这样的结构,由一系列的短的metadata/data box对组成,而不是一个长的metadata/data对。Fragmented MP4文件结构如图1所示,图中只给出了两个fragments。
7 |
8 |
9 | fmp4 是基于 MPEG-4 Part 12 的**流媒体格式**。与普通MP4相比:
10 |
11 | - fmp4不需要一个 moov Box 来进行 initialization
12 | - fmp4 的 moov Box 只包含了一些 track 信息
13 | - fmp4 的 视频/音频 metadata 信息与数据都存在一个个 moof、mdat 中,它是一个流式的封装格式
14 |
15 |
16 |
17 |
18 |
19 | 
20 |
21 | 
22 |
23 |
24 |
25 | Fragmented MP4 以 Fragment 的方式存储视频信息。每个 Fragment 由一个 moof box 和一个 mdat box 组成:
26 |
27 | - ‘mdat’(media data box)
28 |
29 | 和普通MP4文件的‘mdat’一样,用于存放媒体数据,不同的是普通MP4文件只有一个‘mdat’box,而Fragmented MP4文件中,每个fragment都会有一个‘mdat’类型的box。
30 |
31 | - ‘moof’(movie fragment box)
32 |
33 | 该类型的box存放的是fragment-level的metadata信息,用于描述所在的fragment。该类型的box在普通的MP4文件中是不存在的,而在Fragmented MP4文件中,每个fragment都会有一个‘moof’类型的box。moof和moov非常像,它包含了当前片段中mp4的相关元信息。
34 |
35 | 一个‘moof’和一个‘mdat’组成Fragmented MP4文件的一个fragment,这个fragment包含一个video track或audio track,并且包含足够的metadata以保证这部分数据可以单独解码。Fragmented MP4 中的 moov box 只存储文件级别的媒体信息,因此 moov box 的体积比传统的 MP4 中的 moov box 体积要小很多。
36 |
37 |
38 |
39 | 
40 |
41 |
42 |
43 | ### FMP4与普通MP4 BOX的区别
44 |
45 | #### Movie Extends Box (mvex)(fMP4专有)
46 |
47 | **mvex 是 fMP4 的标准盒子。它的作用是告诉解码器这是一个fMP4的文件,具体的 samples 信息内容不再放到 trak 里面,而是在每一个 moof 中**。基本格式为:
48 | ```
49 | aligned(8) class MovieExtendsHeaderBox extends FullBox(‘mehd’, version, 0) { if (version==1) {
50 | unsigned int(64) fragment_duration;
51 | } else { // version==0
52 | unsigned int(32) fragment_duration;
53 | }
54 | }
55 | ```
56 |
57 |
58 | ### moof
59 |
60 | moof 主要是用来存放 FMP4 的相关内容。它本身没啥太多的内容。
61 |
62 |
63 |
64 | ---
65 |
66 | - 邮箱 :charon.chui@gmail.com
67 | - Good Luck!
--------------------------------------------------------------------------------
/VideoDevelopment/视频编码/AV1.md:
--------------------------------------------------------------------------------
1 | AV1
2 | ===
3 |
4 |
5 |
6 |
7 |
8 | TODO
9 |
10 |
11 |
12 | ---
13 |
14 | - 邮箱 :charon.chui@gmail.com
15 | - Good Luck!
--------------------------------------------------------------------------------
/VideoDevelopment/视频编码/H265.md:
--------------------------------------------------------------------------------
1 | H265
2 | ===
3 |
4 |
5 |
6 | TODO
7 |
8 |
9 |
10 | ---
11 |
12 | - 邮箱 :charon.chui@gmail.com
13 | - Good Luck!
--------------------------------------------------------------------------------
/VideoDevelopment/视频编码/视频编码原理.md:
--------------------------------------------------------------------------------
1 | ## 视频编码原理
2 |
3 |
4 | 为什么巨大的原始视频可以编码成很小的视频呢?这其中的技术是什么呢?
5 | 核心思想就是去除冗余信息:
6 | - 空间冗余: 图像相邻像素之间有较强的相关性
7 | - 时间冗余:视频序列的相邻图像之间内容相似
8 | - 编码冗余:不同像素值出现的概率不同
9 | - 视觉冗余:人的视觉系统对某些细节不敏感
10 | - 知识冗余:规律性的结构可由先验知识和背景知识得到
11 |
12 | 视频本质上讲是一系列图片连续快速的播放,最简单的压缩方式就是对每一帧图片进行压缩,例如比较古老的MJPEG编码就是这种编码方式,这种编码方式只是帧内编码,利用空间上的取样预测来编码。
13 | 但是帧与帧之间因为时间的相关性,后续开发出了一些比较高级的编码器可以采用帧间编码,简单点说就是通过搜索算法选定了帧上的某些区域,然后通过计算当前帧和前后参考帧的向量差进行编码的一种形式,例如一个人滑雪,前后两帧画面的差异就是人的位置移动了。
14 | ### 编码器中的关键技术
15 | - 预测: 预测编码可以用于处理视频中的时间和空间域的冗余,主要分为两大类:帧内预测和帧间预测。
16 | - 帧内预测: 预测值与实际值位于同一帧内,用于消除图像的空间冗余。帧内预测的特点是压缩率相对较低,然而可以独立解码,不依赖其他帧的数据。通常视频中的关键帧都采用帧内预测。
17 | - 帧间预测: 帧间预测的实际值位于当前帧,预测值位于参考帧,用于消除图像的时间冗余。帧间预测的压缩率高于帧内预测,然而不能独立解码,必须在获取参考帧数据之后才能重建当前帧。
18 | 通常在视频码流中,I帧全部使用帧内编码,P帧、B帧中的数据可能使用帧内或帧间编码。
19 | - 变换: 变化编码是指将给定的图像变换到另一个数据域如频域上,使得大量的信息能用较少的数据来表示,从而达到压缩的目的。目前主流的视频编码算法均属于有损编码,通过对视频造成有限而可以容忍的损失,获取相对更高的编码效率。而造成信息损失的部分即在于变换量化这一部分。在进行量化之前,首先需要将图像信息从空间域通过变换编码变换至频域,并计算其变换系数供后续的编码。
20 | - 量化
21 | - 熵编码: 视频编码中的熵编码方法主要用于消除视频信息中的统计冗余。由于信源中每一个符号的出现概率并不一致,这就导致使用同样长度的码字表示所有的符号会造成浪费。通过熵编码,针对不同的语法元素分配不同长度的码元,可以有效消除视频信息中由于符号概率导致的冗余。
22 |
23 | 
24 | 
25 | 
26 |
27 | - 对I帧的处理,是采用帧内编码方式,只利用本帧图像内的空间相关性。
28 | 帧内编码虽然只有空间相关性,但整个编码过程并不简单,主要如下:
29 | 
30 | - RGB转YUV:固定公式
31 | - 图片宏块切割:宏块16*16
32 | - DCT:离散余弦变换
33 | - 量化:取样
34 | - ZigZag:扫描
35 | - DPCM:差值脉冲编码调制
36 | - RLE:游程编码
37 | - 霍夫曼编码
38 | - 算数编码
39 | - 对P帧的处理,采用帧间编码(前向运动估计),同时利用空间和时间上的相关性。
40 | 简单来说,采用运动补偿(motion compensation)算法来去掉冗余信息。
41 |
42 | ##### 编解码中的关键流程
43 |
44 | 
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/VideoDevelopment/音频编码/AAC.md:
--------------------------------------------------------------------------------
1 | ## AAC
2 |
3 | AAC 是高级音频编码(Advanced Audio Coding)的缩写,出现于1997 年,最
4 | 初是基于MPEG-2 的音频编码技术,目的是取代MP3 格式。2000 年,MPEG-4
5 | 标准出台,AAC 重新集成了其它技术(PS,SBR),为区别于传统的MPEG-2 AAC,
6 | 故含有SBR 或PS 特性的AAC 又称为MPEG-4 AAC。
7 | AAC 是新一代的音频有损压缩技术,它通过一些附加的编码技术(比如PS,SBR
8 | 等),衍生出了LC-AAC,HE-AAC,HE-AACv2 三种主要的编码。其中LC-AAC 就是比
9 | 较传统的AAC,相对而言,主要用于中高码率(>=80Kbps),HE-AAC(相当于AAC+SBR)
10 | 主要用于中低码(<=80Kbps),而新近推出的HE-AACv2(相当于AAC+SBR+PS)主要用
11 | 于低码率(<=48Kbps)。事实上大部分编码器设成<=48Kbps 自动启用PS 技术,
12 | 而>48Kbps 就不加PS,就相当于普通的HE-AAC。
13 |
14 | #### AAC编码特点
15 | (1). AAC 是一种高压缩比的音频压缩算法,但它的压缩比要远超过较老的音频压
16 | 缩算法,如AC-3、MP3 等。并且其质量可以同未压缩的CD 音质相媲美。
17 | (2). 同其他类似的音频编码算法一样,AAC 也是采用了变换编码算法,但AAC
18 | 使用了分辨率更高的滤波器组,因此它可以达到更高的压缩比。
19 | (3). AAC 使用了临时噪声重整、后向自适应线性预测、联合立体声技术和量化哈
20 | 夫曼编码等最新技术,这些新技术的使用都使压缩比得到进一步的提高。
21 | (4). AAC 支持更多种采样率和比特率、支持1 个到48 个音轨、支持多达15 个低
22 | 频音轨、具有多种语言的兼容能力、还有多达15 个内嵌数据流。
23 | (5). AAC 支持更宽的声音频率范围,最高可达到96kHz,最低可达8KHz,远宽于
24 | MP3 的16KHz-48kHz 的范围。
25 | (6). 不同于MP3 及WMA,AAC 几乎不损失声音频率中的甚高、甚低频率成分,
26 | 并且比WMA 在频谱结构上更接近于原始音频,因而声音的保真度更好。专业评
27 | 测中表明,AAC 比WMA 声音更清晰,而且更接近原音。
28 | (7). AAC 采用优化的算法达到了更高的解码效率,解码时只需较少的处理能力。
29 |
30 |
31 |
32 | 原始的PCM音频数据也是非常大的数据量,因此也需要对其进行压缩编码。
33 | 和视频编码一样,音频也有许多的编码格式,如:WAV、MP3、WMA、APE、FLAC等等,音乐发烧友应该对这些格式非常熟悉,特别是后两种无损压缩格式。
34 |
35 | AAC是新一代的音频有损压缩技术,一种高压缩比的音频压缩算法。
36 | 在MP4视频中的音频数据,大多数时候都是采用的AAC压缩格式。
37 |
38 | AAC格式主要分为两种: ADIF、ADTS。
39 |
40 | - ADIF:Audio Data Interchange Format。 音频数据交换格式
41 | 这种格式的特征是可以确定的找到这个音频数据的开始,不需进行在音频数据流中间开始的解码,即它的解码必须在明确定义的开始处进行。这种格式常用在磁盘文件中。
42 | ADIF只有一个统一的头,所以必须得到所有的数据后解码。
43 | - ADTS: Audio Data Transport Stream。音频数据传输流
44 | 这种格式的特征是它是一个由同步字的比特流,解码可以在这个流中任何位置开始。
45 | 它的特征类似于mp3数据流格式。
46 |
47 | ADTS可以在任意帧解码,它每一帧都有头信息。
48 | ADIF只有一个统一的头,所以必须得到所有的数据后解码。
49 | 且这两种的header的格式也是不同的,目前一般编码后的都是ADTS格式的音频流。
50 |
51 | 简单说,ADTS 可以在任意帧解码,也就是说它每一帧都有头信息。ADIF 只有一个统一的头,所以必须得到所有的数据后解码。这两种的header 的格式也是
52 | 不同的,一般编码后的和抽取出的都是ADTS 格式的音频流。
53 |
54 |
55 | 
56 |
57 |
58 |
--------------------------------------------------------------------------------
/VideoDevelopment/音频编码/PCM.md:
--------------------------------------------------------------------------------
1 | ## PCM
2 |
3 | PCM(Pulse Code Modulation),脉冲编码调制。人耳听到的是模拟信号,PCM是把声音从模拟信号转化为数字信号的技术。
4 |
5 | 原理是用一个固定的频率对模拟信号进行采样,采样后的信号在波形上看就像一串连续的幅值不一的脉冲(脉搏似的短暂起伏的电冲击),把这些脉冲的幅值按一定精度进行量化,这些量化后的数值被连续的输出、传输、处理或记录到存储介质中,所有这些组成了数字音频的产生过程(抽样、量化、编码三个过程)。
6 |
7 | ### 声音的三要素
8 |
9 | - 音调:音频的快慢
10 | - 音量:震动的幅度
11 | - 音色:谐波
12 |
13 | ### 量化
14 | - 采样大小(采样位数):一个采样数据用多少bit存放,8bit、16bit
15 | - 采样率:采样的频率8k、16k、32k、44.1k、48k
16 | - 声道数:单声道、双声道、多声道
17 |
18 |
19 | ### 大小计算
20 | 一秒数据大小(码率) = 采样大小 * 采样率 * 声道数 (Kb/s)。
21 | 假设采样率为8kHz、声道数、采样为16bit,时长为1s,则音频数据的大小为:
22 | 1 * 8000 * 2 * 16 = 256000 bit / 8 = 32000 byte / 1024 = 31.25KB
23 |
24 | ### 数据排列方式
25 |
26 |
27 |
28 | 左右声道每个样本点数据交错排列
29 | 存储格式1: 交错、打包、packed
30 |
31 |
32 | 存储格式2: 平面、平行、planar
33 |
--------------------------------------------------------------------------------
/VideoDevelopment/音频编码/WAV.md:
--------------------------------------------------------------------------------
1 | ## WAV
2 |
3 | WAVE文件格式是微软FIFF(Resource Interchange FileFormat)资源交换文件标准的一种,是针对于多媒体文件存储的一种文件格式和标准。
4 |
5 | 一般而言,RIFF文件由文件头和数据两部分组成,一个WAVE文件由一个“WAVE"数据块组成,这个“WAVE”块又由一个fmt子数据块和一个data子数据块组成,也称这种格式为Canonical form(权威/牧师格式),如下图所示:
6 |
7 |
8 |
9 |
10 |
11 |
12 | - [WAV格式](https://docs.fileformat.com/audio/wav/)
13 |
--------------------------------------------------------------------------------
/VideoDevelopment/音频编码/音频编码格式.md:
--------------------------------------------------------------------------------
1 | ## 音频编码格式
2 |
3 | 多媒体要想发声,就得加个喇叭,但是喇叭是靠模拟信号驱动的,所以在喇叭之前需要增加一个数字信号转模拟信号的“解码器”,
4 | 将数字信号转换为模拟信号。
5 |
6 | 其次还需要增加一个信号放大器,将微弱的电流放大成足以驱动喇叭磁铁产生足够磁力从而震动纸盆的足够强的电流。
7 |
8 | 原始的PCM音频数据也是非常大的数据量,因此也需要对其进行压缩编码。
9 | 和视频编码一样,音频也有许多的编码格式,如:WAV、MP3、WMA、APE、FLAC等等,音乐发烧友应该对这些格式非常熟悉,特别是后两种无损压缩格式。
10 |
11 | 音频压缩的本质: 消除冗余数据。
12 |
13 | 人耳能察觉到的声音信号频率范围为20Hz ~ 20KHz,在这个频率范围以外的音频信号属于冗余信号。
14 | - 去除人耳听觉频率范围临界附近的值
15 | - 大声音附近如果有小的声音可以去除
16 | - 时域屏蔽效应
17 | - 高声附近50ms内如果声音比较小可以去掉
18 | - 无损压缩
19 |
20 |
21 |
22 | ### WAV
23 |
24 | WAV是微软公司开发的一种声音文件格式,也叫波形声音文件,是最早的数字音频格式,被Windows平台及其应用程序广泛支持,但压缩率比较低。WAV编码是在PCM数据格式的前面加上44B的头部,分别用来描述PCM的采样率、声道数、数据格式等信息。WAV编码的特点是音质非常好、大量软件支持。一般应用于多媒体开发的中间文件、保存音乐和音效素材等。
25 |
26 | WAV编码是在PCM数据格式的前面加上44字节,分别用来描述PCM的采样率、声道数、数据根式等信息。
27 | 特点: 音质非常好、大量软件都支持。
28 | 使用场景:多媒体开发的中间文件、保存音乐和音效素材等。
29 |
30 | ### MP3
31 |
32 | MP3全称是MPEG-1 Audio Layer 3,它在1992年合并至MPEG规范中。MP3能够以高音质、低采样率对数字音频文件进行压缩,应用最普遍。MP3具有不错的压缩比,使用LAME编码的中高码率的MP3文件,在听感上非常接近源WAV文件。其特点是音质在128kb/s以上表现还不错,压缩比比较高,兼容性好。主要应用于在高比特率下对兼容性有要求的音乐欣赏方面。
33 | MP3具有不错的压缩比,使用LAME编码的中高码率的MP3文件,听感上非常接近源WAV文件。
34 | 特点:音质在128Kbps以上表现还不错,压缩比比较高,兼容性好。
35 | 使用场景:高比特率下对兼容性有要求的音乐欣赏。
36 |
37 | ### MP3Pro
38 |
39 | MP3Pro是由瑞典Coding科技公司开发的,其中包含了两大技术,一是来自于Coding科技公司所特有的解码技术,二是由MP3的专利持有者法国汤姆森多媒体公司和德国Fraunhofer集成电路协会共同研究的一项译码技术。MP3Pro可以在基本不改变文件大小的情况下改善原先的MP3音乐音质。它能够在用较低的比特率压缩音频文件的情况下,最大程度地保持压缩前的音质。
40 |
41 | ### AAC
42 | 高级音频编码(Advanced Audio Coding,AAC)是由Fraunhofer IIS-A、杜比和AT&T共同开发的一种音频格式,它是MPEG-2规范的一部分。AAC所采用的运算法则与MP3的运算法则有所不同,AAC通过结合其他的功能来提高编码效率。AAC的音频算法在压缩能力上远远超过了以前的一些压缩算法,如MP3。它还同时支持多达48个音轨、15个低频音轨、更多种采样率和比特率、多种语言的兼容能力、更高的解码效率。总之,AAC可以在比MP3文件缩小30%的前提下提供更好的音质。
43 | AAC是新一代的音频有损压缩技术,它通过一些附加编码技术(如PS和SBR)衍生出LC-AAC、HE-AAC、HE-AAC V2这3种主要编码格式。在小于128kb/s码率下表现优异,且多用于视频中的音频编码。在128kb/s码率下的音频编码,多用于视频中的音频轨的编码。
44 |
45 | AAC是新一代的有损压缩技术,它通过一些附加编码技术(如PS、SBR)等,衍生出LC-AAC、HE-AAC、HE-AAC V2三种主要编码格式。
46 |
47 | 特点:在小于128kbps码率下表现优异,且多用于视频中的音频编码。
48 | 使用场景:128Kbps码率下的音频编码,多用于视频中音频轨的编码。
49 |
50 | ### Ogg
51 |
52 | Ogg Vorbis是一种新的音频压缩格式,类似于MP3等现有的音乐格式,但有一点不同的是,它是完全免费、开放和没有专利限制的。Vorbis是这种音频压缩机制的名字,而Ogg则是一个计划的名字,该计划意图设计一个完全开放性的多媒体系统。Vorbis也是有损压缩,但通过使用更加先进的声学模型以减少损失,因此,同样位速率编码的Ogg与MP3相比听起来更好一些。Ogg编码音质好、完全免费,可以用更小的码率达到更好的音质,128kb/s的Ogg比192kb/s甚至更高的MP3还要出色,但是目前在媒体软件支持上还是不够友好。在高、中、低码率下都有良好的表现,但兼容性不够好,流媒体特性不支持。
53 |
54 | Ogg编码音质好、完全免费。
55 | 可以用更小的码率达到更好的音质,128Kbps的Ogg比192Kbps甚至更高的MP3还要出色。但是目前媒体软件支持上还不够友好。
56 | 特点:高中低码率下都有良好的表现,兼容性不够好,流媒体特性不支持。
57 | 使用场景:语音聊天的音频消息场景。
58 |
59 |
60 | ### APE
61 |
62 | APE是一种无损压缩音频格式,在音质不降低的前提下,可以压缩到传统无损格式WAV文件的一半。简单来讲,APE压缩与WinZip或WinRAR这类专业数据压缩软件压缩原理类似,只是APE等无损压缩数字音乐之后的APE音频文件是可以直接被播放的。APE的压缩速率是动态的,压缩时只压缩可被压缩的部分,不能被压缩的部分还是会被保留下来。
63 |
--------------------------------------------------------------------------------