├── .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 | ![Image](https://github.com/CharonChui/Pictures/blob/master/library_r.error.png?raw=true) 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 | ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/use_shortcut.gif?raw=true) 15 | 16 | 17 | - 自动导入 18 | 经常听到同事抱怨,`Studio`怎么没有`Eclipse`那种批量导包啊,那么多类要到,费劲了。其实不用一个个导的。 19 | 使用`Command+Shift+A(Windows或Linux是Ctrl+Shift+A)`快速的查找设置命令。我们输入`auto import`后将`Add unambiguous imports on fly`选项开启就好了,很爽有木有?你的是不是也没开啊? 20 | ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/auto_import.gif?raw=true) 21 | 你可以快速打开一些设置,例如你想在`finder`中查看该文件,直接输入`find`就好了。 22 | ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/action_find.png?raw=true) 23 | 24 | - 在自动完成代码时使用`Tab`键来替换已存在的方法和参数。 25 | 经常如我们想要修改一个已经存在的方法时,我们移动到对象的`.`的提示区域,如果使用`Command+Space`来补充代码选择后按`enter`的话,会把之前已经存在的代码往后移动,这样还要再去删除,很不方便。但是如果我们直接使用`Tag`键,那就会直接替换当前已经存在的方法。 26 | ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/tab_tip.gif?raw=true) 27 | 28 | - 内容选择技巧 29 | 使用`Control+↑或↓`能在方法间移动。使用`Shift+↑或↓`来选择至上一行或者至下一行代码的代码?那么使用`option++↑或↓(Windows或Linux是alt++↑或↓)`呢?它能选择或者取消选择和你鼠标所在位置的代码区域。同时使用`option+shift++↑或↓(Windows或Linux是alt+shift++↑或↓)可以交换选中代码块与上一行的位置。这样我们就不需要剪切和粘贴了。 30 | 31 | - 使用模板补全代码 32 | 你可以在代码后加用后缀的方式补充代码块,也可以用`Command+J(Windows是Ctrl+J)`来提示。 33 | ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/command_j.gif?raw=true) 34 | 对于一些更多的模式,在代码自动完成时也支持生成对应的模板,例如使用`Toast`的快捷键可以很方便的生成一个新的`toast`对象,你只需要指定文字就可以了。 35 | ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/toast_autocomp.gif?raw=true) 36 | 用`Command+Shift+A`然后输入`live template`然后打开对应的页面,可以看到目前已经存在的模板。当然你也可以添加新的模板。 37 | ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/live_templete.png?raw=true) 38 | 39 | - 在`Evaluating Expressions`中为`Object`对象指定显示内容 40 | 如果我们在`debug`的时候查看断点处的变量值或者`evaluating expressions`,你会发现`objects`会显示他们的`toString()`值。如果你的变量是`String`类型或者基础类型那不会有问题,但是大多数其他对象,这样没有什么意义。 41 | 尤其是在集合对象时,你看的是一个`CallsName:HashValue`的列表。而为了需要看清数据,我们需要知道每个对象的内容。 42 | 你当然可以去对每个对象类型指定索要显示的内容。在对应的对象上邮件`View as`然后创建你想要显示的内容就可以了。 43 | ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/debug_eva.gif?raw=true) 44 | 45 | - 结构性的搜索、替换、和检查 46 | `Command+shift+A`然后输入`structural `后选择`search structurally`或者`replace structurally`,然后可以对应的结构性搜索的模板,完成之后所有符合该模板的代码都会提示`warning`。 47 | ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/structural_search.gif?raw=true) 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 | ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/studio_ndk_jni.png?raw=true) 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 | ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/studio_ndk_build.png?raw=true) 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 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_1.png?raw=true) 12 | 进入下载页后下载`Apache Subversion command line tools`, 解压即可。 13 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_2.png?raw=true) 14 | 15 | - 方法二 16 | `Windows`下的`Tortoise SVN`也是带有`command line`的,但是安装的时候默认是不安装这个选项的,所以安装时要注意选择一下。 17 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_5.png?raw=true) 18 | 选择安装即可 19 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_6.png?raw=true) 20 | 21 | - 配置`SVN` 22 | 进入设置中心,搜索`Version Control`后选择`Subversion`, 将右侧的`Use command line client`设置你本地的`command line`路径即可。 23 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_3.png?raw=true) 24 | 如果是用第二种方式安装`Tortoise SVN`的话地址就是: 25 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_4.png?raw=true) 26 | PS: 设置成自己的路径啊,不要写我的... 27 | 28 | - `Git` 29 | 安装`Git`, [Git](http://git-scm.com/) 30 | 选择相应系统版本安装即可。 31 | 安装完成后进入`Android Studio`设置中心-> `Version Control` -> `Git`设置`Path to Git executable`即可。 32 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_7.png?raw=true) 33 | 34 | - `Github` 35 | 怎么能少了它呢?哈哈 36 | 这个就不用安装了,直接配置下用户名和密码就好了。 37 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_8.png?raw=true) 38 | 39 | - `Checkout` 40 | 在工具栏中点击 `VCS` 能看到相应检出和导入功能。这里以`Github`检出为例介绍一下。 41 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_9.png?raw=true) 42 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_10.png?raw=true) 43 | 确定之后就能在底部导航栏看到检出进度 44 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_11.png?raw=true) 45 | 46 | 完成之后会提示是否想要把刚才检出的项目导入`AndroidStudio`中。 47 | Why not? 48 | 以`Gradle`方式导入, 然后`next`, 然后`next`然后就没有然后了。 49 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_12.png?raw=true) 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 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_1.png?raw=true) 33 | 效果如下图: 34 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_2.png?raw=true) 35 | 36 | - 使用布局编辑器 37 | 38 | 布局编辑器的适时和多屏幕预览的确是一个亮点。 39 | 40 | - 点击布局页面右侧的`Preview`按钮,可以进行预览。 41 | 42 | 想预览多屏幕效果时可以在预览界面设备的下拉菜单上选择`Preview All Screen Sizes`. 43 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_3.png?raw=true) 44 | 45 | - 选择主题 46 | 47 | 想给应用设置一个主题,可以点击`Theme`图标![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_4.png?raw=true), 48 | 就会显示出选择对话框。 49 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_5.png?raw=true) 50 | 51 | - 国际化 52 | 对于国际化的适配选择国际化图标![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_6.png?raw=true), 53 | 然后在弹出的列表中选择需要进行国际化的国家进行适配即可。 54 | 55 | - 常用功能 56 | 有些人进来之后可能找不到`DDMS`了. 57 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_7.png?raw=true) 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`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_1.png?raw=true)图标。 10 | - 在接下来的选择设备窗口选择相应的设备或创建虚拟机, 点击`OK`即可。 11 | `Android Studio`在`debug`时会打开`Debug`工具栏, 可以点击`Debug`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_2.png?raw=true)图标打开`Debug`窗口。 12 | 13 | ###设置断点 14 | 与`Eclipse`十分相似, 在代码左侧位置点击一下即可, 圆点的颜色变了。 15 | 16 | ###Attach the debugger to a running process 17 | 在`debug`时不用每次都去重启应用程序。 我们可以对正在运行的程序进行`debug`: 18 | - 点击`Attach debugger to Android proccess`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_3.png?raw=true)图标。 19 | - 设备选择窗口选择想要`debug`的设备。 20 | - 点击`Debug`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_2.png?raw=true)图标打开`Debug`工具栏。 21 | - 在`Debug`工具栏中有`Stop Over`, `Stop Into`等图标和快捷键,这些就不仔细说明了, 和`Eclipse`都差不多。 22 | 23 | ### View the system log 24 | 在`Android DDMS`中和`Debug`工具栏中都可以查看系统`log`日志, 25 | - 在窗口底部栏点击`Android`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_4.png?raw=true) 图标打开`Android DDMS`工具栏。 26 | - 如果此时`Logcat`窗口中的日志是空得,点击`Restart`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_5.png?raw=true)图标。 27 | - 如果想要只显示当前某个进程的信息点击`Only Show Logcat from Selected Process`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_6.png?raw=true). 如果当时设备窗口不可见,点击右上角的`Restore Devices View`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_7.png?raw=true)图标,该图标只有设备窗口不可见时才会显示。 28 | 29 | 删除`Project`及`Module` 30 | --- 31 | 32 | 很多人都在问`AndroidStudio`中如何删除`Project`,如何删除`Module`?怎么和`Eclipse`不同啊,找不到`delete`或`remove`选项。 33 | - 删除`Project` 34 | 点击左侧`File`-->`Close project`,关闭当前工程, 然后直接找到工程所在本地文件进行删除(慎重啊), 删除完之后点击最近列表中的该项目就会提示不存在,我们把他从最近项目中移除即可.![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_11.png?raw=true)你会发现,点击`remove`之后没效果,以后估计会解决。 35 | - 删除`Module` 36 | 在该`Module`邮件选择`Open Module Settings`。 37 | ![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_8.png?raw=true) 38 | 进入设置页后选中要删除的`Module`点击左上角的删除图标`-`后点击确定。 39 | ![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_9.png?raw=true) 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 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/uml_demo.png?raw=true) 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 | ![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/oom.png) 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`可以看到`