├── 1-计算机基础相关知识.md ├── 2-ANDROID JAVA知识.md ├── 3-ANDROID 四大组件.md ├── 4-ANDROID 其他知识.md ├── README.md ├── ReadingNotes-AndroidHeros ├── 2015-11-25-Android-Heros-Reading-Notes-1.md ├── 2015-11-26-Android-Heros-Reading-Notes-2.md ├── 2015-11-27-Android-Heros-Reading-Notes-3.md ├── 2015-11-28-Android-Heros-Reading-Notes-4.md ├── 2015-11-29-Android-Heroes-Reading-Notes-5.md └── 2015-11-29-Android-Heroes-Reading-Notes.md ├── ReadingNotes-ArtOfAndroidDevelopment ├── 2015-11-29-Art-of-Android-Development-Reading-Notes-1.md ├── 2015-11-30-Art-of-Android-Development-Reading-Notes-2.md ├── 2015-12-01-Art-of-Android-Development-Reading-Notes-3.md ├── 2015-12-02-Art-of-Android-Development-Reading-Notes-4.md ├── 2015-12-03-Art-of-Android-Development-Reading-Notes-5.md ├── 2015-12-04-Art-of-Android-Development-Reading-Notes-6.md ├── 2015-12-04-Art-of-Android-Development-Reading-Notes-7.md ├── 2015-12-04-Art-of-Android-Development-Reading-Notes-8.md ├── 2015-12-04-Art-of-Android-Development-Reading-Notes-9.md ├── 2015-12-05-Art-of-Android-Development-Reading-Notes-10.md ├── 2015-12-05-Art-of-Android-Development-Reading-Notes-11.md ├── 2015-12-05-Art-of-Android-Development-Reading-Notes-12.md ├── 2015-12-05-Art-of-Android-Development-Reading-Notes-13.md └── 2015-12-06-Art-of-Android-Development-Reading-Notes.md ├── ReadingNotes-PythonAlgorithms ├── 2014-05-07-python-algorithms-search.markdown ├── 2014-05-07-python-algorithms-sort.markdown ├── 2014-05-08-python-algorithms-Trees.markdown ├── 2014-05-08-python-algorithms-datastructures.markdown ├── 2014-07-01-python-algorithms-counting-101.markdown ├── 2014-07-01-python-algorithms-divide-and-combine-and-conquer.markdown ├── 2014-07-01-python-algorithms-dynamic-programming.markdown ├── 2014-07-01-python-algorithms-graphs.markdown ├── 2014-07-01-python-algorithms-greedy.markdown ├── 2014-07-01-python-algorithms-induction.markdown ├── 2014-07-01-python-algorithms-introduction.markdown ├── 2014-07-01-python-algorithms-the-basics.markdown └── 2014-07-01-python-algorithms-traversal.markdown ├── images ├── contentprovider_ashmem.png ├── http_cache.png ├── https_connection.png ├── resource_path.png ├── service_lifecycle.png ├── tcp_connection.png └── tcp_open_close.jpg └── pdfs ├── 1-计算机基础相关知识.pdf ├── 2-ANDROID JAVA知识.pdf ├── 3-ANDROID 四大组件.pdf ├── 4-ANDROID 其他知识.pdf ├── leetcode-cpp.pdf └── leetcode-java.pdf /2-ANDROID JAVA知识.md: -------------------------------------------------------------------------------- 1 | **多线程** 2 | 3 | **Callable和Runnable** 4 | 5 | 参考资料 6 | [Java并发编程:Callable、Future和FutureTask](http://www.cnblogs.com/dolphin0520/p/3949310.html) 7 | 8 | **ReentrantLock** 9 | 10 | **BlockingQueue** 11 | 12 | BlockingQueue的特性: 13 | 当队列是空的时,从队列中获取或删除元素的操作将会被阻塞; 14 | 当队列是满时,往队列里添加元素的操作会被阻塞。 15 | 16 | 阻塞队列不接受空值,当你尝试向队列中添加空值的时候,它会抛出NullPointerException。 17 | 阻塞队列的实现都是线程安全的,所有的查询方法都是原子的并且使用了内部锁或者其他形式的并发控制。 18 | BlockingQueue接口是java collections框架的一部分,它主要用于实现生产者-消费者问题。 19 | 20 | **final、static、static final修饰的字段赋值的区别:** 21 | 22 | static修饰的字段在类加载过程中的准备阶段被初始化为0或null等默认值,而后在初始化阶段(触发类构造器)才会被赋予代码中设定的值,如果没有设定值,那么它的值就为默认值; 23 | final修饰的字段在运行时被初始化(可以直接赋值,也可以在实例构造器中赋值),一旦赋值便不可更改; 24 | static final修饰的字段在Javac时生成ConstantValue属性,在类加载的准备阶段根据ConstantValue的值为该字段赋值,它没有默认值,必须显式地赋值,否则Javac时会报错。可以理解为在编译期即把结果放入了常量池中。 25 | 26 | **HashMap** 27 | 28 | 参考资料 29 | [深入理解HashMap](http://tech.meituan.com/java-hashmap.html) 30 | 31 | **Java虚拟机** 32 | 33 | 参考资料 34 | [深入Java虚拟机系列](http://www.importnew.com/19946.html) 35 | [深入探讨Java类加载器](https://www.ibm.com/developerworks/cn/java/j-lo-classloader/) 36 | [插件化中 Classloader 的加载 dex 分析](http://solart.cc/2016/11/16/plugin_classloader/) 37 | 38 | **重要的类和代码** 39 | 40 | LRU Cache的实现 41 | 42 | ```java 43 | public class LRUCache { 44 | private class Node{ 45 | Node prev; 46 | Node next; 47 | int key; 48 | int value; 49 | 50 | public Node(int key, int value) { 51 | this.key = key; 52 | this.value = value; 53 | this.prev = null; 54 | this.next = null; 55 | } 56 | } 57 | 58 | private int capacity; 59 | private HashMap hs = new HashMap(); 60 | private Node head = new Node(-1, -1); 61 | private Node tail = new Node(-1, -1); 62 | 63 | public LRUCache(int capacity) { 64 | this.capacity = capacity; 65 | tail.prev = head; 66 | head.next = tail; 67 | } 68 | 69 | public int get(int key) { 70 | if( !hs.containsKey(key)) { 71 | return -1; 72 | } 73 | 74 | // remove current 75 | Node current = hs.get(key); 76 | current.prev.next = current.next; 77 | current.next.prev = current.prev; 78 | 79 | // move current to tail 80 | move_to_tail(current); 81 | 82 | return hs.get(key).value; 83 | } 84 | 85 | public void set(int key, int value) { 86 | if( get(key) != -1) { 87 | hs.get(key).value = value; 88 | return; 89 | } 90 | 91 | if (hs.size() == capacity) { 92 | hs.remove(head.next.key); 93 | head.next = head.next.next; 94 | head.next.prev = head; 95 | } 96 | 97 | Node insert = new Node(key, value); 98 | hs.put(key, insert); 99 | move_to_tail(insert); 100 | } 101 | 102 | private void move_to_tail(Node current) { 103 | current.prev = tail.prev; 104 | tail.prev = current; 105 | current.prev.next = current; 106 | current.next = tail; 107 | } 108 | } 109 | ``` 110 | 111 | -------------------------------------------------------------------------------- /4-ANDROID 其他知识.md: -------------------------------------------------------------------------------- 1 | #### Handler机制 2 | **Message**:消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息; 3 | **MessageQueue**:消息队列的主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next); 4 | **Handler**:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage); 5 | **Looper**:不断循环执行(Looper.loop),按分发机制将消息分发给目标处理者。 6 | 7 | Handler容易引起内存泄露的原因:非静态内部类和匿名内部类都持有对外部类的引用。 8 | [http://droidyue.com/blog/2014/12/28/in-android-handler-classes-should-be-static-or-leaks-might-occur/](http://droidyue.com/blog/2014/12/28/in-android-handler-classes-should-be-static-or-leaks-might-occur/) 9 | 10 | 如何避免Handler引起的内存泄露?使用静态内部类和弱引用来解决这个问题。 11 | ```java 12 | private static class MyHandler extends Handler { 13 | private final WeakReference mActivity; 14 | 15 | public MyHandler(SampleActivity activity) { 16 | mActivity = new WeakReference(activity); 17 | } 18 | 19 | @Override 20 | public void handleMessage(Message msg) { 21 | SampleActivity activity = mActivity.get(); 22 | if (activity != null) { 23 | // ... 24 | } 25 | } 26 | } 27 | 28 | private final MyHandler mHandler = new MyHandler(this); 29 | ``` 30 | 31 | #### Parcelable 32 | Serializable和Parcelable的区别 33 | **Serializalbe会使用反射(用于创建对象),序列化和反序列化过程需要大量I/O操作。Parcelable自已实现封送和解封(marshalled &unmarshalled)操作不需要用反射,数据也存放在Native内存中,效率要快很多。** 34 | 35 | Parcelable和Parcel的区别 36 | **Parcelable 接口定义在封送/解封送过程中混合和分解对象的契约,Parcelable接口的底层是Parcel容器对象。Parcel类是一种快速的序列化/反序列化机制,专为Android中的进程间通信而设计。该类提供了一些方法来将成员容纳到容器中,以及从容器展开成员。** 37 | 38 | Activity之间传递参数时要注意对象的大小,Intent中的Bundle是在使用Binder机制进行数据传递的,能使用的Binder的缓冲区是有大小限制的(有些手机是2M),而一个进程默认有16个binder线程,所以一个线程能占用的缓冲区就更小了(以前做过测试,大约一个线程可以占用128KB),所以当看到“The Binder transaction failed because it was too large.” 这类TransactionTooLargeException异常时就说明传递的参数太大了,需要精简。使用Intent在Activity之间传递List和Bitmap对象是有风险的。 39 | 40 | 参考资料 41 | [http://www.jianshu.com/p/be593134eeae](http://www.jianshu.com/p/be593134eeae) 42 | 43 | #### ListView 44 | 45 | **ListView的优化** 46 | 问:getView的执行时间应该控制在多少以内? 47 | 答:1秒之内屏幕可以完成30帧的绘制,这样人看起来会觉得比较流畅(苹果手机是接近60帧,高于60之后人眼也无法分辨)。每帧可使用的时间:1000ms/30 = 33.33 ms 48 | 每个ListView一般要显示6个ListItem,加上1个重用convertView:33.33ms/7 = 4.76ms 49 | 即是说,每个getView要在4.76ms内完成工作才会较流畅,但是事实上,每个getView间的调用也会有一定的间隔(有可能是由于handler在处理别的消息),UI的handler处理不好的话,这个间隔也可能会很大(0ms-200ms)。结论就是,留给getView使用的时间应该在4ms之内,如果不能控制在这之内的话,ListView的滑动就会有卡顿的现象。 50 | 51 | 优化建议: 52 | 1.重用ConvertView (避免每次都创建ItemView); 53 | 2.使用View Holder模式 (避免每次都inflate View和findViewById); 54 | 3.使用异步线程加载图片(一般都是直接使用图片库加载,如Glide, Picasso); 55 | 4.在adapter的getView方法中尽可能的减少逻辑判断,特别是耗时的判断; 56 | 5.避免GC(可以从logcat中查看有无GC的LOG); 57 | 6.在快速滑动时不要加载图片; 58 | 7.将ListView的scrollingCache和animateCache这两个属性设置为false(默认是true); 59 | 8.尽可能减少List Item的Layout层次(如可以使用RelativeLayout替换LinearLayout,或使用自定的View代替组合嵌套使用的Layout); 60 | 9.使用硬件加速来解决莫名的卡顿问题,给Activity添加配置`android:hardwareAccelerated="true"`; 61 | 62 | 关于第6点快速滑动时不要加载图片的优化实现原理: 63 | 在适配器中声明一个全局的boolean变量用来保存此刻是否在滚动,然后通过给ListView设置滚动监听,然后在滚动监听器的onScrollStateChanged()方法中给boolean值赋值,看是否在滚动。 64 | 这样在我们使用这个适配器的时候,就可以根据滚动状态的不同来判断:比如正在滚动的时候就只显示内存缓存的图片,如果内存缓存中没有就显示一张默认图片;而如果没有在滚动就采用正常的图片加载方案去加载网络或者缓存中的图片。 65 | 66 | 关于第7点设置在某些型号的手机上效果比较明显 67 | android:scrollingCache="false" 68 | android:animationCache="false" 69 | 70 | 参考资料 71 | [http://www.jianshu.com/p/8fd5fa90ee6c](http://www.jianshu.com/p/8fd5fa90ee6c) 72 | [http://www.kymjs.com/code/2015/04/28/01/](http://www.kymjs.com/code/2015/04/28/01/) 73 | 74 | #### SharedPreferences 75 | 76 | **commit和apply的区别** 77 | commit是同步的提交,这种方式很常用,在比较早的SDK版本中就有了。这种提交方式会阻塞调用它的线程,并且这个方法会返回boolean值告知保存是否成功(如果不成功,可以做一些补救措施)。 78 | apply是异步的提交方式,目前Android Studio也会提示大家使用这种方式。 79 | 80 | **注册监听器监听键值变化** 81 | SharedPreferences提供了registerOnSharedPreferenceChangeListener方法来注册监听器 82 | 83 | **多进程操作和读取SharedPreferences** 84 | 在Android 3.0及以上版本,可以通过`Context.MODE_MULTI_PROCESS`属性来实现SharedPreferences多进程共享,但是6.0的版本将这个MODE_MULTI_PROCESS标识为deprecated,因为它容易出现问题。 85 | 86 | SharedPreferences的多进程共享数据的替代品[https://github.com/grandcentrix/tray](https://github.com/grandcentrix/tray) 87 | 88 | #### SQLite 89 | 90 | SQLite每个数据库都是以单个文件(.db)的形式存在,这些数据都是以B-Tree的数据结构形式存储在磁盘上。 91 | 92 | 使用SQLiteDatabase的insert,delete等方法或者execSQL方法默认都开启了事务,如果操作顺利完成才会更新.db数据库。事务的实现是依赖于名为rollback journal文件,借助这个临时文件来完成原子操作和回滚功能。在/data/data//databases/目录下看到一个和数据库同名的.db-journal文件。 93 | 94 | 问:如何对SQLite数据库中进行大量的数据插入? 95 | 答:可以通过“**SQLiteStatement+事务**”的方式来提高效率。 96 | 97 | 查询方面的优化一般可以通过建立索引。建立索引会对插入和更新的操作性能产生影响,使用索引需要考虑实际情况进行利弊权衡,对于查询操作量级较大,业务对要求查询要求较高的,还是推荐使用索引。 98 | 99 | SQLite的同步锁精确到数据库级,粒度比较大,不像别的数据库有表锁,行锁。同一个时间只允许一个连接进行写入操作。 100 | 101 | #### Resources 102 | 103 | **资源的查找顺序** 104 | 在查找时会先去掉有冲突的资源目录,然后再按MCC、MNC、语言等指定的优先级进行查找,直到确认一个匹配资源。**根据屏幕尺寸限定符选择资源时,如果没有更好的匹配资源,则系统将使用专为小于当前屏幕的屏幕而设计的资源。** 105 | 106 | MCC 移动国家码,由3为数字组成;MNC 移动网络码,由2位数字组成 ? 107 | ![img](images/resource_path.png) 108 | 109 | **图片的放置位置问题** 110 | 如果能提供各种dpi的对应资源那是最好,可以达到较好内存使用效果。**如果提供的图片资源有限,那么图片资源应该尽量放在**高密度文件夹**下,这样可以节省图片的内存开支。** 111 | 112 | **res/raw 和 assets 的区别** 113 | 这两个目录下的文件都会被打包进APK,并且不经过任何的压缩处理。 114 | assets与res/raw不同点在于,assets支持任意深度的子目录,这些文件不会生成任何资源ID,只能使用AssetManager按相对的路径读取文件。如需访问原始文件名和文件层次结构,则可以考虑将某些资源保存在assets目录下。 115 | 116 | **drawable-nodpi** 117 | **drawable-nodpi文件夹是一个密度无关的文件夹,放在这里的图片系统就不会对它进行自动缩放,原图片是多大就会实际展示多大。**但是要注意一个加载的顺序,**drawable-nodpi文件夹是在匹配密度文件夹和更高密度文件夹都找不到的情况下才会去这里查找图片的,因此放在drawable-nodpi文件夹里的图片通常情况下不建议再放到别的文件夹里面。** 118 | 119 | **mipmap 和 drawable 的区别** 120 | **Android对放在mipmap目录的图标会忽略屏幕密度,会去尽量匹配大一点的,然后系统自动对图片进行缩放,从而优化显示和节省资源。** 121 | drawable文件夹是存放一些xml(如selector)和图片,Android会根据设备的屏幕密度(density)自动去对应的drawable文件夹查找匹配的资源文件。 122 | 123 | **Resources.updateConfiguration** 124 | 125 | #### Bitmap 126 | 127 | 推荐阅读[Android官方培训课程中文版](http://hukai.me/android-training-course-in-chinese/index.html)中的Android图像和动画这章的`高效显示Bitmap`这节。 128 | 129 | **Bitmap 和 Drawable** 130 | Bitmap代表的是图片资源在内存中的数据结构,如它的像素数据、长宽等属性都存放在Bitmap对象中。**Bitmap类的构造函数是私有的,只能是通过JNI实例化,系统提供BitmapFactory工厂类从File、Stream和byte[]创建Bitmap的方式**。 131 | 132 | Drawable官方文档说明为可绘制物件的一般抽象。View也是可以绘制的,但Drawable与View不同,**Drawable不接受事件,无法与用户进行交互**。我们知道很多UI控件都提供设置Drawable的接口,如ImageView可以通过setImageDrawable(Drawable drawable)设置它的显示,这个drawable可以是来自Bitmap的BitmapDrawable,也可以是其他的如ShapeDrawable。 133 | 134 | **Drawable是一种抽像,最终实现的方式可以是绘制Bitmap的数据或者图形、Color数据等。** 135 | 136 | **Bitmap优化** 137 | 在Android 3.0之前的版本,Bitmap像素数据存放在Native内存中,而且Nativie内存的释放是不确定的,容易内存溢出而Crash,所以一般我们不使用的图片要调用recycle()。 138 | 从3.0开始,Bitmap像素数据和Bitmap对象一起存放在Dalvik堆内存中(从源代码上看是多了一个byte[] buffer用来存放数据),也就是我们常说的Java Heap内存。 139 | 除了这点改变之外,**3.0版本的还增加了一个inBitmap属性(BitmapFactory.Options.inBitmap)。如果设置了这个属性则会重用这个Bitmap的内存从而提升性能。但是这个重用是有条件的,在Android4.4之前只能重用相同大小的Bitmap,Android4.4+则只要比重用Bitmap小即可。** 140 | 141 | 其他优化手段: 142 | 1.使用采样率(inSampleSize),如果最终要压缩图片,如显示缩略图,我们并不需要加载完整的图片数据,只需要按一定的比例加载即可; 143 | 2.使用Matrix变形等,比如使用Matrix进行放大,虽然图像大了,但并没有占用更多的内存。 144 | 145 | #### AsyncTask 146 | 147 | AnsycTask执行任务时,内部会创建一个**进程作用域的线程池**来管理要运行的任务,也就在调用了AsyncTask.execute()之后,AsyncTask会把任务交给线程池,由线程池来管理创建Thread和运行Therad。最后和UI打交道就交给Handler去处理了。 148 | 149 | 问:线程池可以同时执行多少个TASK? 150 | 答:Android 3.0之前(1.6之前的版本不再关注)规定线程池的核心线程数为5个(corePoolSize),线程池总大小为128(maximumPoolSize),还有一个缓冲队列(sWorkQueue,缓冲队列可以放10个任务),当我们尝试去添加第139个任务时,程序就会崩溃。**当线程池中的数量大于corePoolSize,缓冲队列已满,并且线程池中的数量小于maximumPoolSize,将会创建新的线程来处理被添加的任务。** (不放在线程池中管理?) 151 | 152 | 问:多个AsyncTask任务是串行还是并行? 153 | 答:从Android 1.6到2.3(Gingerbread) AsyncTask是并行的,即上面我们提到的有5个核心线程的线程池(ThreadPoolExecutor)负责调度任务。从Android 3.0开始,Android团队又把AsyncTask改成了串行,默认的Executor被指定为SERIAL_EXECUTOR。 154 | 155 | **AsyncTask容易引发的Activity内存泄露** 156 | 如果AsyncTask被声明为Activity的非静态内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。 157 | 158 | 参考资料 159 | [http://www.jianshu.com/p/c925b3ea1444](http://www.jianshu.com/p/c925b3ea1444) 160 | 161 | #### SurfaceView 162 | 163 | **SurfaceView与View的区别** 164 | (1)View主要适用于主动更新的情况下,而SurfaceView主要适用于被动更新,例如频繁地刷新; 165 | (2)View在主线程中对画面进行刷新,而SurfaceView通常会通过一个子线程来进行页面刷新; 166 | (3)View在绘图时没有使用双缓冲机制,而SurfaceView在底层实现机制中就已经实现了双缓冲机制。 167 | 168 | #### Android安全机制 169 | 170 | 五道防线: 171 | (1)代码安全机制——代码混淆proguard 172 | (2)应用接入权限机制——AndroidManifest文件权限声明、权限检查机制 173 | 系统检查操作者权限的顺序:首先,判断permission名称,如果为空则直接返回PERMISSION_DENIED;其次,判断uid,如果uid为0或者为System Service的uid,不做权限控制,如果uid与参数中的请求uid不同,那么返回PERMISSION_DENIED;最后,通过调用PackageManagerService.checkUidPermission方法判断该uid是否具有相应的权限,该方法会去xml的权限列表和系统级的platform.xml中进行查找。 174 | (3)应用签名机制——数字证书:系统不会安装没有签名的app,只有拥有相同数字签名的app才会在升级时被认为是同一个app。 175 | (4)Linux内核层安全机制——Uid、访问权限控制 176 | (5)Android虚拟机沙箱机制——沙箱隔离:每个app运行在单独的虚拟机中,与其他应用完全隔离 177 | 178 | 179 | #### 进程 180 | 181 | 优先级:前台进程,可见进程,服务进程,后台进程,空进程 182 | 进程优先级从-19到19,默认是0,数字越大优先级越低 183 | 184 | oom_adj:值越大越容易被系统杀,系统进程的oom_adj值为负 185 | 查看oom_adj的方式:cat /proc/[pid]/oom_adj 186 | 或者执行 adb dumpsys meminfo 其中包含了 Total PSS by OOM adjustment 187 | 188 | android:process属性 189 | 如果被设置的进程名是以一个冒号开头的,则这个新的进程对于这个应用来说是私有的,当它需要在新进程中运行的时候,这个新进程将会被创建。如果这个进程的名字是以小写字符开头的,则这个服务将运行在一个以这个名字命名的全局的进程中,当然前提是它有相应的权限。这将允许在不同应用中的各种组件可以共享一个进程,从而减少资源的占用。 190 | 191 | Application的onCreate方法被执行多次的原因,以及避免资源多次进行初始化的解决方案 192 | 193 | ```java 194 | public void onCreate() { 195 | super.onCreate(); 196 | 197 | String processName = getProcessName(this, android.os.Process.myPid()); 198 | if (processName != null) { 199 | boolean defaultProcess = processName.equals(PACKAGE_NAME); 200 | if (defaultProcess) { 201 | Log.e(TAG, "application init");//必要的初始化资源操作 202 | } 203 | } 204 | 205 | } 206 | 207 | public String getProcessName(Context cxt, int pid) { 208 | ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE); 209 | List runningApps = am.getRunningAppProcesses(); 210 | if (runningApps == null) { 211 | return null; 212 | } 213 | for (RunningAppProcessInfo procInfo : runningApps) { 214 | if (procInfo.pid == pid) { 215 | return procInfo.processName; 216 | } 217 | } 218 | return null; 219 | } 220 | ``` 221 | 222 | [http://www.android100.org/html/201506/20/156136.html](http://www.android100.org/html/201506/20/156136.html) 223 | 224 | 提高进程优先级的方法: 225 | ①进程要运行一个组件,不要是空进程; 226 | ②运行一个service,并设置为前台运行方式(调用startForground); 227 | 228 | ```java 229 | private void keepAlive() { 230 | try { 231 | Notification notification = new Notification(); 232 | notification.flags |= Notification.FLAG_NO_CLEAR; 233 | notification.flags |= Notification.FLAG_ONGOING_EVENT; 234 | startForeground(0, notification); // 设置为前台服务避免kill,Android4.3及以上需要设置id为0时通知栏才不显示该通知; 235 | } catch (Throwable e) { 236 | e.printStackTrace(); 237 | } 238 | } 239 | ``` 240 | 241 | ③AndroidManifest.xml中配置persistent属性(persistent的App会被优先照顾,进程优先级设置为**PERSISTENT_PROC_ADJ=-12**) 242 | 243 | 244 | #### 线程 245 | 246 | 线程是CPU调度的基本单元,每个应用都有一个主线程负责处理消息。一个应用启动后,至少会有3个线程,一个主线程(UI线程)和2个Binder线程。Zygote进程(APK所在的进程是由Zygote进程Fork出来的)还会产生有一些Daemon线程如:ReferenceQueueDaemon、FinalizerDaemon、FinalizerWatchdogDaemon、HeapTaskDaemon,从名字大家也可以对它们的用途猜出一二。 247 | 248 | **wait/notify/notifyAll** 249 | wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。 250 | 251 | **wait和sleep/yield的区别** 252 | (1)线程状态变化不同 253 | wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,而yield()是让线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权。 254 | (2)线程是否释放锁 255 | wait()会使线程释放它所持有对象的同步锁,而yield()方法不会释放锁。 256 | 257 | **sleep和wait这两个函数被调用之后线程都应该放弃执行权,不同的是sleep不释放锁而wait的话是释放锁。**直白的意思是一个线程调用sleep之后进入了阻塞状态中的其他阻塞,它的意思就是当sleep状态超时、join等待线程终止或者超时,线程重新转入可运行(runnable)状态。而wait是不同的,在释放执行权之后wait也把锁释放了,进入了线程等待阻塞,它要运行的话还是要和其他的线程去竞争锁,之后才可以获得执行权。 258 | 259 | **CountDownLatch** 260 | CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。CountDownLatch的应用场景是主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。 261 | 262 | 参考资料 263 | [什么时候使用CountDownLatch](http://www.importnew.com/15731.html) 264 | 265 | #### APK打包流程 266 | 267 | 参考资料 268 | [浅析Android打包流程](http://mp.weixin.qq.com/s?__biz=MzI0NjIzNDkwOA==&mid=2247483789&idx=1&sn=6aed8c7907d5bd9c8a5e7f2c2dcdac2e&scene=1&srcid=0831CCuRJsbJNuz1WxU6uUsI#wechat_redirect) 269 | [Android构建过程分析](http://mp.weixin.qq.com/s?__biz=MzI1NjEwMTM4OA==&mid=2651232113&idx=1&sn=02f413999ab0865e23d272e69b9e6196&scene=1&srcid=0831gT4p6M0NFG5HTTeRHTUC#wechat_redirect) 270 | 271 | 272 | **Binder机制** 273 | 274 | [Gityuan在知乎上的回答:为什么 Android 要采用 Binder 作为 IPC 机制](https://www.zhihu.com/question/39440766/answer/89210950) 275 | 276 | 部分摘录: 277 | 1. **管道:**在创建时分配一个page大小的内存,缓存区大小比较有限; 278 | 2. **消息队列**:信息复制两次,额外的CPU消耗;不合适频繁或信息量大的通信; 279 | 3. **共享内存**:无须复制,共享缓冲区直接附加到进程虚拟地址空间,速度快;但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决; 280 | 4. **套接字**:作为更通用的接口,传输效率低,主要用于不同机器或跨网络的通信; 281 | 5. **信号量**:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 282 | 6. **信号**: 不适用于信息交换,更适用于进程中断控制,比如非法内存访问,杀死某个进程等; 283 | 284 | 285 | #### adb 286 | 287 | 参考资料 288 | [Awesome Adb 一份超全超详细的 ADB 用法大全](http://www.jianshu.com/p/e15b02f07ff2) 289 | 290 | #### 开源库学习 291 | 292 | **Volley** 293 | [Volley源码解析](http://codekk.com/open-source-project-analysis/detail/Android/grumoon/Volley%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90) 294 | 295 | 296 | #### 源码学习 297 | 298 | **HandlerThread** 299 | [查看源码](http://androidxref.com/6.0.0_r1/xref/frameworks/base/core/java/android/os/HandlerThread.java) 300 | 301 | **AsyncTask** 302 | [查看源码](http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/os/AsyncTask.java) 303 | 304 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Android开发面试大礼包** 2 | 3 | 2016年年底换工作的重新时候整理了下面试资料,后来又陆陆续续抽空整理了些,目前已经整理的差不多了,希望对正在找工作的小伙伴们有点帮助。   4 | 建议结合下面的Android群英传和Android开发艺术探索这两本书的读书笔记系列一起看,因为读书笔记中涉及到的很多重要知识在这份面试总结中就不再重复介绍了。 5 | 整理的资料大部分都来源于书籍和网络,很多都已经标明了原文出处,若仍有侵权,请通知我,我立即道歉并删除。 6 | 7 | 推荐资料和书籍 8 | 9 | 0. [Android官方培训课程中文版](http://hukai.me/android-training-course-in-chinese/index.html):熟悉Android开发基础知识 10 | 1. Android开发艺术探索:[(在线版读书笔记点这里)](https://javayhu.site/blog) 11 | 2. Android开发进阶-从小工到专家:提高Android开发技能必看 12 | 3. Java程序性能优化:修炼Java的基本功 13 | 4. 剑指Offer:国内的经典面试书 14 | 5. 程序员面试金典:国外的经典面试书 15 | 6. Android面试题一天一题:[@goeasyway 总结的Android面试题](http://www.jianshu.com/users/f9fbc7a39b36) 16 | 17 | 18 | PS:千万记得要去刷[LeetCode](https://leetcode.com/)! 19 | 在此特别感谢戴方勤和唐磊总结的题解(对应`leetcode-cpp.pdf`和`leetcode-java.pdf`两份文档) 20 | PPS:推荐下[九章算法](http://www.jiuzhang.com/),一个不错的在线刷题和算法交流的网站,推荐关注他们的公众号(九章算法) 21 | PPPS:算法主要掌握搜索、排序、递归、分治、回溯、贪心、动规等常见的算法思想,并结合数组、字符串、树等类型的题目进行练习就好。常见数据结构和算法的总结可以考虑阅读我之前总结的[数据结构与算法系列文章 在线版](https://javayhu.site/blog)。 22 | PPPPS:为了方便阅读,我已经将这4份总结转成了排版好的PDF格式的文件。另外,我博客中3本书的阅读笔记也整理好放在相应的文件夹下面啦,这样大家可以下载下来离线阅读(可能存在部分链接失效的问题)。 23 | 24 | **>>> 我在Android面试指南小专栏里面写了一篇稿子[Android面试——算法面试心得](https://xiaozhuanlan.com/topic/1932587460),欢迎阅读!<<<** 25 | 26 | 最后,祝大家找工作一帆风顺! 27 | 28 | 作者: 宅男潇涧 2016年9月 29 | [邮箱: javayhu@gmail.com](mailto:javayhu@gmail.com) 30 | [博客: https://javayhu.site](https://javayhu.site) 31 | [小专栏: https://xiaozhuanlan.com/u/javayhu](https://xiaozhuanlan.com/u/javayhu) 32 | -------------------------------------------------------------------------------- /ReadingNotes-AndroidHeros/2015-11-25-Android-Heros-Reading-Notes-1.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Android Heroes Reading Notes 1 4 | categories: android 5 | date: 2015-11-25 19:17:32 6 | --- 7 | 《Android群英传》读书笔记 (1) 第一章 Android体系与系统架构 + 第二章 Android开发工具新接触 8 | 9 | #### **第一章 Android体系与系统架构** 10 | 11 | 1.Dalvik 和 ART 12 | 在Dalvik中应用好比是一辆可折叠的自行车,平时是折叠的,只有骑的时候,才需要组装起来用。 13 | 在ART中应用好比是一辆组装好了的自行车,装好就可以骑了。 14 | 15 | 2.一个可以查看Android源代码网站:[http://androidxref.com/](http://androidxref.com/) 16 | 17 | 目录结构: 18 | `Makefile` (描述Android各个组件间的联系并指导它们进行自动化编译) 19 | `bionic` (bionic C库) 20 | `bootable` (启动引导相关代码) 21 | `build` (系统编译规则等基础开发包配置) 22 | `cts` (Google兼容性测试标准) 23 | `dalvik` (Dalvik虚拟机) 24 | `development` (应用程序开发相关) 25 | `external` (android使用的一些开源模块) 26 | `frameworks` (Framework框架核心) 27 | `hardware` (厂商硬件适配层HAL) 28 | `out` (编译完成后的代码输出目录) 29 | `packages` (应用程序包) 30 | `prebuilt` (x86和arm架构下预编译资源) 31 | `sdk` (sdk及模拟器) 32 | `system` (底层文件系统库、应用及组件) 33 | `vendor` (厂商定制代码) 34 | 35 | 3.Android系统目录 36 | `/system`和`/data`是开发者特别关心的两个目录 37 | 38 | `/system`目录下主要有:`/app`,`/fonts`,`/framework`,`/lib`,`/media`,`/usr`等子目录 39 | 例如,查看系统的属性信息文件 `/system/build.prop`: 40 | ``` 41 | shell@falcon_umts:/system $ cat build.prop 42 | # begin build properties 43 | # autogenerated by buildinfo.sh 44 | ro.build.id=LMY47M.M003 45 | ro.build.display.id=LMY47M.M003 46 | ro.build.version.incremental=8 47 | ro.build.version.sdk=22 48 | ro.build.version.codename=REL 49 | ro.build.version.all_codenames=REL 50 | ro.build.version.release=5.1 51 | ro.build.date=Wed Aug 19 10:44:57 PDT 2015 52 | ro.build.date.utc=1440006297 53 | ro.build.type=user 54 | ro.build.user=hdsplat 55 | ro.build.host=buildlinux16 56 | ro.build.tags=release-keys 57 | ro.build.flavor=falcon_gpe-user 58 | ro.product.model=XT1032 59 | ro.product.brand=motorola 60 | ro.product.name=falcon_gpe 61 | ro.product.device=falcon_umts 62 | ro.product.board=MSM8226 63 | ...... 64 | ``` 65 | 66 | `/data`目录下主要有`/app`,`/data`,`/system`,`/misc`等子目录,其中`/data/data`是开发者访问最多的目录,这里包含了app的数据信息、文件信息以及数据库信息等,以包名的方式来区别不同的应用。 67 | 68 |
69 | #### **第二章 Android开发工具新接触** 70 | 71 | 1.adb命令的来源 72 | [`/system/core/toolbox`](http://androidxref.com/6.0.1_r10/xref/system/core/toolbox/)和[`/frameworks/base/cmds`](http://androidxref.com/6.0.1_r10/xref/frameworks/base/cmds/)是所有adb命令和shell命令的来源,此处链接的是Android 6.0的源码路径。 73 | 74 | 2.常用的android命令 75 | `android list avds` 列出所有创建的android模拟器 76 | `android list targets` 列出我们所有的SDK可用版本 77 | ``` 78 | hujiawei-MBPR:hexoblog hujiawei$ android list targets 79 | Available Android targets: 80 | ---------- 81 | id: 1 or "android-8" 82 | Name: Android 2.2 83 | Type: Platform 84 | API level: 8 85 | Revision: 3 86 | Skins: HVGA, QVGA, WQVGA400, WQVGA432, WVGA800 (default), WVGA854 87 | Tag/ABIs : default/armeabi 88 | ---------- 89 | id: 2 or "android-10" 90 | Name: Android 2.3.3 91 | Type: Platform 92 | API level: 10 93 | Revision: 2 94 | Skins: HVGA, QVGA, WQVGA400, WQVGA432, WVGA800 (default), WVGA854 95 | Tag/ABIs : default/armeabi 96 | ---------- 97 | id: 3 or "android-15" 98 | Name: Android 4.0.3 99 | Type: Platform 100 | API level: 15 101 | Revision: 5 102 | Skins: HVGA, QVGA, WQVGA400, WQVGA432, WSVGA, WVGA800 (default), WVGA854, WXGA720, WXGA800 103 | Tag/ABIs : no ABIs. 104 | ---------- 105 | ``` 106 | 107 | 3.常用的adb命令 108 | `adb push `,`adb pull ` (文件传输) 109 | `adb install xxx`,`adb uninstall yyy` (apk安装和卸载) 110 | `adb usb`,`adb tcpip `,`adb connect`,`adb devices` (连接相关命令) 111 | `adb start-server`,`adb kill-server`,`adb reboot`,`adb remount` (重新挂载系统分区,使系统分区重新可写) 112 | 113 | `adb shell`相关命令: 114 | `adb shell df` (查看系统盘符) 115 | `adb shell input keyevent` (模拟按键输入,例如`adb shell input keyevent 3`表示按下HOME键) 116 | `adb shell input touchscreen` (模拟触屏输入,例如`adb shell input touchscreen swipe 18 665 18 350` ) 117 | 118 | `adb shell dumpsys activity activities` (查看activity运行状态) 119 | ``` 120 | hujiawei-MBPR:hexoblog hujiawei$ adb shell dumpsys activity activities 121 | ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities) 122 | Display #0 (activities from top to bottom): 123 | Stack #0: 124 | Task id #279 125 | * TaskRecord{2fbcccec #279 A=com.android.launcher U=0 sz=1} 126 | userId=0 effectiveUid=u0a15 mCallingUid=1000 mCallingPackage=android 127 | affinity=com.android.launcher 128 | intent={act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000000 cmp=com.android.launcher/com.android.launcher2.Launcher} 129 | realActivity=com.android.launcher/com.android.launcher2.Launcher 130 | autoRemoveRecents=false isPersistable=true numFullscreen=1 taskType=1 mTaskToReturnTo=0 131 | rootWasReset=false mNeverRelinquishIdentity=true mReuseTask=false 132 | Activities=[ActivityRecord{74b834e u0 com.android.launcher/com.android.launcher2.Launcher t279}] 133 | askedCompatMode=false inRecents=true isAvailable=true 134 | lastThumbnail=null lastThumbnailFile=/data/system/recent_images/279_task_thumbnail.png 135 | hasBeenVisible=true firstActiveTime=1448539994507 lastActiveTime=1448539994507 (inactive for 58s) 136 | ``` 137 | 138 | `adb pm xxx` (Package管理信息) 139 | 例如,查看所有的packages 140 | ``` 141 | hujiawei-MBPR:hexoblog hujiawei$ adb shell pm list packages -f 142 | package:/system/app/YouTube/YouTube.apk=com.google.android.youtube 143 | package:/system/priv-app/TelephonyProvider/TelephonyProvider.apk=com.android.providers.telephony 144 | package:/system/app/MediaShortcuts/MediaShortcuts.apk=com.google.android.gallery3d 145 | package:/data/app/com.support.android.designlibdemo-1/base.apk=com.support.android.designlibdemo 146 | package:/system/priv-app/Velvet/Velvet.apk=com.google.android.googlequicksearchbox 147 | package:/system/priv-app/CalendarProvider/CalendarProvider.apk=com.android.providers.calendar 148 | package:/data/app/com.imooc.animatedselector-1/base.apk=com.imooc.animatedselector 149 | package:/system/priv-app/MediaProvider/MediaProvider.apk=com.android.providers.media 150 | package:/system/priv-app/GoogleOneTimeInitializer/GoogleOneTimeInitializer.apk=com.google.android.onetimeinitializer 151 | package:/data/app/com.wandoujia-1/base.apk=com.wandoujia 152 | package:/system/app/Bug2GoStub/Bug2GoStub.apk=com.motorola.bug2go 153 | package:/data/app/com.sina.weibo.sdk.gensign-1/base.apk=com.sina.weibo.sdk.gensign 154 | package:/data/app/com.sohu.inputmethod.sogou-1/base.apk=com.sohu.inputmethod.sogou 155 | package:/system/priv-app/WallpaperCropper/WallpaperCropper.apk=com.android.wallpapercropper 156 | package:/data/app/com.jredu.netease.news-1/base.apk=com.jredu.netease.news 157 | ``` 158 | 159 | `adb am xxx` (Activity管理信息) 160 | 例如,启动一个activity `adb shell am start -n packageName[+className]` 161 | ``` 162 | hujiawei-MBPR:hexoblog hujiawei$ adb shell am start com.wandoujia 163 | Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] pkg=com.wandoujia } 164 | ``` 165 | 166 | OK,本节结束,谢谢阅读。 167 | -------------------------------------------------------------------------------- /ReadingNotes-AndroidHeros/2015-11-28-Android-Heros-Reading-Notes-4.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Android Heroes Reading Notes 4 4 | categories: android 5 | date: 2015-11-28 13:48:34 6 | --- 7 | 《Android群英传》读书笔记 (4) 第八章 Activity和Activity调用栈分析 + 第九章 系统信息与安全机制 + 第十章 性能优化 8 | 9 | ### **第八章 Activity和Activity调用栈分析** 10 | 1.Activity生命周期 11 | 理解生命周期就是两张图:第一张图是回字型的生命周期图 12 | {% img https://hujiaweibujidao.github.io/images/androidheros_activitylife1.png 280 340 %} 13 | 第二张图是金字塔型的生命周期图 14 | {% img https://hujiaweibujidao.github.io/images/androidheros_activitylife2.png 350 150%} 15 | 16 | **注意点** 17 | (1)从stopped状态重新回到前台状态的时候会先调用`onRestart`方法,然后再调用后续的`onStart`等方法; 18 | (2)启动另一个Activity然后finish,先调用旧Activity的onPause方法,然后调用新的Activity的onCreate->onStart->onResume方法,然后调用旧Activity的onStop->onDestory方法。 19 | 如果没有调用finish那么旧Activity会调用onPause->onSaveInstanceState->onStop方法,onDestory方法不会被调用。 20 | (3)如果应用长时间处于stopped状态并且此时系统内存极为紧张的时候,系统就会回收Activity,此时系统在回收之前会回调`onSaveInstanceState`方法来保存应用的数据Bundle。当该Activity重新创建的时候,保存的Bundle数据就会传递到`onRestoreSaveInstanceState`方法和`onCreate`方法中,这就是`onCreate`方法中`Bundle savedInstanceState`参数的来源(onRestoreInstanceState的bundle参数也会传递到onCreate方法中,你也可以选择在onCreate方法中做数据还原)。 21 | **onSaveInstanceState方法和onRestoreInstanceState方法“不一定”是成对的被调用的。** 22 | onSaveInstanceState的调用遵循一个重要原则,即当系统“未经你许可”时销毁了你的activity,则onSaveInstanceState会被系统调用,这是系统的责任,因为它必须要提供一个机会让你保存你的数据。 23 | onRestoreInstanceState被调用的前提是,activity“确实”被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用,例如,当正在显示activity的时候,用户按下HOME键回到主界面,然后用户紧接着又返回到activity,这种情况下activity一般不会因为内存的原因被系统销毁,故activity的onRestoreInstanceState方法不会被执行。 24 | 25 | 2.Activity任务栈 26 | 应用内的Activity是被任务栈Task来管理的,一个Task中的Activity可以来自不同的应用,同一个应用的Activity也可能不在同一个Task中。默认情况下,任务栈依据栈的后进先出原则管理Activity,但是Activity可以设置一些“特权”打破默认的规则,主要是通过在AndroidManifest文件中的属性`android:launchMode`或者通过Intent的flag来设置。 27 | 28 | **standard**:默认的启动模式,该模式下会生成一个新的Activity,同时将该Activity实例压入到栈中(不管该Activity是否已经存在在Task栈中,都是采用new操作)。例如: 栈中顺序是A B C D ,此时D通过Intent跳转到A,那么栈中结构就变成 A B C D A,点击返回按钮的 显示顺序是 D C B A,依次摧毁。 29 | 30 | **singleTop**:在singleTop模式下,如果当前Activity D位于栈顶,此时通过Intent跳转到它本身的Activity(即D),那么不会重新创建一个新的D实例(走onNewIntent()),所以栈中的结构依旧为A B C D,如果跳转到B,那么由于B不处于栈顶,所以会新建一个B实例并压入到栈中,结构就变成了A B C D B。应用实例:三条推送,点进去都是一个activity。 31 | 32 | **singleTask**:在singleTask模式下,Task栈中只能有一个对应Activity的实例。例如:现在栈的结构为A B C D,此时D通过Intent跳转到B(走onNewIntent()),则栈的结构变成了:A B。其中的C和D被栈弹出销毁了,也就是说位于B之上的实例都被销毁了。如果系统已经存在一个实例,系统就会将请求发送到这个实例上,但这个时候,系统就不会再调用通常情况下我们处理请求数据的onCreate方法,而是调用onNewIntent方法。通常应用于首页,首页肯定得在栈底部,也只能在栈底部。 33 | 34 | **singleInstance**:singleInstance模式下会将打开的Activity压入一个新建的任务栈中。例如:Task栈1中结构为:A B C,C通过Intent跳转到了D(D的启动模式为singleInstance),那么则会新建一个Task 栈2,栈1中结构依旧为A B C,栈2中结构为D,此时屏幕中显示D,之后D通过Intent跳转到D,栈2中不会压入新的D,所以2个栈中的情况没发生改变。如果D跳转到了C,那么就会根据C对应的启动模式在栈1中进行对应的操作,C如果为standard,那么D跳转到C,栈1的结构为A B C C,此时点击返回按钮,还是在C,栈1的结构变为A B C,而不会回到D。 35 | 36 | 3.Intent Flag启动模式 37 | (1)`Intent.FLAG_ACTIVITY_NEW_TASK`:使用一个新的task来启动Activity,一般用在service中启动Activity的场景,因为service中并不存在Activity栈。 38 | (2)`Intent.FLAG_ACTIVITY_SINGLE_TOP`:类似`andoid:launchMode="singleTop"` 39 | (3)`Intent.FLAG_ACTIVITY_CLEAR_TOP`:类似`andoid:launchMode="singleTask"` 40 | (4)`Intent.FLAG_ACTIVITY_NO_HISTORY`:使用这种模式启动Activity,当该Activity启动其他Activity后,该Activity就消失了,不会保留在task栈中。例如A B,在B中以这种模式启动C,C再启动D,则当前的task栈变成A B D。 41 | 42 | 4.清空任务栈 43 | (1)`clearTaskOnLaunch`:每次返回该Activity时,都将该Activity之上的所有Activity都清除。通过这个属性可以让task每次在初始化的时候都只有这一个Activity。 44 | (2)`finishOnTaskLaunch`:clearTaskOnLaunch作用在别的Activity身上,而finishOnTaskLaunch作用在自己身上。通过这个属性,当离开这个Activity所在的task,那么当用户再返回时,该Activity就会被finish掉。 **[暂时还不明白这个有什么作用]** 45 | (3)`alwaysRetainTaskState`:如果将Activity的这个属性设置为true,那么该Activity所在的task将不接受任何清理命令,一直保持当前task状态,相当于给了task一道”免死金牌”。 46 | 47 |
48 | ### **第九章 Android系统信息与安全机制** 49 | 1.获取系统信息:`android.os.Build`和`SystemProperty` 50 | ``` 51 | String board = Build.BOARD; 52 | String brand = Build.BRAND; 53 | String supported_abis = Build.SUPPORTED_ABIS[0]; 54 | String device = Build.DEVICE; 55 | String display = Build.DISPLAY; 56 | String fingerprint = Build.FINGERPRINT; 57 | String serial = Build.SERIAL; 58 | String id = Build.ID; 59 | String manufacturer = Build.MANUFACTURER; 60 | String model = Build.MODEL; 61 | String hardware = Build.HARDWARE; 62 | String product = Build.PRODUCT; 63 | String tags = Build.TAGS; 64 | String type = Build.TYPE; 65 | String codename = Build.VERSION.CODENAME; 66 | String incremental = Build.VERSION.INCREMENTAL; 67 | String release = Build.VERSION.RELEASE; 68 | String sdk_int = "" + Build.VERSION.SDK_INT; 69 | String host = Build.HOST; 70 | String user = Build.USER; 71 | String time = "" + Build.TIME; 72 | 73 | String os_version = System.getProperty("os.version"); 74 | String os_name = System.getProperty("os.name"); 75 | String os_arch = System.getProperty("os.arch"); 76 | String user_home = System.getProperty("user.home"); 77 | String user_name = System.getProperty("user.name"); 78 | String user_dir = System.getProperty("user.dir"); 79 | String user_timezone = System.getProperty("user.timezone"); 80 | String path_separator = System.getProperty("path.separator"); 81 | String line_separator = System.getProperty("line.separator"); 82 | String file_separator = System.getProperty("file.separator"); 83 | String java_vendor_url = System.getProperty("java.vendor.url"); 84 | String java_class_path = System.getProperty("java.class.path"); 85 | String java_class_version = System.getProperty("java.class.version"); 86 | String java_vendor = System.getProperty("java.vendor"); 87 | String java_version = System.getProperty("java.version"); 88 | String java_home = System.getProperty("java_home"); 89 | ``` 90 | 91 | 2.Apk应用信息:`PackageManager`和`ActivityManager` 92 | 在AndroidManifest文件中,Activity的信息是通过`ActivityInfo`类来封装的;整个Manifest文件中节点的信息是通过`PackageInfo`类来进行封装的;此外还有`ServiceInfo`、`ApplicationInfo`、`ResolveInfo`等。 93 | 其中`ResolveInfo`封装的是包含信息的上一级信息,所以它可以返回ActivityInfo、ServiceInfo等包含的信息,它经常用来帮助我们找到那些包含特定Intent条件的信息,如带分享功能、播放功能的应用。 94 | 95 | PackageManager侧重于获取应用的包信息,而ActivityManager侧重于获取运行的应用程序的信息。 96 | PackageManager常用的方法: 97 | `getPackageManger`、`getApplicationInfo`、`getApplicationIcon`、`getInstalledApplications`、`getInstalledPackages`、`queryIntentActivities`、`queryIntentServices`、`resolveActivity`、`resolveService`等 98 | ActivityManager封装了不少对象,每个对象都保存着一些重要信息。 99 | `ActivityManager.MemoryInfo`:关于系统内存的信息,例如`availMem`(系统可用内存)、`totalMem`(总内存)等; 100 | `Debug.MemoryInfo`:该MemoryInfo主要用于统计进程下的内存信息; 101 | `RunningAppProceeInfo`:运行进程的信息,存储的是与进程相关的信息,例如`processName`、`pid`、`uid`等; 102 | `RunningServiceInfo`:运行服务的信息,存储的是服务进程的信息,例如`activeSince`(第一次被激活时间)等。 103 | 104 | 3.packages.xml文件(位于`/data/system`目录下) 105 | 在系统初始化的时候,PackageManager的底层实现类PackageManagerService会去扫描系统中的一些特定的目录,并解析其中的apk文件,最后把它获得的应用信息保存到packages.xml文件中,当系统中的应用安装、删除或者升级时,它也会被更新。 106 | 107 | 4.Android安全机制 108 | 五道防线: 109 | (1)代码安全机制——代码混淆proguard 110 | (2)应用接入权限机制——AndroidManifest文件权限声明、权限检查机制 111 | 系统检查操作者权限的顺序:首先,判断permission名称,如果为空则直接返回PERMISSION_DENIED;其次,判断Uid,如果uid为0或者为System Service的uid,不做权限控制,如果uid与参数中的请求uid不同,那么返回PERMISSION_DENIED;最后,通过调用PackageManagerService.checkUidPermission方法判断该uid是否具有相应的权限,该方法会去xml的权限列表和系统级的platform.xml中进行查找。 112 | (3)应用签名机制——数字证书:系统不会安装没有签名的app,只有拥有相同数字签名的app才会在升级时被认为是同一个app 113 | (4)Linux内核层安全机制——Uid、访问权限控制 114 | (5)Android虚拟机沙箱机制——沙箱隔离:每个app运行在单独的虚拟机中,与其他应用完全隔离 115 | 116 | **apk反编译** 117 | 使用apktool、dex2jar、jd-gui三个工具反编译查看应用源码 118 | 119 | **apk加密** 120 | proguard不仅可以用来混淆代码(用无意义的字母来重命名类、方法和属性等),还可以删除无用的类、字段、方法和属性,以及删除无用的注释,最大限度地优化字节码文件。 121 | 下面是常见的proguard配置,其中`minifyEnabled`属性控制是否启动proguard;`proguardFiles`属性用于配置混淆文件,它分为两部分,一个是系统默认的混淆文件,它位于`/tools/proguard/proguard-android.txt`;另一个是自定义的混淆文件,可以在项目的app文件夹下找到该文件,在该文件中定义引入的第三方依赖包的混淆规则。 122 | ``` 123 | buildTypes { 124 | release { 125 | minifyEnabled false 126 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 127 | } 128 | } 129 | ``` 130 | 131 |
132 | ### **第十章 Android性能优化** 133 | 1.布局优化 134 | **人眼感觉的流畅需要画面的帧数达到每秒40帧到60帧,那么差不多每16ms系统就要对UI进行渲染和重绘。** 135 | (1)android系统提供了检测UI渲染时间的工具,“开发者选项”-“Profile GPU rendering”-“On screen as bars”,这个时候屏幕上将显示一些条形图,如下左图所示,每条柱状线都包含三部分,蓝色代表测量绘制Display List的时间,红色代表OpenGL渲染Display List所需要的时间,黄色代表CPU等待GPU处理的时间。中间的绿色横线代表VSYNC时间16ms,需要尽量将所有条形图都控制在这条绿线之下。 136 | (2)过度绘制(Overdraw)也是很浪费CPU/GPU资源的,系统也提供了检测工具`Debug GPU Overdraw`来查看界面overdraw的情况。该工具会使用不同的颜色绘制屏幕,来指示overdraw发生在哪里以及程度如何,其中: 137 | 没有颜色: 意味着没有overdraw。像素只画了一次。 138 | 蓝色: 意味着overdraw 1倍。像素绘制了两次。大片的蓝色还是可以接受的(若整个窗口是蓝色的,可以摆脱一层)。 139 | 绿色: 意味着overdraw 2倍。像素绘制了三次。中等大小的绿色区域是可以接受的但你应该尝试优化、减少它们。 140 | 浅红: 意味着overdraw 3倍。像素绘制了四次,小范围可以接受。 141 | 暗红: 意味着overdraw 4倍。像素绘制了五次或者更多。这是错误的,要修复它们。 142 | 143 | {%img /images/androidheros_gpu.png 200 360%}    {%img /images/androidheros_overdraw.png 200 360%} 144 | (3)优化布局层级,Google在文档中建议View树的高度不宜超过10层 145 | 避免嵌套过多无用布局: 146 | ①使用标签重用layout 147 | **如果需要在标签中覆盖类似原布局中的android:layout_xxx的属性,就必须在标签中同时指定android:layout_width和android:layout_height属性。** 148 | ②使用实现view的延迟加载 149 | ViewStub是一个非常轻量级的组件,它不仅不可见,而且大小为0。 150 | **ViewStub和View.GONE有啥区别?** 151 | 它们的共同点是初始时都不会显示,但是前者只会在显示时才去渲染整个布局,而后者在初始化布局树的时候就已经添加到布局树上了,相比之下前者的布局具有更高的效率。 152 | (4)Hierarchy Viewer:查看视图树的工具 153 | 154 | 2.内存优化 155 | 通常情况下我们所说的内存是指手机的RAM,它包括以下几部分: 156 | (1)寄存器:寄存器处于CPU内部,在程序中无法控制; 157 | (2)栈:存放基本数据类型和对象的引用; 158 | (3)堆:存放对象和数组,由虚拟机GC来管理; 159 | (4)静态存储区域(static field):在固定的位置存放应用程序运行时一直存在的数据,Java在内存中专门划分了一个静态存储区域来管理一些特殊的数据变量,如静态的数据变量; 160 | (5)常量池(constant pool):虚拟机必须为每个被装在的类维护一个常量池,常量池就是这个类所用的常量的一个有序集合,包括直接常量(基本类型、string)和对其他类型、字段和方法的符号引用。 161 | 162 | **内存优化实例** 163 | (1)Bitmap优化 164 | 使用适当分辨率和大小的图片; 165 | 及时回收内存:从Android 3.0开始,Bitmap被放置到了堆中,其内存由GC管理,所以不用手动调用bitmap.recycle()方法进行释放了; 166 | 使用图片缓存:设计内存缓存和磁盘缓存可以更好地利用Bitmap。 167 | 168 | (2)代码优化 169 | 使用静态方法,它比普通方法会提高15%左右的访问速度; 170 | 尽量不要使用枚举,少用迭代器;**[我还不知道为什么]** 171 | 对Cursor、Receiver、Sensor、File等对象,要非常注意对它们的创建、回收与注册、解注册; 172 | 使用SurfaceView来替代view进行大量的、频繁的绘图操作; 173 | 尽量使用视图缓存,而不是每次都执行inflate方法解析视图。 174 | 175 | 3.其他的辅助工具 176 | (1)Lint工具:代码提示工具,可以用来发现代码中隐藏的一些问题 177 | (2)Memory Monitor工具:内存监视工具 178 | (3)TraceView工具:可视化性能调查工具,它用来分析TraceView日志 179 | (4)MAT工具:内存分析工具 180 | (5)dumpsys命令:该命令可以列出android系统相关的信息和服务状态,可使用的配置参数很多,常见的有: 181 | `activity`:显示所有Activity栈的信息; 182 | `meminfo`:显示内存信息; 183 | `battery`:显示电池信息; 184 | `package`:显示包信息; 185 | `wifi`:显示wifi信息; 186 | `alarm`:显示alarm信息; 187 | `procstats`:显示进程和内存状态。 188 | 189 | OK,本节结束,谢谢阅读。 190 | 191 | 192 | -------------------------------------------------------------------------------- /ReadingNotes-AndroidHeros/2015-11-29-Android-Heroes-Reading-Notes-5.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Android Heroes Reading Notes 5 4 | categories: android 5 | date: 2015-11-29 10:15:23 6 | --- 7 | 《Android群英传》读书笔记 (5) 第十一章 搭建云端服务器 + 第十二章 Android 5.X新特性详解 + 第十三章 Android实例提高 8 | 9 | ### **第十一章 搭建云端服务器** 10 | 该章主要介绍了移动后端服务的概念以及Bmob的使用,比较简单,所以略过不总结。 11 | 12 | ### **第十三章 Android实例提高** 13 | 该章主要介绍了拼图游戏和2048的小项目实例,主要是代码,所以略过不总结。 14 | 15 | ### **第十二章 Android 5.X新特性详解** 16 | 1.Material Design 17 | (1)MD主题:“拟物扁平化” 18 | ``` 19 | @android:style/Theme.Material 20 | @android:style/Theme.Material.Light 21 | @android:style/Theme.Material.Light.DarkActionBar 22 | ``` 23 | 24 | (2)Color Palette 和 Palette 25 | Color Palette颜色主题,可以通过自定义style的方式自定义颜色风格,对应的name值如下面左图所示 26 | ``` 27 | 32 | ``` 33 | 使用Palette来提取颜色,从而让主题能够动态适应当前页面的色调,做到整个app颜色基调和谐统一,使用的时候要引入依赖`com.android.support:palette-v7:x.y.z`引用。提取颜色的种类:Vibrant(充满活力的),Vibrant dark, Vibrant light, Muted(柔和的), Muted dark, Muted light。 34 | ``` 35 | Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test); 36 | // 创建Palette对象 37 | Palette.generateAsync(bitmap, 38 | new Palette.PaletteAsyncListener() { 39 | @Override 40 | public void onGenerated(Palette palette) { 41 | // 通过Palette来获取对应的色调 //getDarkMutedSwatch,getDarkVibrantSwatch,getLightVibrantSwatch,getMutedSwatch 42 | Palette.Swatch vibrant = palette.getDarkVibrantSwatch(); 43 | // 将颜色设置给相应的组件 44 | getActionBar().setBackgroundDrawable(new ColorDrawable(vibrant.getRgb())); 45 | getWindow().setStatusBarColor(vibrant.getRgb()); 46 | } 47 | }); 48 | ``` 49 | 显示效果如下面右图所示 50 | {% img /images/androidheros_colorpalette.png 200 360 %} {% img /images/androidheros_palette.png 200 360 %} 51 | 52 | (3)阴影效果 53 | View增加了Z属性,对应垂直方向上的高度变化,Z由elevation和translationZ两部分组成(Z=elevation+translationZ),它们都是5.X引入的新属性。elevation是静态的成员,translationZ可以在代码中用来实现动画效果。 54 | 布局属性:`android:elevation="xxxdp"` 55 | 56 | (4)Tinting(着色)和Clipping(裁剪) 57 | tinting的使用就是配置tint和tintMode就可以了,tint通过修改图像的alpha遮罩来修改图像的颜色,从而达到重新着色的目的。 58 | clipping可以改变一个view的外形,要使用它,首先需要使用ViewOutlineProvider来修改outline,然后再通过setOutlineProvider将outline作用给view。 59 | 60 | (5)列表和卡片 61 | RecyclerView和CardView是support-v7包中新添加的组件,使用它们需要引用依赖`com.android.support:recyclerview-v7:x.y.z`和`com.android.support:cardview-v7:x.y.z`。 62 | RecyclerView也具有ListView一样的item复用机制,还可以直接把ViewHolder的实现封装起来,开发者只要是实现ViewHolder就行了,RecyclerView会自动回收复用每一个item。RecyclerView还引入了LayoutManager来帮助开发者方便地创建不同的布局,例如LinearLayoutManager、GridLayoutManager等,此外,为RecyclerView编写Adapter的代码也更加方便了。 63 | ``` 64 | public class RecyclerAdapter extends RecyclerView.Adapter { 65 | 66 | private List mData; 67 | public RecyclerAdapter(List data) { 68 | mData = data; 69 | } 70 | public OnItemClickListener itemClickListener; 71 | 72 | public void setOnItemClickListener(OnItemClickListener itemClickListener) { 73 | this.itemClickListener = itemClickListener; 74 | } 75 | 76 | public interface OnItemClickListener { 77 | void onItemClick(View view, int position); 78 | } 79 | 80 | public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 81 | 82 | public TextView textView; 83 | 84 | public ViewHolder(View itemView) { 85 | super(itemView); 86 | textView = (TextView) itemView; 87 | textView.setOnClickListener(this); 88 | } 89 | 90 | // 通过接口回调来实现RecyclerView的点击事件 91 | @Override 92 | public void onClick(View v) { 93 | if (itemClickListener != null) { 94 | itemClickListener.onItemClick(v, getPosition()); 95 | } 96 | } 97 | } 98 | 99 | @Override 100 | public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { 101 | // 将布局转化为View并传递给RecyclerView封装好的ViewHolder 102 | View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.rc_item, viewGroup, false); 103 | return new ViewHolder(v); 104 | } 105 | 106 | @Override 107 | public void onBindViewHolder(ViewHolder viewHolder, int i) { 108 | // 建立起ViewHolder中视图与数据的关联 109 | viewHolder.textView.setText(mData.get(i)); 110 | } 111 | 112 | @Override 113 | public int getItemCount() { 114 | return mData.size(); 115 | } 116 | } 117 | ``` 118 | 119 | CardView也是一种容器内布局,只是它提供了卡片样的形式。在XML布局文件中使用CardView的时候还需要引入其命名空间`xmlns:cardview=http://schemas.android.com/apk/res-auto`。 120 | 121 | (6)Activity过渡动画 122 | 以前Activity过渡动画是通过`overridePendingTransition(int inAnim, int outAnim)`来实现的,效果差强人意。 123 | 现在Android 5.X提供了三种Transition类型: 124 | **进入和退出动画**:两者又包括了`explode`(分解)、`slide`(滑动)和`fade`(淡出)三种效果; 125 | 使用方式:假设Activity从A跳转到B,那么将A中原来的`startActivity`改为如下代码: 126 | ``` 127 | startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle()); 128 | ``` 129 | 然后在B的onCreate方法中添加如下代码: 130 | ``` 131 | //首先声明需要开启Activity过渡动画 132 | getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); 133 | //然后设置当前Activity的进入和退出动画 134 | getWindow().setEnterTransition(new Fade()); 135 | getWindow().setExitTransition(new Fade()); 136 | ``` 137 | 138 | **共享元素过渡动画**:一个共享元素过渡动画决定两个Activity之间的过渡怎么共享它们的视图,包括了 139 | `changeBounds`:改变目标视图的布局边界; 140 | `changeClipBounds`:裁剪目标视图的边界; 141 | `changeTransform`:改变目标视图的缩放比例和旋转角度; 142 | `changeImageTransform`:改变目标图片的大小和缩放比例。 143 | 使用方式:假设Activity从A跳转到B,那么将A中原来的`startActivity`改为如下代码: 144 | ``` 145 | //单个共享元素的调用方式 146 | startActivity(intent,ActivityOptions.makeSceneTransitionAnimation(this, view, "share").toBundle()); 147 | //多个共享元素的调用方式 148 | startActivity(intent,ActivityOptions.makeSceneTransitionAnimation(this, 149 | Pair.create(view, "share"), 150 | Pair.create(fab, "fab")).toBundle()); 151 | ``` 152 | 然后在B的onCreate方法中添加如下代码: 153 | ``` 154 | //声明需要开启Activity过渡动画 155 | getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); 156 | ``` 157 | 其次还要在Activity A和B的布局文件中为共享元素组件添加`android:transitionName="xxx"`属性。 158 | 159 | (7)MD动画效果 160 | **Ripple效果** 161 | 水波纹效果有两种:波纹有边界和波纹无边界。前者是指波纹被限制在控件的边界,后者指波纹不会限制在控件边界中,会呈圆形发放出去。 162 | 除了使用xml文件自定义ripple效果之外,还可以通过下面的代码来快速实现ripple效果 163 | ``` 164 | //波纹有边界 165 | android:background="?android:attr/selectableItemBackground" 166 | //波纹无边界 167 | android:background="?android:attr/selectableItemBackgroundBorderless" 168 | ``` 169 | 170 | **Circular Reveal效果** 171 | 圆形显现效果:通过ViewAnimationUtils.createCircularReveal方法可以创建一个RevealAnimator动画,代码如下,其中`centerX/centerY`表示动画开始的位置,`startRadius`和`endRadius`分别表示动画的起始半径和结束半径。 172 | ``` 173 | public static Animator createCircularReveal(View view, 174 | int centerX, int centerY, float startRadius, float endRadius) { 175 | return new RevealAnimator(view, centerX, centerY, startRadius, endRadius); 176 | } 177 | ``` 178 | 下面是一个例子,该例子会呈现出图片从一个点以圆形的方式放大到图片大小的动画效果: 179 | ``` 180 | final ImageView imageView = (ImageView) findViewById(R.id.imageview); 181 | imageView.setOnClickListener(new View.OnClickListener() { 182 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 183 | @Override 184 | public void onClick(View v) { 185 | Animator animator = ViewAnimationUtils.createCircularReveal(imageView, imageView.getWidth() / 2, imageView.getHeight() / 2, 0, imageView.getWidth()); 186 | animator.setDuration(2000); 187 | animator.setInterpolator(new AccelerateDecelerateInterpolator()); 188 | animator.start(); 189 | } 190 | }); 191 | ``` 192 | 193 | **view的状态切换动画** 194 | 在Android 5.X中,可以使用动画来作为视图改变的效果,有两种方式来实现该动画:StateListAnimator和animated-selector。 195 | StateListAnimator是将动画效果(objectAnimator)配置到原来的selector的item中来实现的,看下面的例子: 196 | ``` 197 | //定义StateListAnimator 198 | 199 | 200 | 201 | 202 | 206 | 207 | 208 | 209 | 210 | 214 | 215 | 216 | 217 | 218 | //将StateListAnimator设置给checkbox 219 | 225 | ``` 226 | 227 | animated-selector是一个状态改变的动画效果selector,MD中很多控件设计用到了animated-selector,例如check-box,下面便是一个类似check-box效果的例子: 228 | ``` 229 | 230 | 233 | 234 | 235 | 236 | 237 | 238 | 241 | 242 | 243 | 244 | 245 | ... 246 | 247 | 248 | 249 | 250 | 251 | 254 | 255 | 256 | 257 | 258 | ... 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | ``` 267 | 268 | (8)Toolbar 269 | Toolbar和ActionBar以前灰常详细地介绍过,此处略过不总结,[点击这里查看](https://hujiaweibujidao.github.io/blog/2015/06/02/android-ui-2-toolbar/)。 270 | 271 | (9)Notification 272 | Android 5.x改进了通知栏,优化了Notification,现在共有三种类型的Notification: 273 | 基本Notification:最基本的通知,只有icon,text,时间等信息 274 | 折叠式Notification:可以折叠的通知,有两种显示状态:一种普通状态,另一种是展开状态 275 | 悬挂式Notification:在屏幕上方显示通知,且不会打断用户操作 276 | 277 | 三种类型的notification的使用如下所示: 278 | ``` 279 | public void basicNotify(View view) { 280 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.baidu.com")); 281 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); 282 | Notification.Builder builder = new Notification.Builder(this); 283 | builder.setSmallIcon(R.drawable.ic_launcher);// 设置Notification的各种属性 284 | builder.setContentIntent(pendingIntent); 285 | builder.setAutoCancel(true); 286 | builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher)); 287 | builder.setContentTitle("Basic Notifications"); 288 | builder.setContentText("I am a basic notification"); 289 | builder.setSubText("it is really basic"); 290 | // 通过NotificationManager来发出Notification 291 | NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 292 | notificationManager.notify(NOTIFICATION_ID_BASIC, builder.build()); 293 | } 294 | 295 | public void collapsedNotify(View view) { 296 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.sina.com")); 297 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); 298 | Notification.Builder builder = new Notification.Builder(this); 299 | builder.setSmallIcon(R.drawable.ic_launcher); 300 | builder.setContentIntent(pendingIntent); 301 | builder.setAutoCancel(true); 302 | builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher)); 303 | // 通过RemoteViews来创建自定义的Notification视图 304 | RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.notification); 305 | contentView.setTextViewText(R.id.textView, "show me when collapsed"); 306 | Notification notification = builder.build(); 307 | notification.contentView = contentView; 308 | // 通过RemoteViews来创建自定义的Notification视图 309 | RemoteViews expandedView = new RemoteViews(getPackageName(), R.layout.notification_expanded); 310 | notification.bigContentView = expandedView; 311 | 312 | NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); 313 | nm.notify(NOTIFICATION_ID_COLLAPSE, notification); 314 | } 315 | 316 | public void headsupNotify(View view) { 317 | Notification.Builder builder = new Notification.Builder(this) 318 | .setSmallIcon(R.drawable.ic_launcher) 319 | .setPriority(Notification.PRIORITY_DEFAULT) 320 | .setCategory(Notification.CATEGORY_MESSAGE) 321 | .setContentTitle("Headsup Notification") 322 | .setContentText("I am a Headsup notification."); 323 | 324 | Intent push = new Intent(); 325 | push.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 326 | push.setClass(this, MainActivity.class); 327 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, push, PendingIntent.FLAG_CANCEL_CURRENT); 328 | builder.setContentText("Heads-Up Notification on Android 5.0").setFullScreenIntent(pendingIntent, true); 329 | 330 | NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); 331 | nm.notify(NOTIFICATION_ID_HEADSUP, builder.build()); 332 | } 333 | ``` 334 | 显示效果如下: 335 | 336 | {% img https://hujiaweibujidao.github.io/images/androidheros_basicnotification.png 200 360 %} {% img https://hujiaweibujidao.github.io/images/androidheros_collapsenotification.png 200 360 %} {% img https://hujiaweibujidao.github.io/images/androidheros_headsupnotification.png 200 360 %} 337 | 338 | **通知的显示等级** 339 | Android 5.x将通知分为了三个等级: 340 | `VISIBILITY_PRIVATE`:表明只有当没有锁屏的时候才会显示; 341 | `VISIBILITY_PUBLIC`:表明任何情况下都会显示; 342 | `VISIBILITY_SECRET`:表明在pin、password等安全锁和没有锁屏的情况下才会显示; 343 | 设置等级的方式是`builder.setVisibility(Notification.VISIBILITY_PRIVATE);` 344 | 345 | **其他学习资料** 346 | 1.[使用 Android Design 支持库构建 Material Design Android 应用](https://www.code-labs.io/codelabs/Material-Design-Style/index.html) 347 | 348 | OK,本节结束,谢谢阅读。 349 | 350 | 351 | -------------------------------------------------------------------------------- /ReadingNotes-AndroidHeros/2015-11-29-Android-Heroes-Reading-Notes.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Android Heroes Reading Notes 4 | categories: android 5 | date: 2015-11-29 16:47:12 6 | --- 7 | **《Android群英传》读书笔记系列目录** 8 | 9 | 最近有幸认识了很多国内安卓开发的大神,每每想到这里都感觉自己总算是找到了组织,步入了正轨。(^o^) 10 | 前段时间购入了几位大牛们写的书,这些书都是好评如潮啊,哈哈哈,慢慢看过来,永远相信勤能补拙吧。(^o^) 11 | 12 | 《Android群英传》读书笔记分为了5部分,每部分包含几章,除了第十一章 搭建云端服务器和第十三章 Android实例提高没有总结之外,其他各章只有少部分内容没有进行总结,也许是暂时自己还未完全理解,抑或是打算自己用到的时候再学习然后总结。OK,目录如下: 13 | 14 | (1)[《Android群英传》读书笔记 (1)](https://hujiaweibujidao.github.io/blog/2015/11/25/Android-Heros-Reading-Notes-1/) 15 | 第一章 Android体系与系统架构 + 第二章 Android开发工具新接触 16 | (2)[《Android群英传》读书笔记 (2)](https://hujiaweibujidao.github.io/blog/2015/11/26/Android-Heros-Reading-Notes-2/) 17 | 第三章 Android控件架构与自定义控件详解 + 第四章 ListView使用技巧 + 第五章 Android Scroll分析 18 | (3)[《Android群英传》读书笔记 (3)](https://hujiaweibujidao.github.io/blog/2015/11/27/Android-Heros-Reading-Notes-3/) 19 | 第六章 Android绘图机制与处理技巧 + 第七章 Android动画机制与使用技巧 20 | (4)[《Android群英传》读书笔记 (4)](https://hujiaweibujidao.github.io/blog/2015/11/28/Android-Heros-Reading-Notes-4/) 21 | 第八章 Activity和Activity调用栈分析 + 第九章 Android系统信息与安全机制 + 第十章 Android性能优化 22 | (5)[《Android群英传》读书笔记 (5)](https://hujiaweibujidao.github.io/blog/2015/11/29/Android-Heroes-Reading-Notes-5/) 23 | 第十一章 搭建云端服务器 + 第十二章 Android 5.X新特性详解 + 第十三章 Android实例提高 24 | 25 | OK,接下来开始写《Android开发艺术探索》读书笔记,哈哈哈 26 | 27 | 28 | -------------------------------------------------------------------------------- /ReadingNotes-ArtOfAndroidDevelopment/2015-11-29-Art-of-Android-Development-Reading-Notes-1.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Art of Android Development Reading Notes 1 4 | categories: android 5 | date: 2015-11-29 23:07:49 6 | --- 7 | 《Android开发艺术探索》读书笔记 (1) 第1章 Activity的生命周期和启动模式 8 | 9 | 本节和《Android群英传》中的**第8章Activity和Activity调用栈分析**有关系,[建议先阅读该章的总结](https://hujiaweibujidao.github.io/blog/2015/11/28/Android-Heros-Reading-Notes-4/) 10 | 11 | ### **第1章 Activity的生命周期和启动模式** 12 | #### 1.1 Activity生命周期全面分析 13 | 1.1.1 典型情况下生命周期分析 14 | (1)一般情况下,当当前Activity从不可见重新变为可见状态时,`onRestart`方法就会被调用。 15 | (2)当用户打开新的Activity或者切换到桌面的时候,回调如下:`onPause` -> `onStop`,但是如果新Activity采用了透明主题,那么`onStop`方法不会被回调。当用户再次回到原来的Activity时,回调如下:`onRestart` -> `onStart` -> `onResume`。 16 | (3)`onStart`和`onStop`对应,它们是从Activity是否可见这个角度来回调的;`onPause`和`onResume`方法对应,它们是从Activity是否位于前台这个角度来回调的。 17 | (4)从Activity A进入到Activity B,回调顺序是`onPause(A) -> onCreate(B) -> onStart(B) -> onResume(B) -> onStop(A)`,所以不能在onPause方法中做重量级的操作。 18 | 19 | 1.1.2 异常情况下生命周期分析 20 | (1)`onSaveInstanceState`方法只会出现在Activity被异常终止的情况下,它的调用时机是在`onStop`之前,它和`onPause`方法没有既定的时序关系,可能在它之前,也可能在它之后。当Activity被重新创建的时候,`onRestoreInstanceState`会被回调,它的调用时机是`onStart`之后。 21 | **系统只会在Activity即将被销毁并且有机会重新显示的情况下才会去调用onSaveInstanceState方法。** 22 | **当Activity在异常情况下需要重新创建时,系统会默认为我们保存当前Activity的视图结构,并且在Activity重启后为我们恢复这些数据,比如文本框中用户输入的数据、listview滚动的位置等,这些view相关的状态系统都会默认为我们恢复。具体针对某一个view系统能为我们恢复哪些数据可以查看view的源码中的onSaveInstanceState和onRestoreInstanceState方法。** 23 | (2)Activity按优先级的分类 24 | 前台Activity;可见但非前台Activity;后台Activity 25 | (3)`android:configChanges="xxx"`属性,常用的主要有下面三个选项: 26 | `local`:设备的本地位置发生了变化,一般指切换了系统语言; 27 | `keyboardHidden`:键盘的可访问性发生了变化,比如用户调出了键盘; 28 | `orientation`:屏幕方向发生了变化,比如旋转了手机屏幕。 29 | 配置了`android:configChanges="xxx"`属性之后,Activity就不会在对应变化发生时重新创建,而是调用Activity的`onConfigurationChanged`方法。 30 | 31 | #### 1.2 Activity的启动模式 32 | 1.2.1 启动模式 33 | (1)当任务栈中没有任何Activity的时候,系统就会回收这个任务栈。 34 | (2)从非Activity类型的Context(例如ApplicationContext、Service等)中以`standard`模式启动新的Activity是不行的,因为这类context并没有任务栈,所以需要为待启动Activity指定`FLAG_ACTIVITY_NEW_TASK`标志位。 35 | (3)任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态,用户可以通过切换将后台任务栈再次调到前台。 36 | (4)参数`TaskAffinity`用来指定Activity所需要的任务栈,意为任务相关性。默认情况下,所有Activity所需的任务栈的名字为应用的包名。TaskAffinity属性主要和`singleTask`启动模式或者`allowTaskReparenting`属性配对使用,在其他情况下没有意义。 37 | 当TaskAffinity和singleTask启动模式配对使用的时候,它是具有该模式的Activity的目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中; 38 | 当TaskAffinity和allowTaskReparenting结合的时候,当一个应用A启动了应用B的某个Activity C后,如果Activity C的allowTaskReparenting属性设置为true的话,那么当应用B被启动后,系统会发现Activity C所需的任务栈存在了,就将Activity C从A的任务栈中转移到B的任务栈中。 39 | (5)singleTask模式的具体分析:当一个具有singleTask启动模式的Activity请求启动之后,系统首先会寻找是否存在A想要的任务栈,如果不存在,就重新创建一个任务栈,然后创建Activity的实例把它放到栈中;如果存在Activity所需的任务栈,这时候要看栈中是否有Activity实例存在,如果有,那么系统就会把该Activity实例调到栈顶,并调用它的onNewIntent方法(它之上的Activity会被迫出栈,所以**singleTask模式具有FLAG_ACTIVITY_CLEAR_TOP效果**);如果Activity实例不存在,那么就创建Activity实例并把它压入栈中。 40 | (6)设置启动模式既可以使用xml属性`android:launchMode`,也可以使用代码`intent.addFlags()`。**区别在于限定范围不同,前者无法直接为Activity设置FLAG_ACTIVITY_CLEAR_TOP标识,而后者无法为Activity指定singleInstance模式。** 41 | 42 | 1.2.2 Activity的Flags 43 | `FLAG_ACTIVITY_NEW_TASK`,`FLAG_ACTIVITY_SINGLE_TOP`,`FLAG_ACTIVITY_CLEAR_TOP` 44 | `FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS`:具有这个标记的Activity不会出现在历史Activity列表中,当某些情况下我们不希望用户通过历史列表回到我们的Activity的时候这个标记比较有用,它等同于属性设置`android:excludeFromRecents="true"`。 45 | 46 | #### 1.3 IntentFilter的匹配规则 47 | (1)IntentFilter中的过滤信息有action、category、data,为了匹配过滤列表,需要同时匹配过滤列表中的action、category、data信息,否则匹配失败。**一个过滤列表中的action、category、data可以有多个,所有的action、category、data分别构成不同类别,同一类别的信息共同约束当前类别的匹配过程。只有一个Intent同时匹配action类别、category类别和data类别才算完全匹配,只有完全匹配才能成功启动目标Activity。此外,一个Activity中可以有多个intent-filter,一个Intent只要能匹配任何一组intent-filter即可成功启动对应的Activity。** 48 | ``` 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ``` 60 | (2)action匹配规则 61 | 只要Intent中的action能够和过滤规则中的任何一个action相同即可匹配成功,action匹配区分大小写。 62 | (3)category匹配规则 63 | Intent中如果有category那么所有的category都必须和过滤规则中的其中一个category相同,如果没有category的话那么就是默认的category,即`android.intent.category.DEFAULT`,所以为了Activity能够接收隐式调用,配置多个category的时候必须加上默认的category。 64 | (4)data匹配规则 65 | data的结构很复杂,语法大致如下: 66 | ``` 67 | 74 | ``` 75 | 主要由`mimeType`和`URI`组成,其中mimeType代表媒体类型,而URI的结构也复杂,大致如下: 76 | `://:/[]|[]|[pathPattern]` 77 | 例如`content://com.example.project:200/folder/subfolder/etc` 78 | `scheme、host、port`分别表示URI的模式、主机名和端口号,其中如果scheme或者host未指定那么URI就无效。 79 | `path、pathPattern、pathPrefix`都是表示路径信息,其中path表示完整的路径信息,pathPrefix表示路径的前缀信息;pathPattern表示完整的路径,但是它里面包含了通配符(*)。 80 | 81 | **data匹配规则:Intent中必须含有data数据,并且data数据能够完全匹配过滤规则中的某一个data。** 82 | **URI有默认的scheme!** 83 | 如果过滤规则中的mimeType指定为`image/*`或者`text/*`等这种类型的话,那么即使过滤规则中没有指定URI,URI有默认的scheme是content和file!如果过滤规则中指定了scheme的话那就不是默认的scheme了。 84 | ``` 85 | //URI有默认值 86 | 87 | 88 | ... 89 | 90 | //URI默认值被覆盖 91 | 92 | 93 | ... 94 | 95 | ``` 96 | 97 | **如果要为Intent指定完整的data,必须要调用`setDataAndType`方法!** 98 | 不能先调用setData然后调用setType,因为这两个方法会彼此清除对方的值。 99 | ``` 100 | intent.setDataAndType(Uri.parse("file://abc"), "image/png"); 101 | ``` 102 | 103 | data的下面两种写法作用是一样的: 104 | ``` 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | ``` 114 | 115 | **如何判断是否有Activity能够匹配我们的隐式Intent?** 116 | (1)`PackageManager`的`resolveActivity`方法或者Intent的`resolveActivity`方法:如果找不到就会返回null 117 | (2)PackageManager的`queryIntentActivities`方法:它返回所有成功匹配的Activity信息 118 | 针对Service和BroadcastReceiver等组件,PackageManager同样提供了类似的方法去获取成功匹配的组件信息,例如`queryIntentServices`、`queryBroadcastReceivers`等方法 119 | 120 | 有一类action和category比较重要,它们在一起用来标明这是一个入口Activity,并且会出现在系统的应用列表中。 121 | ``` 122 | 123 | 124 | 125 | 126 | ``` 127 | 128 | OK,本章结束,谢谢阅读。 129 | 130 | 131 | -------------------------------------------------------------------------------- /ReadingNotes-ArtOfAndroidDevelopment/2015-11-30-Art-of-Android-Development-Reading-Notes-2.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Art of Android Development Reading Notes 2 4 | categories: android 5 | date: 2015-12-05 11:50:54 6 | --- 7 | 《Android开发艺术探索》读书笔记 (2) 第2章 IPC机制 8 | 9 | ### 第2章 IPC机制 10 | #### 2.1 Android IPC简介 11 | (1)任何一个操作系统都需要有相应的IPC机制,Linux上可以通过命名通道、共享内存、信号量等来进行进程间通信。Android系统不仅可以使用Binder机制来实现IPC,还可以使用Socket实现任意两个终端之间的通信。 12 | 13 | #### 2.2 Android中的多进程模式 14 | (1)通过给四大组件指定`android:process`属性就可以开启多进程模式,默认进程的进程名是包名packageName,进程名以`:`开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以`:`开头的进程属于全局进程,其他应用通过`ShareUID`方法可以和它跑在同一个进程中。 15 | ``` 16 | android:process=":xyz" //进程名是 packageName:xyz 17 | android:process="aaa.bbb.ccc" //进程名是 aaa.bbb.ccc 18 | ``` 19 | (2)Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。**两个应用通过ShareUID跑在同一个进程中是有要求的,需要这两个应用有相同的ShareUID并且签名相同才可以。** 在这种情况下,它们可以相互访问对方的私有数据,比如data目录、组件信息等,不管它们是否跑在同一个进程中。如果它们跑在同一个进程中,还可以共享内存数据,它们看起来就像是一个应用的两个部分。 20 | (3)android系统会为每个进程分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,所以不同的虚拟机中访问同一个类的对象会产生多个副本。 21 | (4)使用多进程容易造成以下几个问题: 22 | 1.静态成员和单例模式完全失效; 23 | 2.线程同步机制完全失效:无论锁对象还是锁全局对象都无法保证线程同步; 24 | 3.`SharedPreferences`的可靠性下降:SharedPreferences不支持并发读写; 25 | 4.Application会多次创建:当一个组件跑在一个新的进程的时候,系统要在创建新的进程的同时分配独立的虚拟机,应用会重新启动一次,也就会创建新的Application。**运行在同一个进程中的组件是属于同一个虚拟机和同一个Application。** 26 | **同一个应用的不同组件,如果它们运行在不同进程中,那么和它们分别属于两个应用没有本质区别。** 27 | 28 | #### 2.3 IPC基础概念介绍 29 | (1)`Serializable`接口是Java中为对象提供标准的序列化和反序列化操作的接口,而`Parcelable`接口是Android提供的序列化方式的接口。 30 | (2)`serialVersionUId`是一串long型数字,主要是用来辅助序列化和反序列化的,原则上序列化后的数据中的serialVersionUId只有和当前类的serialVersionUId相同才能够正常地被反序列化。 31 | **serialVersionUId的详细工作机制**:序列化的时候系统会把当前类的serialVersionUId写入`序列化的文件`中,当反序列化的时候系统会去检测文件中的serialVersionUId,看它是否和当前类的serialVersionUId一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则说明版本不一致无法正常反序列化。**一般来说,我们应该手动指定serialVersionUId的值。** 32 | 1.静态成员变量属于类不属于对象,所以不参与序列化过程; 33 | 2.声明为`transient`的成员变量不参与序列化过程。 34 | (3)`Parcelable`接口内部包装了可序列化的数据,可以在Binder中自由传输,`Parcelable`主要用在`内存序列化`上,可以直接序列化的有Intent、Bundle、Bitmap以及List和Map等等,下面是一个实现了`Parcelable`接口的示例 35 | ``` 36 | public class Book implements Parcelable { 37 | public int bookId; 38 | public String bookName; 39 | public Book() { 40 | } 41 | 42 | public Book(int bookId, String bookName) { 43 | this.bookId = bookId; 44 | this.bookName = bookName; 45 | } 46 | 47 | //“内容描述”,如果含有文件描述符返回1,否则返回0,几乎所有情况下都是返回0 48 | public int describeContents() { 49 | return 0; 50 | } 51 | 52 | //实现序列化操作,flags标识只有0和1,1表示标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为0 53 | public void writeToParcel(Parcel out, int flags) { 54 | out.writeInt(bookId); 55 | out.writeString(bookName); 56 | } 57 | 58 | //实现反序列化操作 59 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 60 | //从序列化后的对象中创建原始对象 61 | public Book createFromParcel(Parcel in) { 62 | return new Book(in); 63 | } 64 | public Book[] newArray(int size) {//创建指定长度的原始对象数组 65 | return new Book[size]; 66 | } 67 | }; 68 | 69 | private Book(Parcel in) { 70 | bookId = in.readInt(); 71 | bookName = in.readString(); 72 | } 73 | 74 | } 75 | ``` 76 | (4)`Binder`是Android中的一个类,它实现了`IBinder`接口。从IPC角度看,Binder是Android中一种跨进程通信的方式;Binder还可以理解为虚拟的物理设备,它的设备驱动是`/dev/binder`;从Framework层角度看,Binder是ServiceManager连接各种`Manager`和相应的`ManagerService`的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当`bindService`的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。 77 | 在Android开发中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间通信,较为简单;而Messenger的底层其实是AIDL,正是Binder的核心工作机制。 78 | (5)aidl工具根据aidl文件自动生成的java接口的解析:首先,它声明了几个接口方法,同时还声明了几个整型的id用于标识这些方法,id用于标识在`transact`过程中客户端所请求的到底是哪个方法;接着,它声明了一个内部类`Stub`,这个Stub就是一个`Binder`类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的`transact`过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub内部的代理类`Proxy`来完成。 79 | **所以,这个接口的核心就是它的内部类Stub和Stub内部的代理类Proxy。** 下面分析其中的方法: 80 | 1.`asInterface(android.os.IBinder obj)`:用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,**如果客户端和服务端是在同一个进程中,那么这个方法返回的是服务端的`Stub`对象本身,否则返回的是系统封装的`Stub.Proxy`对象。** 81 | 2.`asBinder`:返回当前Binder对象。 82 | 3.`onTransact`:这个方法运行在**服务端中的Binder线程池**中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。 83 | 这个方法的原型是`public Boolean onTransact(int code, Parcelable data, Parcelable reply, int flags)` 84 | 服务端通过`code`可以知道客户端请求的目标方法,接着从`data`中取出所需的参数,然后执行目标方法,执行完毕之后,将结果写入到`reply`中。如果此方法返回false,说明客户端的请求失败,利用这个特性可以做权限验证(即验证是否有权限调用该服务)。 85 | 4.`Proxy#[Method]`:代理类中的接口方法,这些方法运行在客户端,当客户端远程调用此方法时,它的内部实现是:首先创建该方法所需要的参数,然后把方法的参数信息写入到`_data`中,接着调用`transact`方法来发起RPC请求,同时当前线程挂起;然后服务端的`onTransact`方法会被调用,直到RPC过程返回后,当前线程继续执行,并从`_reply`中取出RPC过程的返回结果,最后返回`_reply`中的数据。 86 | 87 | **如果搞清楚了自动生成的接口文件的结构和作用之后,其实是可以不用通过AIDL而直接实现Binder的,[主席写的示例代码](https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_2/src/com/ryg/chapter_2/manualbinder/BookManagerImpl.java)** 88 | 89 | (6)Binder的两个重要方法`linkToDeath`和`unlinkToDeath` 90 | Binder运行在服务端,如果由于某种原因服务端异常终止了的话会导致客户端的远程调用失败,所以Binder提供了两个配对的方法`linkToDeath`和`unlinkToDeath`,通过`linkToDeath`方法可以给Binder设置一个死亡代理,当Binder死亡的时候客户端就会收到通知,然后就可以重新发起连接请求从而恢复连接了。 91 | **如何给Binder设置死亡代理呢?** 92 | 1.声明一个`DeathRecipient`对象,`DeathRecipient`是一个接口,其内部只有一个方法`bindeDied`,实现这个方法就可以在Binder死亡的时候收到通知了。 93 | ``` 94 | private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { 95 | @Override 96 | public void binderDied() { 97 | if (mRemoteBookManager == null) return; 98 | mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0); 99 | mRemoteBookManager = null; 100 | // TODO:这里重新绑定远程Service 101 | } 102 | }; 103 | ``` 104 | 2.在客户端绑定远程服务成功之后,给binder设置死亡代理 105 | ``` 106 | mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0); 107 | ``` 108 | 109 | #### 2.4 Android中的IPC方式 110 | (1)**使用Bundle**:Bundle实现了Parcelable接口,Activity、Service和Receiver都支持在Intent中传递Bundle数据。 111 | 112 | (2)**使用文件共享**:这种方式简单,适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读写的问题。 113 | `SharedPreferences`是一个特例,虽然它也是文件的一种,但是由于系统对它的读写有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读写就变得不可靠,当面对高并发读写访问的时候,有很大几率会丢失数据,因此,不建议在进程间通信中使用SharedPreferences。 114 | 115 | (3)**使用Messenger**:`Messenger`是一种轻量级的IPC方案,它的底层实现就是AIDL。Messenger是以串行的方式处理请求的,即服务端只能一个个处理,不存在并发执行的情形,详细的示例见原书。 116 | 117 | (4)**使用AIDL** 118 | 大致流程:首先建一个Service和一个AIDL接口,接着创建一个类继承自AIDL接口中的Stub类并实现Stub类中的抽象方法,在Service的onBind方法中返回这个类的对象,然后客户端就可以绑定服务端Service,建立连接后就可以访问远程服务端的方法了。 119 | 1.AIDL支持的数据类型:基本数据类型、`String`和`CharSequence`、`ArrayList`、`HashMap`、`Parcelable`以及`AIDL`; 120 | 2.某些类即使和AIDL文件在同一个包中也要显式import进来; 121 | 3.AIDL中除了基本数据类,其他类型的参数都要标上方向:`in`、`out`或者`inout`; 122 | 4.AIDL接口中支持方法,不支持声明静态变量; 123 | 5.为了方便AIDL的开发,建议把所有和AIDL相关的类和文件全部放入同一个包中,这样做的好处是,当客户端是另一个应用的时候,可以直接把整个包复制到客户端工程中。 124 | 6.`RemoteCallbackList`是系统专门提供的用于删除跨进程Listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口,因为所有的AIDL接口都继承自`IInterface`接口。 125 | 126 | (5)**使用ContentProvider** 127 | 1.ContentProvider主要以表格的形式来组织数据,并且可以包含多个表; 128 | 2.ContentProvider还支持文件数据,比如图片、视频等,系统提供的`MediaStore`就是文件类型的ContentProvider; 129 | 3.ContentProvider对底层的数据存储方式没有任何要求,可以是SQLite、文件,甚至是内存中的一个对象都行; 130 | 4.要观察ContentProvider中的数据变化情况,可以通过`ContentResolver`的`registerContentObserver`方法来注册观察者; 131 | 132 | (6)**使用Socket** 133 | Socket是网络通信中“套接字”的概念,分为流式套接字和用户数据包套接字两种,分别对应网络的传输控制层的TCP和UDP协议。 134 | 135 | #### 2.5 Binder连接池 136 | (1)当项目规模很大的时候,创建很多个Service是不对的做法,因为service是系统资源,太多的service会使得应用看起来很重,所以最好是将所有的AIDL放在同一个Service中去管理。整个工作机制是:每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务端提供自己的唯一标识和其对应的Binder对象;对于服务端来说,只需要一个Service,服务端提供一个`queryBinder`接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。 137 | **Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service去执行,从而避免了重复创建Service的过程。** 138 | (2)作者实现的Binder连接池[`BinderPool`的实现源码](https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_2/src/com/ryg/chapter_2/binderpool/BinderPool.java),建议在AIDL开发工作中引入BinderPool机制。 139 | 140 | #### 2.6 选用合适的IPC方式 141 | 142 | ![img](https://hujiaweibujidao.github.io/images/androidart_ipc.png) 143 | 144 | OK,本章结束,谢谢阅读。 145 | 146 | 147 | -------------------------------------------------------------------------------- /ReadingNotes-ArtOfAndroidDevelopment/2015-12-01-Art-of-Android-Development-Reading-Notes-3.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Art of Android Development Reading Notes 3 4 | categories: android 5 | date: 2015-12-01 10:50:54 6 | --- 7 | 《Android开发艺术探索》读书笔记 (3) 第3章 View的事件体系 8 | 9 | 本节和《Android群英传》中的**第五章Scroll分析**有关系,[建议先阅读该章的总结](https://hujiaweibujidao.github.io/blog/2015/11/26/Android-Heros-Reading-Notes-2/) 10 | 11 | ### 第3章 View的事件体系 12 | #### 3.1 View基本知识 13 | (1)view的层次结构:`ViewGroup`也是View; 14 | (2)view的位置参数:`top、left、right、bottom`,分别对应View的左上角和右下角相对于父容器的横纵坐标值。 15 | 从Android 3.0开始,view增加了`x、y、translationX、translationY`四个参数,这几个参数也是相对于父容器的坐标。x和y是左上角的坐标,而translationX和translationY是view左上角相对于父容器的偏移量,默认值都是0。 16 | x = left + translationX 17 | y = top + translationY 18 | (3)`MotionEvent`是指手指接触屏幕后所产生的一系列事件,主要有`ACTION_UP`、`ACTION_DOWN`、`ACTION_MOVE`等。正常情况下,一次手指触屏会触发一系列点击事件,主要有下面两种典型情况: 19 | 1.点击屏幕后离开,事件序列是`ACTION_DOWN` -> `ACTION_UP`; 20 | 2.点击屏幕后滑动一会再离开,事件序列是`ACTION_DOWN` -> `ACTION_MOVE` -> `ACTION_MOVE` -> ... -> `ACTION_UP`; 21 | 通过MotionEvent可以得到点击事件发生的x和y坐标,其中`getX`和`getY`是相对于当前view左上角的x和y坐标,`getRawX`和`getRawY`是相对于手机屏幕左上角的x和y坐标。 22 | (4)`TouchSlope`是系统所能识别出的可以被认为是滑动的最小距离,获取方式是`ViewConfiguration.get(getContext()).getScaledTouchSlope()`。 23 | (5)`VelocityTracker`用于追踪手指在滑动过程中的速度,包括水平和垂直方向上的速度。 24 | 速度计算公式: `速度 = (终点位置 - 起点位置) / 时间段` 25 | 速度可能为负值,例如当手指从屏幕右边往左边滑动的时候。此外,速度是单位时间内移动的像素数,单位时间不一定是1秒钟,可以使用方法`computeCurrentVelocity(xxx)`指定单位时间是多少,单位是ms。例如通过`computeCurrentVelocity(1000)`来获取速度,手指在1s中滑动了100个像素,那么速度是100,即`100`(像素/1000ms)。如果`computeCurrentVelocity(100)`来获取速度,在100ms内手指只是滑动了10个像素,那么速度是10,即`10`(像素/100ms)。 26 | 27 | VelocityTracker的使用方式: 28 | ``` 29 | //初始化 30 | VelocityTracker mVelocityTracker = VelocityTracker.obtain(); 31 | //在onTouchEvent方法中 32 | mVelocityTracker.addMovement(event); 33 | //获取速度 34 | mVelocityTracker.computeCurrentVelocity(1000); 35 | float xVelocity = mVelocityTracker.getXVelocity(); 36 | //重置和回收 37 | mVelocityTracker.clear(); //一般在MotionEvent.ACTION_UP的时候调用 38 | mVelocityTracker.recycle(); //一般在onDetachedFromWindow中调用 39 | ``` 40 | (6)`GestureDetector`用于辅助检测用户的单击、滑动、长按、双击等行为。GestureDetector的使用比较简单,主要也是辅助检测常见的触屏事件。**作者建议:如果只是监听滑动相关的事件在onTouchEvent中实现;如果要监听双击这种行为的话,那么就使用GestureDetector。** 41 | (7)`Scroller`分析:详细内容可以参见[《Android群英传》读书笔记 (2) 第五章 Scroll分析](https://hujiaweibujidao.github.io/blog/2015/11/26/Android-Heros-Reading-Notes-2/) 42 | 43 | #### 3.2 View的滑动 44 | (1)常见的实现view的滑动的方式有三种: 45 | 第一种是通过view本身提供的scrollTo和scrollBy方法:操作简单,适合对view内容的滑动; 46 | 第二种是通过动画给view施加平移效果来实现滑动:操作简单,适用于没有交互的view和实现复杂的动画效果; 47 | 第三种是通过改变view的LayoutParams使得view重新布局从而实现滑动:操作稍微复杂,适用于有交互的view。 48 | 以上三种方法的详情可以参考阅读[《Android群英传》读书笔记 (2)](https://hujiaweibujidao.github.io/blog/2015/11/26/Android-Heros-Reading-Notes-2/)中的内容,此处不再细述。 49 | (2)**scrollTo和scrollBy方法只能改变view内容的位置而不能改变view在布局中的位置。** scrollBy是基于当前位置的相对滑动,而scrollTo是基于所传参数的绝对滑动。通过View的`getScrollX`和`getScrollY`方法可以得到滑动的距离。 50 | (3)使用动画来移动view主要是操作view的translationX和translationY属性,既可以使用传统的view动画,也可以使用属性动画,使用后者需要考虑兼容性问题,如果要兼容Android 3.0以下版本系统的话推荐使用[nineoldandroids](http://nineoldandroids.com/)。 51 | 使用动画还存在一个交互问题:**在android3.0以前的系统上,view动画和属性动画,新位置均无法触发点击事件,同时,老位置仍然可以触发单击事件。从3.0开始,属性动画的单击事件触发位置为移动后的位置,view动画仍然在原位置。** 52 | (4)动画兼容库nineoldandroids中的`ViewHelper`类提供了很多的get/set方法来为属性动画服务,例如`setTranslationX`和`setTranslationY`方法,这些方法是没有版本要求的。 53 | 54 | #### 3.3 弹性滑动 55 | (1)**Scroller的工作原理:Scroller本身并不能实现view的滑动,它需要配合view的computeScroll方法才能完成弹性滑动的效果,它不断地让view重绘,而每一次重绘距滑动起始时间会有一个时间间隔,通过这个时间间隔Scroller就可以得出view的当前的滑动位置,知道了滑动位置就可以通过scrollTo方法来完成view的滑动。就这样,view的每一次重绘都会导致view进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动,这就是Scroller的工作原理。** 56 | (2)使用延时策略来实现弹性滑动,它的核心思想是通过发送一系列延时消息从而达到一种渐进式的效果,具体来说可以使用Handler的`sendEmptyMessageDelayed(xxx)`或view的`postDelayed`方法,也可以使用线程的sleep方法。 57 | 58 | #### 3.4 view的事件分发机制 59 | (1)事件分发过程的三个重要方法 60 | `public boolean dispatchTouchEvent(MotionEvent ev)` 61 | 用来进行事件的分发。如果事件能够传递给当前view,那么此方法一定会被调用,返回结果受当前view的onTouchEvent和下级view的dispatchTouchEvent方法的影响,表示是否消耗当前事件。 62 | 63 | `public boolean onInterceptTouchEvent(MotionEvent event)` 64 | 在`dispatchTouchEvent`方法内部调用,用来判断是否拦截某个事件,如果当前view拦截了某个事件,那么在同一个事件序列当中,此方法不会再被调用,返回结果表示是否拦截当前事件。 65 | 若返回值为True事件会传递到自己的onTouchEvent(); 66 | 若返回值为False传递到子view的dispatchTouchEvent()。 67 | 68 | `public boolean onTouchEvent(MotionEvent event)` 69 | 在`dispatchTouchEvent`方法内部调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前view无法再次接收到事件。 70 | 若返回值为True,事件由自己处理,后续事件序列让其处理; 71 | 若返回值为False,自己不消耗事件,向上返回让其他的父容器的onTouchEvent接受处理。 72 | 73 | 三个方法的关系可以用下面的伪代码表示: 74 | ``` 75 | public boolean dispatchTouchEvent(MotionEvent ev) { 76 | boolean consume = false; 77 | if (onInterceptTouchEvent(ev)) { 78 | consume = onTouchEvent(ev); 79 | } else { 80 | consume = child.dispatchTouchEvent(ev); 81 | } 82 | return consume; 83 | } 84 | ``` 85 | (2)**OnTouchListener的优先级比onTouchEvent要高** 86 | 如果给一个view设置了OnTouchListener,那么OnTouchListener中的`onTouch`方法会被回调。这时事件如何处理还要看onTouch的返回值,如果返回false,那么当前view的onTouchEvent方法会被调用;如果返回true,那么onTouchEvent方法将不会被调用。 87 | **在onTouchEvent方法中,如果当前view设置了OnClickListener,那么它的onClick方法会被调用,所以OnClickListener的优先级最低。** 88 | (3)当一个点击事件发生之后,传递过程遵循如下顺序:Activity -> Window -> View。 89 | 如果一个view的onTouchEvent方法返回false,那么它的父容器的onTouchEvent方法将会被调用,依此类推,如果所有的元素都不处理这个事件,那么这个事件将会最终传递给Activity处理(调用Activity的onTouchEvent方法)。 90 | (4)正常情况下,一个事件序列只能被一个view拦截并消耗,因为一旦某个元素拦截了某个事件,那么同一个事件序列内的所有事件都会直接交给它处理,并且该元素的onInterceptTouchEvent方法不会再被调用了。 91 | (5)某个view一旦开始处理事件,如果它不消耗ACTION_DOWN事件,那么同一事件序列的其他事件都不会再交给它来处理,并且事件将重新交给它的父容器去处理(调用父容器的onTouchEvent方法);如果它消耗ACTION_DOWN事件,但是不消耗其他类型事件,那么这个点击事件会消失,父容器的onTouchEvent方法不会被调用,当前view依然可以收到后续的事件,但是这些事件最后都会传递给Activity处理。 92 | (6)ViewGroup默认不拦截任何事件,因为它的`onInterceptTouchEvent`方法默认返回false。view没有`onInterceptTouchEvent`方法,一旦有点击事件传递给它,那么它的`onTouchEvent`方法就会被调用。 93 | (7)View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(`clickable`和`longClickable`都为false)。view的`longClickable`默认是false的,`clickable`则不一定,Button默认是true,而TextView默认是false。 94 | (8)View的`enable`属性不影响onTouchEvent的默认返回值。哪怕一个view是`disable`状态,只要它的clickable或者longClickable有一个是true,那么它的onTouchEvent就会返回true。 95 | (9)事件传递过程总是先传递给父元素,然后再由父元素分发给子view,通过`requestDisallowInterceptTouchEvent`方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外,即当面对ACTION_DOWN事件时,ViewGroup总是会调用自己的onInterceptTouchEvent方法来询问自己是否要拦截事件。 96 | ViewGroup的dispatchTouchEvent方法中有一个标志位`FLAG_DISALLOW_INTERCEPT`,这个标志位就是通过子view调用`requestDisallowInterceptTouchEvent`方法来设置的,一旦设置为true,那么ViewGroup不会拦截该事件。 97 | (10)以上结论均可以在书中的源码解析部分得到解释。Window的实现类为`PhoneWindow`,获取Activity的contentView的方法 98 | ``` 99 | ((ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0); 100 | ``` 101 | 102 | #### 3.5 view的滑动冲突 103 | (1)常见的滑动冲突的场景: 104 | 1.外部滑动方向和内部滑动方向不一致,例如viewpager中包含listview; 105 | 2.外部滑动方向和内部滑动方向一致,例如viewpager的单页中存在可以滑动的bannerview; 106 | 3.上面两种情况的嵌套,例如viewpager的单个页面中包含了bannerview和listview。 107 | (2)滑动冲突处理规则 108 | 可以根据滑动距离和水平方向形成的夹角;或者根绝水平和竖直方向滑动的距离差;或者两个方向上的速度差等 109 | (3)解决方式 110 | 1.外部拦截法:点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要就不拦截。该方法需要重写父容器的`onInterceptTouchEvent`方法,在内部做相应的拦截即可,其他均不需要做修改。 111 | 伪代码如下: 112 | ``` 113 | public boolean onInterceptTouchEvent(MotionEvent event) { 114 | boolean intercepted = false; 115 | int x = (int) event.getX(); 116 | int y = (int) event.getY(); 117 | 118 | switch (event.getAction()) { 119 | case MotionEvent.ACTION_DOWN: { 120 | intercepted = false; 121 | break; 122 | } 123 | case MotionEvent.ACTION_MOVE: { 124 | int deltaX = x - mLastXIntercept; 125 | int deltaY = y - mLastYIntercept; 126 | if (父容器需要拦截当前点击事件的条件,例如:Math.abs(deltaX) > Math.abs(deltaY)) { 127 | intercepted = true; 128 | } else { 129 | intercepted = false; 130 | } 131 | break; 132 | } 133 | case MotionEvent.ACTION_UP: { 134 | intercepted = false; 135 | break; 136 | } 137 | default: 138 | break; 139 | } 140 | 141 | mLastXIntercept = x; 142 | mLastYIntercept = y; 143 | 144 | return intercepted; 145 | } 146 | ``` 147 | 148 | 2.内部拦截法:父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器来处理。这种方法和Android中的事件分发机制不一致,需要配合`requestDisallowInterceptTouchEvent`方法才能正常工作。 149 | ``` 150 | public boolean dispatchTouchEvent(MotionEvent event) { 151 | int x = (int) event.getX(); 152 | int y = (int) event.getY(); 153 | 154 | switch (event.getAction()) { 155 | case MotionEvent.ACTION_DOWN: {] 156 | getParent().requestDisallowInterceptTouchEvent(true); 157 | break; 158 | } 159 | case MotionEvent.ACTION_MOVE: { 160 | int deltaX = x - mLastX; 161 | int deltaY = y - mLastY; 162 | if (当前view需要拦截当前点击事件的条件,例如:Math.abs(deltaX) > Math.abs(deltaY)) { 163 | getParent().requestDisallowInterceptTouchEvent(false); 164 | } 165 | break; 166 | } 167 | case MotionEvent.ACTION_UP: { 168 | break; 169 | } 170 | default: 171 | break; 172 | } 173 | 174 | mLastX = x; 175 | mLastY = y; 176 | return super.dispatchTouchEvent(event); 177 | } 178 | ``` 179 | 书中对这两种拦截法写了两个例子,感兴趣阅读源码看下,[外部拦截法使用示例链接](https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_3/src/com/ryg/chapter_3/ui/HorizontalScrollViewEx.java)和[内部拦截法使用示例链接](https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_3/src/com/ryg/chapter_3/ui/ListViewEx.java)。 180 | 181 | OK,本章结束,谢谢阅读。 182 | 183 | 184 | -------------------------------------------------------------------------------- /ReadingNotes-ArtOfAndroidDevelopment/2015-12-02-Art-of-Android-Development-Reading-Notes-4.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Art of Android Development Reading Notes 4 4 | categories: android 5 | date: 2015-12-01 14:50:54 6 | --- 7 | 《Android开发艺术探索》读书笔记 (4) 第4章 View的工作原理 8 | 9 | 本节和《Android群英传》中的**第3章Android控件架构与自定义控件详解**有关系,[建议先阅读该章的总结](https://hujiaweibujidao.github.io/blog/2015/11/26/Android-Heros-Reading-Notes-2/) 10 | 11 | ### 第4章 View的工作原理 12 | #### 4.1 初始ViewRoot和DecorView 13 | (1)`ViewRoot`对应`ViewRootImpl`类,它是连接`WindowManager`和`DecorView`的纽带,View的三大流程均通过ViewRoot来完成。 14 | (2)`ActivityThread`中,Activity创建完成后,会将DecorView添加到Window中,同时创建ViewRootImpl对象,并建立两者的关联。 15 | (3)View的绘制流程从ViewRoot的`performTraversals`方法开始,经过`measure`、`layout`和`draw`三大流程。 16 | (4)`performMeasure`方法中会调用`measure`方法,在`measure`方法中又会调用`onMeasure`方法,在onMeasure方法中会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素了,这样就完成了一次measure过程,layout和draw的过程类似。 (书中175页画出详细的图示) 17 | (5)measure过程决定了view的宽高,在几乎所有的情况下这个宽高都等同于view最终的宽高。layout过程决定了view的四个顶点的坐标和view实际的宽高,通过`getWidth`和`getHeight`方法可以得到最终的宽高。draw过程决定了view的显示。 18 | (6)DecorView其实是一个FrameLayout,其中包含了一个竖直方向的LinearLayout,上面是标题栏,下面是内容区域(id为`android.R.id.content`)。 19 | 20 | 4.2 理解MeasureSpec 21 | (1)`MeasureSpec`和`LayoutParams`的对应关系 22 | 在view测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽高。 23 | **MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定view的MeasureSpec,从而进一步确定view的宽高。对于DecorView,它的MeasureSpec由窗口的尺寸和其自身的LayoutParams来决定;对于普通view,它的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定。** 24 | (2)普通view的MeasureSpec的创建规则 (书中182页列出详细的表格) 25 | 当view采用固定宽高时,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精确模式,并且大小是LayoutParams中的大小。 26 | 当view的宽高是`match_parent`时,如果父容器的模式是精确模式,那么view也是精确模式,并且大小是父容器的剩余空间;如果父容器是最大模式,那么view也是最大模式,并且大小是不会超过父容器的剩余空间。 27 | 当view的宽高是`wrap_content`时,不管父容器的模式是精确模式还是最大模式,view的模式总是最大模式,并且大小不超过父容器的剩余空间。 28 | 29 | 4.3 view的工作流程 30 | (1)view的measure过程和Activity的生命周期方法不是同步执行的,因此无法保证Activity执行了`onCreate`、`onStart`、`onResume`时某个view已经测量完毕了。如果view还没有测量完毕,那么获得的宽高就都是0。下面是四种解决该问题的方法: 31 | 1.`Activity/View # onWindowFocusChanged`方法 32 | `onWindowFocusChanged`方法表示view已经初始化完毕了,宽高已经准备好了,这个时候去获取宽高是没问题的。**这个方法会被调用多次,当Activity继续执行或者暂停执行的时候,这个方法都会被调用。** 33 | 2.`view.post(runnable)` 34 | 通过post将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,view也已经初始化好了。 35 | 3.`ViewTreeObserver` 36 | 使用`ViewTreeObserver`的众多回调方法可以完成这个功能,比如使用`onGlobalLayoutListener`接口,当view树的状态发生改变或者view树内部的view的可见性发生改变时,`onGlobalLayout`方法将被回调。**伴随着view树的状态改变,这个方法也会被多次调用。** 37 | 4.`view.measure(int widthMeasureSpec, int heightMeasureSpec)` 38 | 通过手动对view进行measure来得到view的宽高,这个要根据view的LayoutParams来处理: 39 | `match_parent`:无法measure出具体的宽高; 40 | `wrap_content`:如下measure,设置最大值 41 | ``` 42 | int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST); 43 | int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST); 44 | view.measure(widthMeasureSpec, heightMeasureSpec); 45 | ``` 46 | 精确值:例如100px 47 | ``` 48 | int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY); 49 | int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY); 50 | view.measure(widthMeasureSpec, heightMeasureSpec); 51 | ``` 52 | (2)**在view的默认实现中,view的测量宽高和最终宽高是相等的,只不过测量宽高形成于measure过程,而最终宽高形成于layout过程。** 53 | (3)draw过程大概有下面几步: 54 | 1.绘制背景:`background.draw(canvas)`; 55 | 2.绘制自己:`onDraw()`; 56 | 3.绘制children:`dispatchDraw`; 57 | 4.绘制装饰:`onDrawScrollBars`。 58 | 59 | 4.4 自定义view 60 | (1)继承view重写onDraw方法需要自己支持`wrap_content`,并且`padding`也要自己处理。继承特定的View例如TextView不需要考虑。 61 | (2)尽量不要在View中使用Handler,因为view内部本身已经提供了`post`系列的方法,完全可以替代Handler的作用。 62 | (3)view中如果有线程或者动画,需要在`onDetachedFromWindow`方法中及时停止。 63 | (4)处理好view的滑动冲突情况。 64 | 65 | 接下来是原书中的自定义view的示例,推荐阅读[源码](https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_4/src/com/ryg/chapter_4/ui/CircleView.java)。 66 | 67 | OK,本章结束,谢谢阅读。 68 | 69 | 70 | -------------------------------------------------------------------------------- /ReadingNotes-ArtOfAndroidDevelopment/2015-12-03-Art-of-Android-Development-Reading-Notes-5.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Art of Android Development Reading Notes 5 4 | categories: android 5 | date: 2015-12-01 18:50:54 6 | --- 7 | 《Android开发艺术探索》读书笔记 (5) 第5章 理解RemoteViews 8 | 9 | ### 第5章 理解RemoteViews 10 | #### 5.1 `RemoteViews`的应用 11 | (1)RemoteViews表示的是一个view结构,它可以在其他进程中显示。由于它在其他进程中显示,为了能够更新它的界面,RemoteViews提供了一组基础的操作用于跨进程更新它的界面。 12 | (2)RemoteViews主要用于通知栏通知和桌面小部件的开发,通知栏通知是通过`NotificationManager`的`notify`方法来实现的;桌面小部件是通过`AppWidgetProvider`来实现的,它本质上是一个广播(BroadcastReceiver)。这两者的界面都是运行在`SystemServer`进程中。 13 | (3)RemoteViews在Notification中的应用示例 14 | ``` 15 | Notification notification = new Notification(); 16 | notification.icon = R.drawable.ic_launcher; 17 | notification.tickerText = "hello world"; 18 | notification.when = System.currentTimeMillis(); 19 | notification.flags = Notification.FLAG_AUTO_CANCEL; 20 | Intent intent = new Intent(this, DemoActivity_1.class); 21 | intent.putExtra("sid", "" + sId); 22 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); 23 | 24 | RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_notification); 25 | remoteViews.setTextViewText(R.id.msg, "chapter_5: " + sId);//设置textview的显示文本 26 | remoteViews.setImageViewResource(R.id.icon, R.drawable.icon1); 27 | PendingIntent openActivity2PendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, DemoActivity_2.class), PendingIntent.FLAG_UPDATE_CURRENT); 28 | remoteViews.setOnClickPendingIntent(R.id.open_activity2, openActivity2PendingIntent);//给图片添加点击事件 29 | notification.contentView = remoteViews; 30 | notification.contentIntent = pendingIntent; 31 | NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); 32 | manager.notify(sId, notification); 33 | ``` 34 | (4)RemoteViews在桌面小部件中的应用 35 | 1.定义小部件界面; 36 | 2.定义小部件配置信息:其中`updatePeriodMillis`定义小工具的自动更新周期,单位为ms。 37 | ``` 38 | 39 | 44 | 45 | ``` 46 | 3.定义小部件的实现类:书中的示例实现了一个显示一张图片的小部件,每次点击小部件的时候图片就会旋转一周; 47 | ``` 48 | public class MyAppWidgetProvider extends AppWidgetProvider { 49 | 50 | public static final String TAG = "MyAppWidgetProvider"; 51 | public static final String CLICK_ACTION = "com.ryg.chapter_5.action.CLICK"; 52 | 53 | public MyAppWidgetProvider() { 54 | super(); 55 | } 56 | 57 | @Override 58 | public void onReceive(final Context context, Intent intent) { 59 | super.onReceive(context, intent); 60 | Log.i(TAG, "onReceive : action = " + intent.getAction()); 61 | 62 | // 这里判断是自己的action,做自己的事情,比如小工具被点击了要干啥,这里是做一个动画效果 63 | if (intent.getAction().equals(CLICK_ACTION)) { 64 | Toast.makeText(context, "clicked it", Toast.LENGTH_SHORT).show(); 65 | 66 | new Thread(new Runnable() { 67 | @Override 68 | public void run() { 69 | Bitmap srcbBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.icon1); 70 | AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); 71 | for (int i = 0; i < 37; i++) { 72 | float degree = (i * 10) % 360; 73 | RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget); 74 | remoteViews.setImageViewBitmap(R.id.imageView1, rotateBitmap(context, srcbBitmap, degree)); 75 | Intent intentClick = new Intent(); 76 | intentClick.setAction(CLICK_ACTION); 77 | PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intentClick, 0); 78 | remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent); 79 | appWidgetManager.updateAppWidget(new ComponentName(context, MyAppWidgetProvider.class),remoteViews); 80 | SystemClock.sleep(30); 81 | } 82 | } 83 | }).start(); 84 | } 85 | } 86 | 87 | /** 88 | * 每次窗口小部件被点击更新都调用一次该方法 89 | */ 90 | @Override 91 | public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { 92 | super.onUpdate(context, appWidgetManager, appWidgetIds); 93 | Log.i(TAG, "onUpdate"); 94 | 95 | final int counter = appWidgetIds.length; 96 | Log.i(TAG, "counter = " + counter); 97 | for (int i = 0; i < counter; i++) { 98 | int appWidgetId = appWidgetIds[i]; 99 | onWidgetUpdate(context, appWidgetManager, appWidgetId); 100 | } 101 | } 102 | 103 | /** 104 | * 窗口小部件更新 105 | */ 106 | private void onWidgetUpdate(Context context, AppWidgetManager appWidgeManger, int appWidgetId) { 107 | Log.i(TAG, "appWidgetId = " + appWidgetId); 108 | RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget); 109 | 110 | // "窗口小部件"点击事件发送的Intent广播 111 | Intent intentClick = new Intent(); 112 | intentClick.setAction(CLICK_ACTION); 113 | PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intentClick, 0); 114 | remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent); 115 | appWidgeManger.updateAppWidget(appWidgetId, remoteViews); 116 | } 117 | 118 | private Bitmap rotateBitmap(Context context, Bitmap srcbBitmap, float degree) { 119 | Matrix matrix = new Matrix(); 120 | matrix.reset(); 121 | matrix.setRotate(degree); 122 | return Bitmap.createBitmap(srcbBitmap, 0, 0, srcbBitmap.getWidth(), srcbBitmap.getHeight(), matrix, true); 123 | } 124 | } 125 | ``` 126 | 4.在AndroidManifest.xml文件中声明小部件 127 | 下面的示例中包含了两个action,第一个action用于识别小部件的单击行为,而第二个action是作为小部件必须存在的action `android.appwidget.action.APPWIDGET_UPDATE`,如果不加那么就无法显示小部件。 128 | ``` 129 | 130 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | ``` 141 | 142 | (5)AppWidgetProvider会自动根据广播的action通过`onReceive`方法来自动分发广播,也就是调用下面不同的方法: 143 | `onEnable`:当小部件**第一次**添加到桌面时调用,小部件可以添加多次但是只在第一次添加的时候调用; 144 | `onUpdate`:小部件被添加时或者每次小部件更新时都会调用一次该方法,每个周期小部件都会自动更新一次; 145 | `onDeleted`:每删除一次小部件就调用一次; 146 | `onDisabled`:当**最后一个**该类型的小部件被删除时调用该方法; 147 | `onReceive`:这是广播内置的方法,用于分发具体的事件给其他方法,所以该方法一般要调用`super.onReceive(context, intent);` 如果自定义了其他action的广播,就可以在调用了父类方法之后进行判断,如上面代码所示。 148 | (6)`PendingIntent`表示一种处于Pending状态的Intent,pending表示的是即将发生的意思,它是在将来的某个不确定的时刻放生,而Intent是立刻发生。 149 | (7)PendingIntent支持三种待定意图:启动Activity(getActivity),启动Service(getService),发送广播(getBroadcast)。 150 | `PendingIntent.getActivity(Context context, in requestCode, Intent intent, int flags)` 151 | 获得一个PendingIntent,当待定意图发生时,效果相当于Context.startActivity(intent)。 152 | 第二个参数`requestCode`是PendingIntent发送方的请求码,多数情况下设为0即可,另外requestCode会影响到flags的效果。 153 | **PendingIntent的匹配规则:如果两个PendingIntent内部的Intent相同,并且requestCode也相同,那么这两个PendingIntent就是相同的。** 154 | **Intent的匹配规则:如果两个Intent的ComponentName和intent-filter都相同,那么这两个Intent就是相同的,Extras不参与Intent的匹配过程。** 155 | 第四个参数flags常见的类型有:`FLAG_ONE_SHOT`、`FLAG_NO_CREATE`、`FLAG_CANCEL_CURRENT`、`FLAG_UPDATE_CURRENT`。 156 | `FLAG_ONE_SHOT`:当前描述的PendingIntent只能被调用一次,然后它就会被自动cancel。如果后续还有相同的PendingIntent,那么它们的send方法就会调用失败。对于通知栏消息来说,如果采用这个flag,那么同类的通知只能使用一次,后续的通知单击后将无法打开。 157 | `FLAG_NO_CREATE`:当前描述的PendingIntent不会主动创建,如果当前PendingIntent之前不存在,那么getActivity、getService和getBroadcast方法会直接返回null,即获取PendingIntent失败。这个标志位使用很少。 158 | `FLAG_CANCEL_CURRENT`:当前描述的PendingIntent如果已经存在,那么它们都会被cancel,然后系统会创建一个新的PendingIntent。对于通知栏消息来说,那些被cancel的通知单击后将无法打开。 159 | `FLAG_UPDATE_CURRENT`:当前描述的PendingIntent如果已经存在,那么它们都会被更新,即它们的Intent中的Extras会被替换成最新的。 160 | 161 | (8)分析`NotificationManager.nofify(id, notification)` [未测试,看着有点晕] 162 | 1.如果参数id是常量,那么多次调用notify只能弹出一个通知,后续的通知会把前面的通知完全替代掉; 163 | 2.如果参数id每次都不同,那么当PendingIntent不匹配的时候,不管采用何种标志位,这些通知之间不会相互干扰; 164 | 3.如果参数id每次都不同,且PendingIntent匹配的时候,那就要看标志位: 165 | 如果标志位是FLAG_ONE_SHOT,那么后续的通知中的PendingIntent会和第一条通知保持完全一致,包括其中的Extras,单击任何一条通知后,剩下的通知均无法再打开,当所有的通知都被清除后,会再次重复这个过程; 166 | 如果标志位是FLAG_CANCEL_CURRENT,那么只有最新的通知可以打开,之前弹出的所有通知都无法打开; 167 | 如果标志位是FLAG_UPDATE_CURRENT,那么之前弹出的通知中的PendingIntent会被更新,最终它们和最新的一条通知保持完全一致,包括其中的Extras,并且这些通知都是可以打开的。 168 | 169 | #### 5.2 RemoteViews的内部机制 170 | (1)RemoteViews的构造方法 `public RemoteViews(String packageName, int layoutId)`,第一个参数是当前应用的包名,第二个参数是待加载的布局文件。 171 | (2)RemoteViews只支持部分布局和View组件,下面列举的组件的子类是不支持的 172 | 布局:`FrameLayout、LinearLayout、RelativeLayout、GridLayout` 173 | 组件:`Button、ImageButton、ImageView、TextView、ListView、GridView、ViewStub`等 174 | (3)RemoteViews提供了一系列的set方法完成view的设置,这是通过反射完成的调用的。 175 | 例如方法`setInt(int viewId, String methodName, int value)`就是反射调用view对象的名称为methodName的方法,传入参数value,同样的还有`setBoolean`、`setLong`等。 176 | 方法`setOnClickPendingIntent(int viewId, PendingIntent pi)`用来为view添加单击事件,事件类型只能为PendingIntent。 177 | (4)通知和小部件分别由`NotificationManager`和`AppWidgetManager`管理,而它们通过Binder分别和SystemServer进程中的`NotificationManagerService`和`AppWidgetManagerService`进行通信。所以,布局文件实际上是两个Service加载的,运行在SystemServer进程中。 178 | (5)RemoteViews实现了`Parcelable`接口,它会通过Binder传递到SystemServer进程,系统会根据RemoteViews中的包名信息获取到应用中的资源,从而完成布局文件的加载。 179 | (6)系统将view操作封装成`Action`对象,Action同样实现了Parcelable接口,通过Binder传递到SystemServer进程。远程进程通过RemoteViews的`apply`方法来进行view的更新操作,RemoteViews的apply方法内部则会去遍历所有的action对象并调用它们的apply方法来进行view的更新操作。 180 | 这样做的好处是不需要定义大量的Binder接口,其次批量执行RemoteViews中的更新操作提高了程序性能。 181 | (7)RemoteViews的`apply`和`reapply`方法的区别:`apply`方法会加载布局并更新界面,而`reapply`方法则只会更新界面。 182 | (8)`setOnClickPendingIntent`、`setPendingIntentTemplate`和`setOnClickFillIntent`的区别 183 | `setOnClickPendingIntent`用于给普通的view添加点击事件,但是不能给集合(ListView和StackView)中的view设置点击事件,因为开销太大了。如果需要给ListView和StackView中的item添加点击事件,需要结合`setPendingIntentTemplate`和`setOnClickFillIntent`一起使用。[并没有尝试(⊙o⊙)] 184 | 185 | #### 5.3 RemoteViews的意义 186 | RemoteViews的最大的意义是实现了跨进程的UI更新,这节作者实现了一个模拟通知栏效果的应用来演示跨进程的UI更新,[源码传送门](https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_5/src/com/ryg/chapter_5/MainActivity.java)。 187 | 188 | OK,本章结束,谢谢阅读。 189 | -------------------------------------------------------------------------------- /ReadingNotes-ArtOfAndroidDevelopment/2015-12-04-Art-of-Android-Development-Reading-Notes-6.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Art of Android Development Reading Notes 6 4 | categories: android 5 | date: 2015-11-30 11:50:54 6 | --- 7 | 《Android开发艺术探索》读书笔记 (6) 第6章 Android的Drawable 8 | 9 | 本节和《Android群英传》中的**第六章Android绘图机制与处理技巧**有关系,[建议先阅读该章的总结](https://hujiaweibujidao.github.io/blog/2015/11/27/Android-Heros-Reading-Notes-3/) 10 | 11 | ### 第6章 Android的Drawable 12 | #### 6.1 Drawable简介 13 | (1)Android的`Drawable`表示的是一种可以在`Canvas`上进行绘制的概念,它的种类很多,最常见的就是图片和颜色了。它有两个重要的优点:一是比自定义view要简单;二是非图片类型的drawable占用空间小,利于减小apk大小。 14 | (2)Drawable是抽象类,是所有Drawable对象的基类。 15 | (3)Drawable的内部宽/高可以通过`getIntrinsicWidth`和`getIntrinsicHeight`方法获取,但是并不是所有Drawable都有内部宽/高。图片Drawable的内部宽高就是图片的宽高,但是颜色Drawable就没有宽高的概念,它一般是作为view的背景,所以会去适应view的大小,这两个方法都是返回-1。 16 | 17 | #### 6.2 Drawable分类 18 | (1)BitmapDrawable和NinePatchDrawable 19 | ``` 20 | 21 | 31 | ``` 32 | 属性分析: 33 | `android:antialias`:是否开启图片抗锯齿功能。开启后会让图片变得平滑,同时也会一定程度上降低图片的清晰度,建议开启; 34 | `android:dither`:是否开启抖动效果。当图片的像素配置和手机屏幕像素配置不一致时,开启这个选项可以让高质量的图片在低质量的屏幕上还能保持较好的显示效果,建议开启。 35 | `android:filter`:是否开启过滤效果。当图片尺寸被拉伸或压缩时,开启过滤效果可以保持较好的显示效果,建议开启; 36 | `android:gravity`:当图片小于容器的尺寸时,设置此选项可以对图片进行定位。 37 | `android:tileMode`:平铺模式,有四种选项`["disabled" | "clamp" | "repeat" | "mirror"]`。当开启平铺模式后,gravity属性会被忽略。repeat是指水平和竖直方向上的平铺效果;mirror是指在水平和竖直方向上的镜面投影效果;clamp是指图片四周的像素会扩展到周围区域,这个比较特别。 38 | 39 | (2)ShapeDrawable 40 | ``` 41 | 42 | 45 | 51 | 61 | 66 | 69 | 71 | 76 | 77 | ``` 78 | 79 | `android:shape`:默认的shape是矩形,`line`和`ring`这两种形状需要通过``来制定线的宽度和颜色,否则看不到效果。 80 | `gradient`:`solid`表示纯色填充,而`gradient`表示渐变效果。`andoid:angle`指渐变的角度,默认为0,其值必须是45的倍数,0表示从左到右,90表示从下到上,其他类推。 81 | `padding`:这个表示的是包含它的view的空白,四个属性分别表示四个方向上的padding值。 82 | `size`:ShapeDrawable默认情况下是没有宽高的概念的,但是可以如果指定了size,那么这个时候shape就有了所谓的固有宽高,但是作为view的背景时,shape还是会被拉伸或者缩小为view的大小。 83 | 84 | (3)LayerDrawble 85 | 对应标签``,表示层次化的Drawable集合,实现一种叠加后的效果。 86 | 属性`android:top/left/right/bottom`表示drawable相对于view的上下左右的偏移量,单位为像素。 87 | 88 | (4)StateListDrawable 89 | 对应标签``,也是表示Drawable集合,每个drawable对应着view的一种状态。 90 | 一般来说,默认的item都应该放在selector的最后一条并且不附带任何的状态。 91 | 92 | (5)LevelListDrawable 93 | 对应标签``,同样是Drawable集合,每个drawable还有一个`level`值,根据不同的level,LevelListDrawable会切换不同的Drawable,level值范围从0到100000。 94 | 95 | (6)TransitionDrawable 96 | 对应标签``,它用于是吸纳两个Drawable之间的淡入淡出效果。 97 | ``` 98 | 99 | 100 | 101 | 102 | 103 | TransitionDrawable drawable = (TransitionDrawable) v.getBackground(); 104 | drawable.startTransition(5000); 105 | ``` 106 | 107 | (7)InsetDrawable 108 | 对应标签``,它可以将其他drawable内嵌到自己当中,并可以在四周留出一定的间距。当一个view希望自己的背景比自己的实际区域小的时候,可以采用InsetDrawable来实现。 109 | ``` 110 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | ``` 122 | 123 | (8)ScaleDrawable 124 | 对应标签``,它可以根据自己的level将指定的Drawable缩放到一定比例。如果level越大,那么内部的drawable看起来就越大。 125 | 126 | (9)ClipDrawable 127 | 对应标签``,它可以根据自己当前的level来裁剪另一个drawable,裁剪方向由`android:clipOrientation`和`andoid:gravity`属性来共同控制。level越大,表示裁剪的区域越小。 128 | ``` 129 | 133 | ``` 134 | 135 | #### 6.3 自定义Drawable 136 | (1)Drawable的工作核心就是`draw`方法,所以自定义drawable就是重写`draw`方法,当然还有`setAlpha`、`setColorFilter`和`getOpacity`这几个方法。当自定义Drawable有固有大小的时候最好重写`getIntrinsicWidth`和`getIntrinsicHeight`方法。 137 | (2)Drawable的内部大小不等于Drawable的实际区域大小,Drawable的实际区域大小可以通过它的`getBounds`方法来得到,一般来说它和view的尺寸相同。 138 | 139 | 其他学习资料: 140 | 1.[Android样式的开发:shape篇](http://keeganlee.me/post/android/20150830) 141 | 2.[Android样式的开发:drawable篇](http://keeganlee.me/post/android/20150916) 142 | 3.[Android样式的开发:selector篇](http://keeganlee.me/post/android/20150905) 143 | 4.[Android样式的开发:layer-list篇](http://keeganlee.me/post/android/20150909) 144 | 5.[Android样式的开发:Style篇](http://keeganlee.me/post/android/20151031) 145 | 146 | OK,本章结束,谢谢阅读。 147 | 148 | 149 | -------------------------------------------------------------------------------- /ReadingNotes-ArtOfAndroidDevelopment/2015-12-04-Art-of-Android-Development-Reading-Notes-7.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Art of Android Development Reading Notes 7 4 | categories: android 5 | date: 2015-11-30 21:50:54 6 | --- 7 | 《Android开发艺术探索》读书笔记 (7) 第7章 Android动画深入分析 8 | 9 | 本节和《Android群英传》中的**第七章Android动画机制与使用技巧**有关系,[建议先阅读该章的总结](https://hujiaweibujidao.github.io/blog/2015/11/27/Android-Heros-Reading-Notes-3/) 10 | 11 | ### 第7章 Android动画深入分析 12 | #### 7.1 View动画 13 | (1)android动画分为view动画、帧动画和属性动画,属性动画是API 11(Android 3.0)的新特性,帧动画一般也认为是view动画。 14 | (2)`AnimationSet`的属性`android:shareInterpolator`表示集合中的动画是否共享同一个插值器,如果集合不指定插值器,那么子动画需要单独指定所需的插值器或者使用默认值。 15 | (3)自定义动画需要继承`Animation`抽象类,并重新它的`initialize`和`applyTransformation`方法,在initialize方法中做一些初始化工作,在applyTransformation方法中进行相应的矩阵变换,很多时候需要采用`Camera`类来简化矩阵变换的过程。 16 | (4)帧动画使用比较简单,但是容易引起OOM,所以在使用的时候应尽量避免使用过多尺寸较大的图片。 17 | 18 | #### 7.2 view动画的特殊使用场景 19 | (1)布局动画(`LayoutAnimation`)属性分析 20 | ``` 21 | 26 | ``` 27 | `android:delay`:表示子元素开始动画的时间延迟,比如子元素入场动画的时间周期是300ms,那么0.5表示每个子元素都需要延迟150ms才能播放入场动画。 28 | 29 | 给ViewGroup指定LayoutAnimation的两种方式 30 | ``` 31 | //xml 32 | android:layoutAnimation="xxx" 33 | //java 34 | Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item); 35 | LayoutAnimationController controller = new LayoutAnimationController(animation); 36 | controller.setDelay(0.5f); 37 | controller.setOrder(LayoutAnimationController.ORDER_NORMAL); 38 | listView.setLayoutAnimation(controller); 39 | ``` 40 | 41 | (2)Activity切换效果 42 | 在startActivity方法后或者finish方法之后调用`overridePendingTransition(int inAnim, int outAnim)`方法设置进入或者退出的动画效果。 43 | 还有其他方式可以给Activity添加切换动画效果,但是往往有兼容性限制,参见[《Android群英传》第七章Android动画机制与使用技巧](https://hujiaweibujidao.github.io/blog/2015/11/27/Android-Heros-Reading-Notes-3/)。 44 | 45 | #### 7.3 属性动画 46 | (1)属性动画可以对任意对象的属性进行动画而不仅仅是view,动画默认的时间间隔是`300ms`,默认帧率是`10ms/帧`。 47 | (2)属性动画几乎是无所不能,但是它是从API 11才有的,所以存在兼容性问题,可以考虑使用开源动画库[nineoldandroids](http://nineoldandroids.com)。它的功能和系统原生的`android.animations.*`中的类的功能完全一致,使用方法也是完全一样,只要我们用nineoldandroids编写动画,那么就能运行在所有的android系统上。 48 | (3)属性`android:repeatMode`表示动画的重复模式,`repeat`表示连续重复播放,`reverse`表示逆向重复播放,也就是第一次播放完后第二次倒着播放动画,第三次还是重头开始播放动画,第四次再倒着播放,以此类推。 49 | (4)插值器和估值器:属性动画实现非匀速动画的重要手段 50 | 时间插值器(`TimeInterpolator`)的作用是根据时间流逝的百分比计算出当前属性值改变的百分比,系统内置的插值器有线性插值器(`LinearInterpolator`)、加速减速插值器(`AccelerateDecelerateInterpolator`)和减速插值器(`DecelerateInterpolator`)。 51 | 类型估值器(`TypeEvaluator`)的作用是根据当前属性改变的百分比计算出改变后的属性值,系统内置的估值器有`IntEvaluator`、`FloatEvaluator`和`ArgbEvaluator`。 52 | (5)动画监听器 53 | `AnimatorListener`:监听动画的开始、结束、取消以及重复播放; 54 | `AnimatorUpdateListener`:监听动画的整个过程,动画每播放一帧的时候`onAnimationUpdate`方法就会被回调一次。 55 | (6)对任意属性做动画的方法:封装原始对象或者`ValueAnimator` 56 | (7)属性动画的工作原理:属性动画需要运行在有Looper的线程中,反射调用get/set方法 57 | 58 | #### 7.4 使用动画的注意事项 59 | (1)OOM:尽量避免使用帧动画,使用的话应尽量避免使用过多尺寸较大的图片; 60 | (2)内存泄露:属性动画中的无限循环动画需要在Activity退出的时候及时停止,否则将导致Activity无法释放而造成内存泄露。view动画不存在这个问题; 61 | (3)兼容性问题:某些动画在3.0以下系统上有兼容性问题; 62 | (4)view动画的问题:view动画是对view的影像做动画,并不是真正的改变view的状态,因此有时候动画完成之后view无法隐藏,即`setVisibility(View.GONE)`失效了,此时需要调用`view.clearAnimation()`清除view动画才行。 63 | (5)不要使用px; 64 | (6)动画元素的交互:**在android3.0以前的系统上,view动画和属性动画,新位置均无法触发点击事件,同时,老位置仍然可以触发单击事件。从3.0开始,属性动画的单击事件触发位置为移动后的位置,view动画仍然在原位置**; 65 | (7)硬件加速:使用动画的过程中,建议开启硬件加速,这样会提高动画的流畅性。 66 | 67 | **其他学习资料** 68 | 0.[代码家的重要的开源项目AndroidViewAnimation](https://github.com/daimajia/AndroidViewAnimations) 69 | 1.[Android样式的开发:View Animation篇](http://keeganlee.me/post/android/20151003) 70 | 2.[Android样式的开发:Property Animation篇](http://keeganlee.me/post/android/20151026) 71 | 72 | OK,本章结束,谢谢阅读。 73 | 74 | 75 | -------------------------------------------------------------------------------- /ReadingNotes-ArtOfAndroidDevelopment/2015-12-04-Art-of-Android-Development-Reading-Notes-8.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Art of Android Development Reading Notes 8 4 | categories: android 5 | date: 2015-12-04 10:50:54 6 | --- 7 | 《Android开发艺术探索》读书笔记 (8) 第8章 理解Window和WindowManager 8 | 9 | ### 第8章 理解Window和WindowManager 10 | #### 8.1 Window和WindowManager 11 | (1)`Window`是抽象类,具体实现是`PhoneWindow`,通过`WindowManager`就可以创建Window。WindowManager是外界访问Window的入口,但是Window的具体实现是在`WindowManagerService`中,WindowManager和WindowManagerService的交互是一个IPC过程。所有的视图例如Activity、Dialog、Toast都是附加在Window上的。 12 | (2)通过WindowManager添加View的过程:将一个Button添加到屏幕坐标为(100,300)的位置上 13 | ``` 14 | mFloatingButton = new Button(this); 15 | mFloatingButton.setText("test button"); 16 | mLayoutParams = new WindowManager.LayoutParams( 17 | LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, 18 | PixelFormat.TRANSPARENT);//0,0 分别是type和flags参数,在后面分别配置了 19 | mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL 20 | | LayoutParams.FLAG_NOT_FOCUSABLE 21 | | LayoutParams.FLAG_SHOW_WHEN_LOCKED; 22 | mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR; 23 | mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; 24 | mLayoutParams.x = 100; 25 | mLayoutParams.y = 300; 26 | mFloatingButton.setOnTouchListener(this); 27 | mWindowManager.addView(mFloatingButton, mLayoutParams); 28 | ``` 29 | flags参数解析: 30 | `FLAG_NOT_FOCUSABLE`:表示window不需要获取焦点,也不需要接收各种输入事件。此标记会同时启用`FLAG_NOT_TOUCH_MODAL`,最终事件会直接传递给下层的具有焦点的window; 31 | `FLAG_NOT_TOUCH_MODAL`:在此模式下,系统会将window区域外的单击事件传递给底层的window,当前window区域内的单击事件则自己处理,一般都需要开启这个标记; 32 | `FLAG_SHOW_WHEN_LOCKED`:开启此模式可以让Window显示在锁屏的界面上。 **[奇怪的是我删除这个标记还是在锁屏看到了添加的组件orz]** 33 | 34 | type参数表示window的类型,**window共有三种类型:应用window,子window和系统window。应用window对应着一个Activity,子window不能独立存在,需要附属在特定的父window之上,比如Dialog就是子window。系统window是需要声明权限才能创建的window,比如Toast和系统状态栏这些都是系统window,需要声明的权限是``。** 35 | (3)window是分层的,每个window都对应着`z-ordered`,层级大的会覆盖在层级小的上面,应用window的层级范围是`1~99`,子window的层级范围是`1000~1999`,系统window的层级范围是`2000~2999`。 36 | [注意,应用window的层级范围并不是`1~999`哟] 37 | (4)WindowManager继承自`ViewManager`,常用的只有三个方法:`addView`、`updateView`和`removeView`。 38 | 39 | #### 8.2 Window的内部机制 40 | (1)Window是一个抽象的概念,不是实际存在的,它也是以View的形式存在。在实际使用中无法直接访问Window,只能通过WindowManager才能访问Window。**每个Window都对应着一个View和一个`ViewRootImpl`,Window和View通过ViewRootImpl来建立联系。** 41 | (2)Window的添加、删除和更新过程都是IPC过程,以Window的添加为例,WindowManager的实现类对于`addView`、`updateView`和`removeView`方法都是委托给`WindowManagerGlobal`类,该类保存了很多数据列表,例如所有window对应的view集合`mViews`、所有window对应的ViewRootImpl的集合`mRoots`等,之后添加操作交给了ViewRootImpl来处理,接着会通过`WindowSession`来完成Window的添加过程,这个过程是一个IPC调用,因为最终是通过`WindowManagerService`来完成window的添加的。 42 | 43 | #### 8.3 Window的创建过程 44 | (1)Activity的window创建过程 45 | 1.Activity的启动过程很复杂,最终会由`ActivityThread`中的`performLaunchActivity`来完成整个启动过程,在这个方法内部会通过类加载器创建Activity的实例对象,并调用它的`attach`方法为其关联运行过程中所依赖的一系列上下文环境变量; 46 | 2.Activity实现了Window的`Callback`接口,当window接收到外界的状态变化时就会回调Activity的方法,例如`onAttachedToWindow`、`onDetachedFromWindow`、`dispatchTouchEvent`等; 47 | 3.Activity的Window是由`PolicyManager`来创建的,它的真正实现是`Policy`类,它会新建一个`PhoneWindow`对象,Activity的`setContentView`的实现是由`PhoneWindow`来实现的; 48 | 4.Activity的顶级View是`DecorView`,它本质上是一个`FrameLayout`。如果没有DecorView,那么PhoneWindow会先创建一个DecorView,然后加载具体的布局文件并将view添加到DecorView的`mContentParent`中,最后就是回调Activity的`onContentChanged`通知Activity视图已经发生了变化; 49 | 5.还有一个步骤是让WindowManager能够识别DecorView,在`ActivityThread`调用`handleResumeActivity`方法时,首先会调用Activity的onResume方法,然后会调用`makeVisible`方法,这个方法中DecorView真正地完成了添加和显示过程。 50 | ``` 51 | ViewManager vm = getWindowManager(); 52 | vm.addView(mDecor, getWindow().getAttributes()); 53 | mWindowAdded = true; 54 | ``` 55 | (2)Dialog的Window创建过程 56 | 1.过程与Activity的Window创建过程类似,普通的Dialog的有一个特别之处,即它必须采用Activity的Context,如果采用Application的Context会报错。原因是Application没有`应用token`,应用token一般是Activity拥有的。[service貌似也有token?] 57 | 58 | (3)Toast的Window创建过程 59 | 1.Toast属于系统Window,它内部的视图由两种方式指定:一种是系统默认的演示;另一种是通过`setView`方法来指定一个自定义的View。 60 | 2.Toast具有定时取消功能,所以系统采用了`Handler`。Toast的显示和隐藏是IPC过程,都需要`NotificationManagerService`来实现。在Toast和NMS进行IPC过程时,NMS会跨进程回调Toast中的`TN`类中的方法,TN类是一个Binder类,运行在Binder线程池中,所以需要通过Handler将其切换到当前发送Toast请求所在的线程,所以**Toast无法在没有Looper的线程中弹出**。 61 | 3.对于非系统应用来说,`mToastQueue`最多能同时存在`50`个`ToastRecord`,这样做是为了防止`DOS`(Denial of Service,拒绝服务)。因为如果某个应用弹出太多的Toast会导致其他应用没有机会弹出Toast。 62 | 63 | **其他学习资料** 64 | 1.[Android应用开发之(WindowManager类使用)](http://blog.csdn.net/wang_shaner/article/details/8596380) 65 | 66 | OK,本章结束,谢谢阅读。 67 | -------------------------------------------------------------------------------- /ReadingNotes-ArtOfAndroidDevelopment/2015-12-04-Art-of-Android-Development-Reading-Notes-9.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Art of Android Development Reading Notes 9 4 | categories: android 5 | date: 2015-12-05 11:50:54 6 | --- 7 | 《Android开发艺术探索》读书笔记 (9) 第9章 四大组件的工作过程 8 | 9 | ### 第9章 四大组件的工作过程 10 | #### 9.1 四大组件的运行状态 11 | (1)四大组件中只有`BroadcastReceiver`既可以在AndroidManifest文件中注册,也可以在代码中注册,其他三个组件都必须在AndroidManifest文件中注册;`ContentProvider`的调用不需要借助Intent,其他三个组件都需要借助Intent。 12 | (2)Activity是一种展示型组件,用于向用户展示界面,可由显式或者隐式Intent来启动。 13 | (3)Service是一种计算型组件,用于在后台执行计算任务。尽管service是用于后台执行计算的,但是它本身是运行在主线程中的,因此耗时的后台计算仍然需要在单独的线程中去完成。Service组件有两种状态:启动状态和绑定状态。当service处于绑定状态时,外界可以很方便的和service进行通信,而在启动状态中是不可与外界通信的。 14 | (4)BroadcastReceiver是一种消息型组件,用于在不同的组件乃至不同的应用之间传递消息,它工作在系统内部。广播有两种注册方式:静态注册和动态注册。静态注册是在AndroidManifest中注册,在应用安装的时候会被系统解析,这种广播不需要应用启动就可以收到相应的广播。动态注册需要通过`Context.registerReceiver()`来注册,这种广播需要应用启动才能注册并接收广播。BroadcastReceiver组件一般来说不需要停止,它也没有停止的概念。 15 | (5)ContentProvider是一种数据共享型组件,用于向其他组件乃至其他应用共享数据。ContentProvider中的`insert`、`delete`、`update`、`query`方法需要处理好线程同步,因为这几个方法是在Binder线程池中被调用的,另外ContentProvider组件也不需要手动停止。 16 | 17 | **[下面对四大组件的工作过程的总结需要感谢[`amurocrash`童鞋的读书笔记](http://blog.csdn.net/amurocrash/article/details/48858353)以及他细心制作的UML图,帮助我从原书复杂的方法调用中跳出来看到整体的大致流程]** 18 | 19 | #### 9.2 Activity的工作过程 20 | (1)Activity启动的大致流程 21 | ![img](https://hujiaweibujidao.github.io/images/androidart_activity.png) 22 | (2)`ApplicationThread`是`ActivityThread`的一个内部类,它继承自`ApplicationThreadNative`,而`ApplicationThreadNative`继承自`Binder`并实现了`IApplicationThread`接口,`ApplicationThreadNative`的作用其实就和系统为AIDL文件生成的类是一样的。 23 | (3)`ActivityManagerService`(AMS)继承自`ActivityManagerNative`,而`ActivityManagerNative`继承自`Binder`并实现了`IActivityManager`这个Binder接口,因此AMS也是一个Binder。 24 | (4)一个应用只有一个Application对象,它的创建也是通过`Instrumentation`来完成的,这个过程和Activity对象的创建过程一样,都是通过类加载器来实现的。 25 | (5)`ContextImpl`是Context的具体实现,ContextImpl是通过Activity的`attach`方法来和Activity建立关联的,在`attach`方法中Activity还会完成Window的创建并建立自己和Window的关联,*这样当window接收到外部输入事件后就可以将事件传递给Activity*。 **[这里可能有误,应该是Activity将事件传递给window]** 26 | 27 | #### 9.3 Service的工作过程 28 | (1)Service有两种状态:启动状态和绑定状态,两种状态是可以共存的。 29 | 启动过程: 30 | ![img](https://hujiaweibujidao.github.io/images/androidart_service1.png) 31 | 绑定过程: 32 | ![img](https://hujiaweibujidao.github.io/images/androidart_service2.png) 33 | 34 | #### 9.4 BroadcastReceiver的工作过程 35 | (1)BroadcastReceiver的工作过程包括广播注册过程、广播发送和接收过程。 36 | 注册过程:静态注册的时候是由`PackageManagerService`来完成整个注册过程,下面是动态注册的过程 37 | ![img](https://hujiaweibujidao.github.io/images/androidart_broadcastreceiver1.png) 38 | 发送和接收过程: 39 | ![img](https://hujiaweibujidao.github.io/images/androidart_broadcastreceiver1.png) 40 | (2)广播的发送有几种类型:普通广播、有序广播和粘性广播,有序广播和粘性广播与普通广播相比具有不同的特性,但是发送和接收过程是类似的。 41 | (3)一个应用处于停止状态分为两种情况:一是应用安装后未运行;二是应用被手动或者其他应用强停了。从Android 3.1开始,处于停止状态的应用无法接受到开机广播。 42 | 43 | #### 9.5 ContentProvider的工作过程 44 | (1)当ContentProvider所在的进程启动的时候,它会同时被启动并被发布到AMS中,这个时候它的onCreate要先去Application的onCreate执行。 45 | (2)ContentProvider的启动过程: 46 | 1.当一个应用启动时,入口方法是`ActivityThread`的`main`方法,其中创建ActivityThread的实例并创建主线程的消息队列; 47 | 2.`ActivityThread`的`attach`方法中会远程调用`ActivityManagerService`的`attachApplication`,并将`ApplicationThread`提供给AMS,ApplicationThread主要用于ActivityThread和AMS之间的通信; 48 | 3.`ActivityManagerService`的`attachApplication`会调用`ApplicationThread`的`bindApplication`方法,这个方法会通过`H`切换到ActivityThread中去执行,即调用`handleBindApplication`方法; 49 | 4.`handleBindApplication`方法会创建Application对象并加载ContentProvider,注意是先加载ContentProvider,然后调用Application的`onCreate`方法。 50 | (3)ContentProvider的`android:multiprocess`属性决定它是否是单实例,默认值是false,也就是默认是单实例。当设置为true时,每个调用者的进程中都存在一个ContentProvider对象。 51 | (4)当调用ContentProvider的`insert`、`delete`、`update`、`query`方法中的任何一个时,如果ContentProvider所在的进程没有启动的话,那么就会触发ContentProvider的创建,并伴随着ContentProvider所在进程的启动。下图是ContentProvider的query操作的大致过程: 52 | ![img](https://hujiaweibujidao.github.io/images/androidart_contentprovider.png) 53 | 54 | 详细的过程分析建议阅读原书,简直精彩! 55 | 56 | **其他学习资料** 57 | 1.[Android开发艺术探索读书笔记(三)](http://blog.csdn.net/amurocrash/article/details/48858353) 58 | 59 | OK,本章结束,谢谢阅读。 60 | 61 | 62 | -------------------------------------------------------------------------------- /ReadingNotes-ArtOfAndroidDevelopment/2015-12-05-Art-of-Android-Development-Reading-Notes-10.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Art of Android Development Reading Notes 10 4 | categories: android 5 | date: 2015-12-04 11:50:54 6 | --- 7 | 《Android开发艺术探索》读书笔记 (10) 第10章 Android的消息机制 8 | 9 | ### 第10章 Android的消息机制 10 | #### 10.1 Android消息机制概述 11 | (1)Android的消息机制主要是指Handler的运行机制,其底层需要`MessageQueue`和`Looper`的支撑。MessageQueue是以单链表的数据结构存储消息列表但是以队列的形式对外提供插入和删除消息操作的消息队列。MessageQueue只是消息的存储单元,而Looper则是以无限循环的形式去查找是否有新消息,如果有的话就去处理消息,否则就一直等待着。 12 | (2)Handler的主要作用是将一个任务切换到某个指定的线程中去执行。 13 | **为什么要提供这个功能呢?** 14 | Android规定UI操作只能在主线程中进行,`ViewRootImpl`的`checkThread`方法会验证当前线程是否可以进行UI操作。 15 | **为什么不允许子线程访问UI呢?** 16 | 这是因为UI组件不是线程安全的,如果在多线程中并发访问可能会导致UI组件处于不可预期的状态。另外,如果对UI组件的访问进行加锁机制的话又会降低UI访问的效率,所以还是采用单线程模型来处理UI事件。 17 | (3)Handler的创建会采用当前线程的Looper来构建内部的消息循环系统,如果当前线程中不存在Looper的话就会报错。Handler可以用`post`方法将一个Runnable投递到消息队列中,也可以用`send`方法发送一个消息投递到消息队列中,其实`post`最终还是调用了`send`方法。 18 | 19 | #### 10.2 Android的消息机制分析 20 | (1)`ThreadLocal`的工作原理 21 | 1.ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。**一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,可以考虑使用ThreadLocal。** 对于Handler来说,它需要获取当前线程的Looper,而Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以实现Looper在线程中的存取了。 22 | 2.ThreadLocal的原理:不同线程访问同一个ThreadLocal的`get`方法时,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值,不同线程中的数组是不同的,这就是为什么通过ThreadLocal可以在不同线程中维护一套数据的副本并且彼此互不干扰。 23 | 3.ThreadLocal是一个泛型类`public class ThreadLocal `,下面是它的`set`方法 24 | ``` 25 | public void set(T value) { 26 | Thread currentThread = Thread.currentThread(); 27 | Values values = values(currentThread); 28 | if (values == null) { 29 | values = initializeValues(currentThread); 30 | } 31 | values.put(this, value); 32 | } 33 | ``` 34 | `Values`是Thread类内部专门用来存储线程的ThreadLocal数据的,它内部有一个数组`private Object[] table`,ThreadLocal的值就存在这个table数组中。如果values的值为null,那么就需要对其进行初始化然后再将ThreadLocal的值进行存储。 35 | **ThreadLocal数据的存储规则:ThreadLocal的值在table数组中的存储位置总是ThreadLocal的索引+1的位置。** 36 | 37 | (2)`MessageQueue`的工作原理 38 | 1.MessageQueue其实是通过单链表来维护消息列表的,它包含两个主要操作`enqueueMessage`和`next`,前者是插入消息,后者是取出一条消息并移除。 39 | 2.next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里。当有新消息到来时,next方法会返回这条消息并将它从链表中移除。 40 | 41 | (3)`Looper`的工作原理 42 | 1.为一个线程创建Looper的方法,代码如下所示 43 | ``` 44 | new Thread("test"){ 45 | @Override 46 | public void run() { 47 | Looper.prepare();//创建looper 48 | Handler handler = new Handler();//可以创建handler了 49 | Looper.loop();//开始looper循环 50 | } 51 | }.start(); 52 | ``` 53 | 2.Looper的`prepareMainLooper`方法主要是给主线程也就是`ActivityThread`创建Looper使用的,本质也是调用了`prepare`方法。 54 | 3.Looper的`quit`和`quitSafely`方法的区别是:前者会直接退出Looper,后者只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全地退出。Looper退出之后,通过Handler发送的消息就会失败,这个时候Handler的send方法会返回false。 55 | **在子线程中,如果手动为其创建了Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。** 56 | 4.Looper的`loop`方法会调用`MessageQueue`的`next`方法来获取新消息,而next是一个阻塞操作,当没有消息时,next方法会一直阻塞着在那里,这也导致了loop方法一直阻塞在那里。如果MessageQueue的next方法返回了新消息,Looper就会处理这条消息:`msg.target.dispatchMessage(msg)`,其中的`msg.target`就是发送这条消息的Handler对象。 57 | 58 | (4)Handler的工作原理 59 | 1.Handler就是处理消息的发送和接收之后的处理; 60 | 2.Handler处理消息的过程 61 | ``` 62 | public void dispatchMessage(Message msg) { 63 | if (msg.callback != null) { 64 | handleCallback(msg);//当message是runnable的情况,也就是Handler的post方法传递的参数,这种情况下直接执行runnable的run方法 65 | } else { 66 | if (mCallback != null) {//如果创建Handler的时候是给Handler设置了Callback接口的实现,那么此时调用该实现的handleMessage方法 67 | if (mCallback.handleMessage(msg)) { 68 | return; 69 | } 70 | } 71 | handleMessage(msg);//如果是派生Handler的子类,就要重写handleMessage方法,那么此时就是调用子类实现的handleMessage方法 72 | } 73 | } 74 | 75 | private static void handleCallback(Message message) { 76 | message.callback.run(); 77 | } 78 | 79 | /** 80 | * Subclasses must implement this to receive messages. 81 | */ 82 | public void handleMessage(Message msg) { 83 | } 84 | ``` 85 | 3.Handler还有一个特殊的构造方法,它可以通过特定的Looper来创建Handler。 86 | ``` 87 | public Handler(Looper looper){ 88 | this(looper, null, false); 89 | } 90 | ``` 91 | 4.Android的主线程就是`ActivityThread`,主线程的入口方法就是main,其中调用了`Looper.prepareMainLooper()`来创建主线程的Looper以及MessageQueue,并通过`Looper.loop()`方法来开启主线程的消息循环。主线程内有一个Handler,即`ActivityThread.H`,它定义了一组消息类型,主要包含了四大组件的启动和停止等过程,例如`LAUNCH_ACTIVITY`等。 92 | `ActivityThread`通过`ApplicationThread`和`AMS`进行进程间通信,AMS以进程间通信的方法完成ActivityThread的请求后会回调ApplicationThread中的`Binder`方法,然后ApplicationThread会向`H`发送消息,`H`收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。 93 | 94 | OK,本章结束,谢谢阅读。 95 | -------------------------------------------------------------------------------- /ReadingNotes-ArtOfAndroidDevelopment/2015-12-05-Art-of-Android-Development-Reading-Notes-11.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Art of Android Development Reading Notes 11 4 | categories: android 5 | date: 2015-12-03 11:50:54 6 | --- 7 | 《Android开发艺术探索》读书笔记 (11) 第11章 Android的线程和线程池 8 | 9 | ### 第11章 Android的线程和线程池 10 | #### 11.1 主线程和子线程 11 | (1)在Java中默认情况下一个进程只有一个线程,也就是主线程,其他线程都是子线程,也叫工作线程。Android中的主线程主要处理和界面相关的事情,而子线程则往往用于执行耗时操作。线程的创建和销毁的开销较大,所以如果一个进程要频繁地创建和销毁线程的话,都会采用线程池的方式。 12 | (2)在Android中除了Thread,还有`HandlerThread`、`AsyncTask`以及`IntentService`等也都扮演着线程的角色,只是它们具有不同的特性和使用场景。**AsyncTask封装了线程池和Handler,它主要是为了方便开发者在子线程中更新UI。HandlerThread是一种具有消息循环的线程,在它的内部可以使用Handler。IntentService是一个服务,它内部采用HandlerThread来执行任务,当任务执行完毕后就会自动退出。因为它是服务的缘故,所以和后台线程相比,它比较不容易被系统杀死。** 13 | (3)从Android 3.0开始,系统要求网络访问必须在子线程中进行,否则网络访问将会失败并抛出`NetworkOnMainThreadException`这个异常,这样做是为了避免主线程由于被耗时操作所阻塞从而出现ANR现象。 14 | (4)AsyncTask是一个抽象泛型类,它提供了`Params`、`Progress`、`Result`三个泛型参数,如果task确实不需要传递具体的参数,那么都可以设置为`Void`。下面是它的四个核心方法,其中`doInBackground`不是在主线程执行的。 15 | `onPreExecute`、`doInBackground`、`onProgressUpdate`、`onPostResult` 16 | 17 | #### 11.2 Android中的线程形态 18 | (1)`AsyncTask`的使用过程中的条件限制: 19 | 1.AsyncTask的类必须在主线程中加载,这个过程在Android 4.1及以上版本中已经被系统自动完成。 20 | 2.AsyncTask对象必须在主线程中创建,`execute`方法必须在UI线程中调用。 21 | 3.一个AsyncTask对象只能执行一次,即只能调用一次`execute`方法,否则会报运行时异常。 22 | 4.**在Android 1.6之前,AsyncTask是串行执行任务的,Android 1.6的时候AsyncTask开始采用线程池并行处理任务,但是从Android 3.0开始,为了避免AsyncTask带来的并发错误,AsyncTask又采用一个线程来串行执行任务。尽管如此,在Android 3.0以及后续版本中,我们可以使用AsyncTask的`executeOnExecutor`方法来并行执行任务。但是这个方法是Android 3.0新添加的方法,并不能在低版本上使用。** 23 | (2)AsyncTask的原理 24 | 1.AsyncTask中有两个线程池:`SerialExecutor`和`THREAD_POOL_EXECUTOR`。前者是用于任务的排队,默认是串行的线程池;后者用于真正执行任务。AsyncTask中还有一个Handler,即`InternalHandler`,用于将执行环境从线程池切换到主线程。AsyncTask内部就是通过InternalHandler来发送任务执行的进度以及执行结束等消息。 25 | 2.AsyncTask排队执行过程:系统先把参数`Params`封装为`FutureTask`对象,它相当于Runnable;接着将FutureTask交给SerialExecutor的`execute`方法,它先把FutureTask插入到任务队列tasks中,如果这个时候没有正在活动的AsyncTask任务,那么就会执行下一个AsyncTask任务,同时当一个AsyncTask任务执行完毕之后,AsyncTask会继续执行其他任务直到所有任务都被执行为止。 26 | (3)`HandlerThread`就是一种可以使用Handler的Thread,它的实现就是在run方法中通过`Looper.prepare()`来创建消息队列,并通过`Looper.loop()`来开启消息循环,这样在实际的使用中就允许在HandlerThread中创建Handler了,外界可以通过Handler的消息方式通知HandlerThread执行一个具体的任务。HandlerThread的run方法是一个无限循环,因此当明确不需要再使用HandlerThread的时候,可以通过它的`quit`或者`quitSafely`方法来终止线程的执行。HandlerThread的最主要的应用场景就是用在IntentService中。 27 | (4)`IntentService`是一个继承自Service的抽象类,要使用它就要创建它的子类。IntentService适合执行一些高优先级的后台任务,这样不容易被系统杀死。IntentService的`onCreate`方法中会创建HandlerThread,并使用HandlerThread的Looper来构造一个Handler对象ServiceHandler,这样通过ServiceHandler对象发送的消息最终都会在HandlerThread中执行。IntentService会将Intent封装到Message中,通过ServiceHandler发送出去,在ServiceHandler的`handleMessage`方法中会调用IntentService的抽象方法`onHandleIntent`,所以IntentService的子类都要是实现这个方法。 28 | 29 | #### 11.3 Android中的线程池 30 | (1)使用线程池的好处: 31 | 1.重用线程,避免线程的创建和销毁带来的性能开销; 32 | 2.能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象; 33 | 3.能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。 34 | (2)`Executor`只是一个接口,真正的线程池是`ThreadPoolExecutor`。ThreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池,Android的线程池都是通过`Executors`提供的工厂方法得到的。 35 | (3)ThreadPoolExecutor的构造参数 36 | 1.`corePoolSize`:核心线程数,默认情况下,核心线程会在线程中一直存活; 37 | 2.`maximumPoolSize`:最大线程数,当活动线程数达到这个数值后,后续的任务将会被阻塞; 38 | 3.`keepAliveTime`:非核心线程闲置时的超时时长,超过这个时长,闲置的非核心线程就会被回收; 39 | 4.`unit`:用于指定keepAliveTime参数的时间单位,有`TimeUnit.MILLISECONDS`、`TimeUnit.SECONDS`、`TimeUnit.MINUTES`等; 40 | 5.`workQueue`:任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中; 41 | 6.`threadFactory`:线程工厂,为线程池提供创建新线程的功能。它是一个接口,它只有一个方法`Thread newThread(Runnable r)`; 42 | 7.`RejectedExecutionHandler`:当线程池无法执行新任务时,可能是由于任务队列已满或者是无法成功执行任务,这个时候就会调用这个Handler的`rejectedExecution`方法来通知调用者,默认情况下,`rejectedExecution`会直接抛出一个`rejectedExecutionException`。 43 | (4)ThreadPoolExecutor执行任务的规则: 44 | 1.如果线程池中的线程数未达到核心线程的数量,那么会直接启动一个核心线程来执行任务; 45 | 2.如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行; 46 | 3.如果在步骤2中无法将任务插入到的任务队列中,可能是任务队列已满,这个时候如果线程数量没有达到规定的最大值,那么会立刻启动非核心线程来执行这个任务; 47 | 4.如果步骤3中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用`RejectedExecutionHandler`的`rejectedExecution`方法来通知调用者。 48 | (5)AsyncTask的THREAD_POOL_EXECUTOR线程池的配置: 49 | 1.`corePoolSize`=CPU核心数+1; 50 | 2.`maximumPoolSize`=2倍的CPU核心数+1; 51 | 3.核心线程无超时机制,非核心线程在闲置时间的超时时间为`1s`; 52 | 4.任务队列的容量为`128`。 53 | (6)Android中常见的4类具有不同功能特性的线程池: 54 | 1.`FixedThreadPool`:线程数量固定的线程池,它只有核心线程; 55 | 2.`CachedThreadPool`:线程数量不固定的线程池,它只有非核心线程; 56 | 3.`ScheduledThreadPool`:核心线程数量固定,非核心线程数量没有限制的线程池,主要用于执行定时任务和具有固定周期的任务; 57 | 4.`SingleThreadPool`:只有一个核心线程的线程池,确保了所有的任务都在同一个线程中按顺序执行。 58 | 59 | OK,本章结束,谢谢阅读。 60 | -------------------------------------------------------------------------------- /ReadingNotes-ArtOfAndroidDevelopment/2015-12-05-Art-of-Android-Development-Reading-Notes-12.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Art of Android Development Reading Notes 12 4 | categories: android 5 | date: 2015-11-30 22:50:54 6 | --- 7 | 《Android开发艺术探索》读书笔记 (12) 第12章 Bitmap的加载和Cache 8 | 9 | ### 第12章 Bitmap的加载和Cache 10 | #### 12.1 Bitmap的高速加载 11 | (1)**Bitmap是如何加载的?** 12 | `BitmapFactory`类提供了四类方法:`decodeFile`、`decodeResource`、`decodeStream`和`decodeByteArray`从不同来源加载出一个Bitmap对象,最终的实现是在底层实现的。 13 | **如何高效加载Bitmap?** 14 | 采用`BitmapFactory.Options`按照一定的采样率来加载所需尺寸的图片,因为imageview所需的图片大小往往小于图片的原始尺寸。 15 | (2)BitmapFactory.Options的`inSampleSize`参数,即采样率 16 | 官方文档指出采样率的取值应该是2的指数,例如k,那么采样后的图片宽高均为原图片大小的 1/k。 17 | **如何获取采样率?** 18 | 下面是常用的获取采样率的代码片段: 19 | ``` 20 | public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { 21 | // First decode with inJustDecodeBounds=true to check dimensions 22 | final BitmapFactory.Options options = new BitmapFactory.Options(); 23 | options.inJustDecodeBounds = true; 24 | BitmapFactory.decodeResource(res, resId, options); 25 | 26 | // Calculate inSampleSize 27 | options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 28 | 29 | // Decode bitmap with inSampleSize set 30 | options.inJustDecodeBounds = false; 31 | return BitmapFactory.decodeResource(res, resId, options); 32 | } 33 | 34 | public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { 35 | if (reqWidth == 0 || reqHeight == 0) { 36 | return 1; 37 | } 38 | 39 | // Raw height and width of image 40 | final int height = options.outHeight; 41 | final int width = options.outWidth; 42 | Log.d(TAG, "origin, w= " + width + " h=" + height); 43 | int inSampleSize = 1; 44 | 45 | if (height > reqHeight || width > reqWidth) { 46 | final int halfHeight = height / 2; 47 | final int halfWidth = width / 2; 48 | 49 | // Calculate the largest inSampleSize value that is a power of 2 and 50 | // keeps both height and width larger than the requested height and width. 51 | while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { 52 | inSampleSize *= 2; 53 | } 54 | } 55 | 56 | Log.d(TAG, "sampleSize:" + inSampleSize); 57 | return inSampleSize; 58 | } 59 | ``` 60 | 61 | 将`inJustDecodeBounds`设置为true的时候,BitmapFactory只会解析图片的原始宽高信息,并不会真正的加载图片,所以这个操作是轻量级的。**需要注意的是,这个时候BitmapFactory获取的图片宽高信息和图片的位置以及程序运行的设备有关,这都会导致BitmapFactory获取到不同的结果。** 62 | 63 | #### 12.2 Android中的缓存策略 64 | (1)最常用的缓存算法是LRU,核心是当缓存满时,会优先淘汰那些近期最少使用的缓存对象,系统中采用LRU算法的缓存有两种:`LruCache`(内存缓存)和`DiskLruCache`(磁盘缓存)。 65 | (2)LruCache是Android 3.1才有的,通过support-v4兼容包可以兼容到早期的Android版本。LruCache类是一个线程安全的泛型类,它内部采用一个`LinkedHashMap`以强引用的方式存储外界的缓存对象,其提供了get和put方法来完成缓存的获取和添加操作,当缓存满时,LruCache会移除较早使用的缓存对象,然后再添加新的缓存对象。 66 | (3)DiskLruCache磁盘缓存,它不属于Android sdk的一部分,[它的源码可以在这里下载](https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java) 67 | DiskLruCache的创建、缓存查找和缓存添加操作 68 | (4)ImageLoader的实现 [具体内容看源码](https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_12/src/com/ryg/chapter_12/loader/ImageLoader.java) 69 | 功能:图片的同步/异步加载,图片压缩,内存缓存,磁盘缓存,网络拉取 70 | 71 | #### 12.3 ImageLoader的使用 72 | 避免发生列表item错位的解决方法:给显示图片的imageview添加`tag`属性,值为要加载的图片的目标url,显示的时候判断一下url是否匹配。 73 | **优化列表的卡顿现象** 74 | (1)不要在getView中执行耗时操作,不要在getView中直接加载图片,否则肯定会导致卡顿; 75 | (2)控制异步任务的执行频率:在列表滑动的时候停止加载图片,等列表停下来以后再加载图片; 76 | (3)使用硬件加速来解决莫名的卡顿问题,给Activity添加配置`android:hardwareAccelerated="true"`。 77 | 78 | 本章的精华就是作者写的[ImageLoader类](https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_12/src/com/ryg/chapter_12/loader/ImageLoader.java),建议阅读源码感受下。 79 | 80 | OK,本章结束,谢谢阅读。 81 | -------------------------------------------------------------------------------- /ReadingNotes-ArtOfAndroidDevelopment/2015-12-05-Art-of-Android-Development-Reading-Notes-13.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Art of Android Development Reading Notes 13 4 | categories: android 5 | date: 2015-12-04 18:50:54 6 | --- 7 | 《Android开发艺术探索》读书笔记 (13) 第13章 综合技术、第14章 JNI和NDK编程、第15章 Android性能优化 8 | 9 | ### 第13章 综合技术 10 | #### 13.1 使用CrashHandler来获取应用的Crash信息 11 | (1)应用发生Crash在所难免,但是如何采集crash信息以供后续开发处理这类问题呢?利用Thread类的`setDefaultUncaughtExceptionHandler`方法!`defaultUncaughtHandler`是Thread类的静态成员变量,所以如果我们将自定义的`UncaughtExceptionHandler`设置给Thread的话,那么当前进程内的所有线程都能使用这个UncaughtExceptionHandler来处理异常了。 12 | ``` 13 | public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) { 14 | Thread.defaultUncaughtHandler = handler; 15 | } 16 | ``` 17 | (2)作者实现了一个简易版本的UncaughtExceptionHandler类的子类`CrashHandler`,[源码传送门](https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_13/CrashTest/src/com/ryg/crashtest/CrashHandler.java) 18 | CrashHandler的使用方式就是在Application的`onCreate`方法中设置一下即可 19 | ``` 20 | //在这里为应用设置异常处理程序,然后我们的程序才能捕获未处理的异常 21 | CrashHandler crashHandler = CrashHandler.getInstance(); 22 | crashHandler.init(this); 23 | ``` 24 | 25 | #### 13.2 使用multidex来解决方法数越界 26 | (1)在Android中单个dex文件所能够包含的最大方法数是`65536`,这包含Android Framework、依赖的jar以及应用本身的代码中的所有方法。如果方法数超过了最大值,那么编译会报错`DexIndexOverflowException`。 27 | 有时方法数没有超过最大值,但是安装在低版本手机上时应用异常终止了,报错`Optimization failed`。这是因为应用在安装的时候,系统会通过`dexopt`程序来优化dex文件,在优化的过程中dexopt采用一个固定大小的缓冲区来存储应用中所有方法的信息,这个缓冲区就是`LinearAlloc`。LinearAlloc缓冲区在新版本的Android系统中大小是8MB或者16MB,但是在Android 2.2和2.3中却只有5MB,当待安装的应用的方法数比较多的时候,尽管它还没有达到最大方法数,但是它的存储空间仍然有可能超过5MB,这种情况下dexopt就会报错导致安装失败。 28 | (2)**如何解决方法数越界的问题呢?** Google在2014年提出了简单方便的`multidex`的解决方案。 29 | 在Android 5.0之前使用multidex需要引入`android-support-multidex.jar`包,从Android 5.0开始,系统默认支持了multidex,它可以从apk中加载多个dex。Multidex方案主要针对AndroidStudio和Gradle编译环境。 30 | 使用Multidex的步骤: 31 | 1.在`build.gradle`文件中添加`multiDexEnabled true` 32 | ``` 33 | android { 34 | ... 35 | 36 | defaultConfig { 37 | ... 38 | 39 | multiDexEnabled true // [添加的配置] enable multidex support 40 | } 41 | ... 42 | } 43 | ``` 44 | 2.添加对multidex的依赖 45 | ``` 46 | compile 'com.android.support:multidex:1.0.0' 47 | ``` 48 | 3.在代码中添加对multidex的支持,这里有三种方案: 49 | ① 在AndroidManifest文件中指定Application为`MultiDexApplication` 50 | ``` 51 | 54 | ``` 55 | ② 让应用的Application继承自`MultiDexApplication` 56 | ③ 重写Application的`attachBaseContext`方法,这个方法要先于`onCreate`方法执行 57 | ``` 58 | public class TestApplication extends Application { 59 | 60 | @Override 61 | protected void attachBaseContext(Context base) { 62 | super.attachBaseContext(base); 63 | MultiDex.install(this); // 64 | } 65 | } 66 | ``` 67 | 68 | 采用上面的配置之后,如果应用的方法数没有越界,那么Gradle并不会生成多个dex文件;如果方法数越界后,Gradle就会在apk中打包2个或者多个dex文件,具体会打包多少个dex文件要看当前项目的代码规模。在有些情况下,可能需要指定主dex文件中所要包含的类,这个可以通过`--main-dex-list`选项来实现这个功能。 69 | ``` 70 | afterEvaluate { 71 | println "afterEvaluate" 72 | tasks.matching { 73 | it.name.startsWith('dex') 74 | }.each { dx -> 75 | def listFile = project.rootDir.absolutePath + '/app/maindexlist.txt' 76 | println "root dir:" + project.rootDir.absolutePath 77 | println "dex task found: " + dx.name 78 | if (dx.additionalParameters == null) { 79 | dx.additionalParameters = [] 80 | } 81 | dx.additionalParameters += '--multi-dex' 82 | dx.additionalParameters += '--main-dex-list=' + listFile 83 | dx.additionalParameters += '--minimal-main-dex' 84 | } 85 | } 86 | ``` 87 | 88 | `--multi-dex`表明当方法数越界时生成多个dex文件,`--main-dex-list`指定了要在主dex中打包的类的列表,`--minimal-main-dex`表明只有`--main-dex-list`所指定的类才能打包到主dex中。multidex的jar包中的9个类必须要打包到主dex中,其次不能在Application中成员以及代码块中访问其他dex中的类,否个程序会因为无法加载对应的类而中止执行。 89 | (3)Multidex方案可能带来的问题: 90 | 1.应用启动速度会降低,因为应用启动的时候会加载额外的dex文件,所以要避免生成较大的dex文件; 91 | 2.需要做大量的兼容性测试,因为Dalvik LinearAlloc的bug,可能导致使用multidex的应用无法在Android 4.0以前的手机上运行。 92 | 93 | #### 13.3 Android的动态加载技术 94 | (1)动态加载技术又称插件化技术,将应用插件化可以减轻应用的内存和CPU占用,还可以在不发布新版本的情况下更新某些模块。不同的插件化方案各有特色,但是都需要解决**三个基础性问题:资源访问,Activity生命周期管理和插件ClassLoader的管理。** 95 | (2)宿主和插件:宿主是指普通的apk,插件是经过处理的dex或者apk。在主流的插件化框架中多采用特殊处理的apk作为插件,处理方式往往和编译以及打包环节有关,另外很多插件化框架都需要用到代理Activity的概念,插件Activity的启动大多数是借助一个代理Activity来实现的。 96 | (3)资源访问:宿主程序调起未安装的插件apk,插件中凡是R开头的资源都不能访问了,因为宿主程序中并没有插件的资源,通过R来访问插件的资源是行不通的。 97 | Activity的资源访问是通过`ContextImpl`来完成的,它有两个方法`getAssets()`和`getResources()`方法是用来加载资源的。 98 | 具体实现方式是通过反射,调用`AssetManager`的`addAssetPath`方法添加插件的路径,然后将插件apk中的资源加载到`Resources`对象中即可。 99 | (4)Activity生命周期管理:有两种常见的方式,反射方式和接口方式。反射方式就是通过反射去获取Activity的各个生命周期方法,然后在代理Activity中去调用插件Activity对应的生命周期方法即可。 100 | 反射方式代码繁琐,性能开销大。接口方式将Activity的生命周期方法提取出来作为一个接口,然后通过代理Activity去调用插件Activity的生命周期方法,这样就完成了插件Activity的生命周期管理。 101 | (5)插件ClassLoader的管理:为了更好地对多插件进行支持,需要合理地去管理各个插件的`DexClassLoader`,这样同一个插件就可以采用同一个ClassLoader去加载类,从而避免了多个ClassLoader加载同一个类时所引起的类型转换错误。 102 | 103 | **其他详细信息看作者插件化框架[singwhatiwanna/dynamic-load-apk](https://github.com/singwhatiwanna/dynamic-load-apk)** 104 | 105 | #### 13.4 反编译初步 106 | 1.主要介绍使用`dex2jar`和`jd-gui`反编译apk和使用`apktool`对apk进行二次打包,比较简单,略过不总结。 107 | 108 | 109 | ### 第14章 JNI和NDK编程 110 | 111 | 本章主要是介绍JNI和NDK编程入门知识,比较简答,略过不总结。 112 | 如果感兴趣NDK开发可以阅读我之前总结的[Android NDK和OpenCV整合开发系列文章](https://hujiaweibujidao.github.io/blog/2013/11/18/android-ndk-and-opencv-developement/)。 113 | 114 | 115 | ### 第15章 Android性能优化 116 | 117 | (1)2015年Google关于Android性能优化典范的专题视频 [Youtube视频地址](https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE) 118 | 119 | (2)布局优化 120 | 1.删除布局中无用的组件和层级,有选择地使用性能较低的ViewGroup; 121 | 2.使用``、``、``等标签:``标签主要用于布局重用,``标签一般和``配合使用,它可以减少布局中的层级;``标签则提供了按需加载的功能,当需要的时候才会将ViewStub中的布局加载到内存,提供了程序的初始化效率。 122 | 3.``标签只支持`android:layout_`开头的属性,`android:id`属性例外。 123 | 4.`ViewStub`继承自View,它非常轻量级且宽高都为0,它本身不参与任何的布局和绘制过程。实际开发中,很多布局文件在正常情况下不会显示,例如网络异常时的界面,这个时候就没有必要在整个界面初始化的时候加载进行,通过ViewStub可以做到在需要的时候再加载。 124 | 如下面示例,`android:id`是ViewStub的id,而`android:inflatedId`是布局的根元素的id。 125 | ``` 126 | 131 | ``` 132 | 133 | (3)绘制优化 134 | 1.在`onDraw`中不要创建新的布局对象,因为`onDraw`会被频繁调用; 135 | 2.`onDraw`方法中不要指定耗时任务,也不能执行成千上万次的循环操作。 136 | 137 | (4)内存泄露优化 138 | 1.可能导致内存泄露的场景很多,例如静态变量、单例模式、属性动画、AsyncTask、Handler等等 139 | 140 | (5)响应速度优化和ANR日志分析 141 | 1.ANR出现的情况:Activity如果`5s`内没有响应屏幕触摸事件或者键盘输入事件就会ANR,而BroadcastReceiver如果`10s`内没有执行完操作也会出现ANR。 142 | 2.当一个进程发生了ANR之后,系统会在`/data/anr`目录下创建一个文件`traces.txt`,通过分析这个文件就能定位ANR的原因。 143 | 144 | (6)ListView和Bitmap优化 145 | 1.ListView优化:采用`ViewHolder`并避免在`getView`方法中执行耗时操作;根据列表的滑动状态来绘制任务的执行频率;可以尝试开启硬件加速来使ListView的滑动更加流畅。 146 | 2.Bitmap优化:根据需要对图片进行采样,详情看[Android开发艺术探索》读书笔记 (12) 第12章 Bitmap的加载和Cache](https://hujiaweibujidao.github.io/blog/2015/11/30/Art-of-Android-Development-Reading-Notes-12/)。 147 | 148 | (7)线程优化 149 | 1.采用线程池,详情看[《Android开发艺术探索》读书笔记 (11) 第11章 Android的线程和线程池](https://hujiaweibujidao.github.io/blog/2015/12/03/Art-of-Android-Development-Reading-Notes-11/)。 150 | 151 | (8)其他优化建议 152 | 1.不要过多使用枚举,枚举占用的内存空间要比整型大; 153 | 2.常量请使用`static final`来修饰; 154 | 3.使用一些Android特有的数据结构,比如`SparseArray`和`Pair`等,他们都具有更好的性能; 155 | 4.适当使用软引用和弱引用; 156 | 5.采用内存缓存和磁盘缓存; 157 | 6.尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄露。 158 | 159 | (9)MAT是功能强大的内存分析工具,主要有`Histograms`和`Dominator Tree`等功能 160 | 161 | OK,本章结束,谢谢阅读。 162 | 163 | 164 | -------------------------------------------------------------------------------- /ReadingNotes-ArtOfAndroidDevelopment/2015-12-06-Art-of-Android-Development-Reading-Notes.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Art of Android Development Reading Notes 4 | categories: android 5 | date: 2015-12-5 12:50:54 6 | --- 7 | **《Android开发艺术探索》读书笔记系列目录** 8 | 9 | 啥也不说了,[@主席](http://weibo.com/uc83018062)的《Android开发艺术探索》真是业界良心之作,不得不看!感谢主席,膜拜主席!主席主席,我要跟你生猴子!(>^ω^<) 10 | 11 | 读书笔记中若有任何问题请留言告知,谢谢关注,谢谢阅读。 12 | 13 | 1.[《Android开发艺术探索》读书笔记 (1) 第1章 Activity的生命周期和启动模式](https://hujiaweibujidao.github.io/blog/2015/11/29/Art-of-Android-Development-Reading-Notes-1/) 14 | 15 | 2.[《Android开发艺术探索》读书笔记 (2) 第2章 IPC机制](https://hujiaweibujidao.github.io/blog/2015/12/05/Art-of-Android-Development-Reading-Notes-2/) 16 | 17 | 3.[《Android开发艺术探索》读书笔记 (3) 第3章 View的事件体系](https://hujiaweibujidao.github.io/blog/2015/12/01/Art-of-Android-Development-Reading-Notes-3/) 18 | 19 | 4.[《Android开发艺术探索》读书笔记 (4) 第4章 View的工作原理](https://hujiaweibujidao.github.io/blog/2015/12/01/Art-of-Android-Development-Reading-Notes-4/) 20 | 21 | 5.[《Android开发艺术探索》读书笔记 (5) 第5章 理解RemoteViews](https://hujiaweibujidao.github.io/blog/2015/12/01/Art-of-Android-Development-Reading-Notes-5/) 22 | 23 | 6.[《Android开发艺术探索》读书笔记 (6) 第6章 Android的Drawable](https://hujiaweibujidao.github.io/blog/2015/11/30/Art-of-Android-Development-Reading-Notes-6/) 24 | 25 | 7.[《Android开发艺术探索》读书笔记 (7) 第7章 Android动画深入分析](https://hujiaweibujidao.github.io/blog/2015/11/30/Art-of-Android-Development-Reading-Notes-7/) 26 | 27 | 8.[《Android开发艺术探索》读书笔记 (8) 第8章 理解Window和WindowManager](https://hujiaweibujidao.github.io/blog/2015/12/04/Art-of-Android-Development-Reading-Notes-8/) 28 | 29 | 9.[《Android开发艺术探索》读书笔记 (9) 第9章 四大组件的工作过程](https://hujiaweibujidao.github.io/blog/2015/12/05/Art-of-Android-Development-Reading-Notes-9/) 30 | 31 | 10.[《Android开发艺术探索》读书笔记 (10) 第10章 Android的消息机制](https://hujiaweibujidao.github.io/blog/2015/12/04/Art-of-Android-Development-Reading-Notes-10/) 32 | 33 | 11.[《Android开发艺术探索》读书笔记 (11) 第11章 Android的线程和线程池](https://hujiaweibujidao.github.io/blog/2015/12/03/Art-of-Android-Development-Reading-Notes-11/) 34 | 35 | 12.[《Android开发艺术探索》读书笔记 (12) 第12章 Bitmap的加载和Cache](https://hujiaweibujidao.github.io/blog/2015/11/30/Art-of-Android-Development-Reading-Notes-12/) 36 | 37 | 13.[《Android开发艺术探索》读书笔记 (13) 第13章 综合技术、第14章 JNI和NDK编程、第15章 Android性能优化](https://hujiaweibujidao.github.io/blog/2015/12/04/Art-of-Android-Development-Reading-Notes-13/) 38 | 39 | 40 | -------------------------------------------------------------------------------- /ReadingNotes-PythonAlgorithms/2014-05-07-python-algorithms-search.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Python Data Structures - C1 Search" 4 | date: 2014-05-07 16:00 5 | categories: algorithm 6 | --- 7 | Python数据结构篇(1) 搜索 8 | 9 | 参考内容: 10 | 11 | 1.[Problem Solving with Python](http://interactivepython.org/courselib/static/pythonds/index.html) 12 | 13 | Chapter5: Search and Sorting [online_link](http://interactivepython.org/courselib/static/pythonds/SortSearch/searching.html#searching) 14 | 15 | 2.[算法导论](http://en.wikipedia.org/wiki/Introduction_to_Algorithms) 16 | 17 | #### 搜索(或查找)总结 18 | 19 | (1)顺序查找:O(n) 20 | 21 | (2)二分查找:O(lgn) 22 | 23 | (3)Hash查找:O(1) 24 | 25 | 概念:hash,hash table,hash function [哈希表_on_wiki](http://zh.wikipedia.org/wiki/%E5%93%88%E5%B8%8C%E8%A1%A8#.E5.A4.84.E7.90.86.E7.A2.B0.E6.92.9E) 26 | 27 | ![image](https://hujiaweibujidao.github.io/images/hashbasics.png) 28 | 29 | 常用的哈希函数: 30 | 31 | 1.reminder method:取余数(size=11,下图对11取余数,例如17取余数得到6) 32 | 33 | ![image](https://hujiaweibujidao.github.io/images/reminder.png) 34 | 35 | 2.folding method: 分组求和再取余数 36 | 37 | ![image](https://hujiaweibujidao.github.io/images/folding.png) 38 | 39 | 3.mid-square method:平方值的中间两位数取余数 40 | 41 | ![image](https://hujiaweibujidao.github.io/images/mid-square.png) 42 | 43 | 4.对于由字符的元素可以尝试使用`ord`函数来将字符串转换成一个有序的数值序列。在Python中`ord`函数可以得到对应字符的ASCII码值。将所有字符的码值累加再取余数。 44 | 45 | ![image](https://hujiaweibujidao.github.io/images/stringord1.png) 46 | 47 | 但是,对于通过回文构词法构成的字符串它们得到的值总是一样,为了解决这个问题,可以根据字符的位置添加一个权重。 48 | 49 | ![image](https://hujiaweibujidao.github.io/images/stringord2.png) 50 | 51 | From wiki 52 | 53 | ![image](https://hujiaweibujidao.github.io/images/hashfun.png) 54 | 55 | 使用哈希查找,难免遇到冲突,该如何解决冲突(Collision Resolution)呢? 56 | 57 | 常用的解决冲突的办法: 58 | 59 | 1.open address(开放寻址):线性探测(linear probing)下一个位置,缺点是容易造成聚集现象(cluster),解决聚集现象的办法是跳跃式地查找下一个空槽。数值的顺序:(54, 26, 93, 17, 77, 31, 44, 55, 20). 60 | 61 | ![image](https://hujiaweibujidao.github.io/images/linearprob.png) 62 | 63 | 2.quadratic probing(平方探测):一开始的hash值为h,如果不是空槽,那就尝试h+1,还不是空槽就尝试h+4,依次继续尝试h+9,h+16等等。 64 | 65 | ![image](https://hujiaweibujidao.github.io/images/quadraticprob.png) 66 | 67 | 3.chain:利用链表链接起来 68 | 69 | ![image](https://hujiaweibujidao.github.io/images/chain.png) 70 | 71 | From wiki 72 | 73 | ![image](https://hujiaweibujidao.github.io/images/hashcollision.png) 74 | 75 | 分析hash查找的性能:一般使用平均查找长度来衡量,和装载因子有关 76 | 77 | > 散列表的载荷因子定义为:$\alpha$ = 填入表中的元素个数 / 散列表的长度 78 | > $\alpha$是散列表装满程度的标志因子。由于表长是定值,$\alpha$与“填入表中的元素个数”成正比,所以,$\alpha$越大,表明填入表中的元素越多,产生冲突的可能性就越大;反之,$\alpha$越小,标明填入表中的元素越少,产生冲突的可能性就越小。实际上,散列表的平均查找长度是载荷因子$\alpha$的函数,只是不同处理冲突的方法有不同的函数。 79 | > 对于开放定址法,荷载因子是特别重要因素,应严格限制在0.7-0.8以下。超过0.8,查表时的CPU缓存不命中(cache missing)按照指数曲线上升。因此,一些采用开放定址法的hash库,如Java的系统库限制了荷载因子为0.75,超过此值将resize散列表。 80 | 81 | ![image](https://hujiaweibujidao.github.io/images/hashanalysis.png) 82 | 83 | From wiki 84 | 85 | ![image](https://hujiaweibujidao.github.io/images/hashefficiency.png) 86 | 87 | 下面的代码包含了顺序查找,二分查找,哈希查找(size=11, plus 1, reminder method) 88 | 89 | ```python 90 | def sequential_search(a_list, item): 91 | pos = 0 92 | found = False 93 | while pos < len(a_list) and not found: 94 | if a_list[pos] == item: 95 | found = True 96 | else: 97 | pos = pos+1 98 | return found 99 | 100 | test_list = [1, 2, 32, 8, 17, 19, 42, 13, 0] 101 | print(sequential_search(test_list, 3)) 102 | print(sequential_search(test_list, 13)) 103 | 104 | 105 | def binary_search(a_list, item): 106 | first = 0 107 | last = len(a_list) - 1 108 | found = False 109 | while first <= last and not found: 110 | midpoint = (first + last) // 2 111 | if a_list[midpoint] == item: 112 | found = True 113 | else: 114 | if item < a_list[midpoint]: 115 | last = midpoint - 1 116 | else: 117 | first = midpoint + 1 118 | return found 119 | 120 | test_list = [0, 1, 2, 8, 13, 17, 19, 32, 42,] 121 | print(binary_search(test_list, 3)) 122 | print(binary_search(test_list, 13)) 123 | 124 | class HashTable: 125 | def __init__(self): 126 | self.size = 11 127 | self.slots = [None] * self.size 128 | self.data = [None] * self.size 129 | 130 | #put data in slot 131 | def put_data_in_slot(self,key,data,slot): 132 | if self.slots[slot] == None: # '==None' ? or 'is None' ? 133 | self.slots[slot] = key 134 | self.data[slot] = data 135 | return True 136 | else: 137 | if self.slots[slot] == key: # not None 138 | self.data[slot] = data #replace 139 | return True 140 | else: 141 | return False 142 | 143 | def put(self, key, data): 144 | slot = self.hash_function(key, self.size); 145 | result = self.put_data_in_slot(key,data,slot); 146 | while not result: 147 | slot = self.rehash(slot, self.size); 148 | result=self.put_data_in_slot(key,data,slot); 149 | 150 | #reminder method 151 | def hash_function(self, key, size): 152 | return key % size 153 | 154 | #plus 1 155 | def rehash(self, old_hash, size): 156 | return (old_hash + 1) % size 157 | 158 | def get(self, key): 159 | start_slot = self.hash_function(key, len(self.slots)) 160 | data = None 161 | stop = False 162 | found = False 163 | position = start_slot 164 | while self.slots[position] != None and not found and not stop: 165 | if self.slots[position] == key: 166 | found = True 167 | data = self.data[position] 168 | else: 169 | position=self.rehash(position, len(self.slots)) 170 | if position == start_slot: 171 | stop = True 172 | return data 173 | 174 | def __getitem__(self, key): 175 | return self.get(key) 176 | 177 | def __setitem__(self, key, data): 178 | self.put(key, data) 179 | 180 | 181 | if __name__ == '__main__': 182 | table=HashTable(); 183 | table[54]='cat'; 184 | table[26]='dog'; 185 | table[93]='lion'; 186 | table[17]="tiger"; 187 | table[77]="bird"; 188 | table[44]="goat"; 189 | table[55]="pig"; 190 | table[20]="chicken"; 191 | print table.slots; 192 | print table.data; 193 | 194 | # [77, 44, 55, None, 26, 93, 17, None, None, 20, 54] 195 | # ['bird', 'goat', 'pig', None, 'dog', 'lion', 'tiger', None, None, 'chicken', 'cat'] 196 | ``` 197 | 198 | 199 | -------------------------------------------------------------------------------- /ReadingNotes-PythonAlgorithms/2014-05-07-python-algorithms-sort.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Python Data Structures - C2 Sort" 4 | date: 2014-05-07 22:00 5 | categories: algorithm 6 | --- 7 | Python数据结构篇(2) 排序 8 | 9 | 参考内容: 10 | 11 | 1.[Problem Solving with Python](http://interactivepython.org/courselib/static/pythonds/index.html) 12 | 13 | Chapter5: Search and Sorting [online_link](http://interactivepython.org/courselib/static/pythonds/SortSearch/sorting.html) 14 | 15 | 2.[算法导论](http://en.wikipedia.org/wiki/Introduction_to_Algorithms) 16 | 17 | #### 排序总结 18 | 19 | 1.冒泡排序(bubble sort):每个回合都从第一个元素开始和它后面的元素比较,如果比它后面的元素更大的话就交换,一直重复,直到这个元素到了它能到达的位置。每次遍历都将剩下的元素中最大的那个放到了序列的“最后”(除去了前面已经排好的那些元素)。注意检测是否已经完成了排序,如果已完成就可以退出了。时间复杂度$O(n^2)$ 20 | 21 | **Python支持对两个数字同时进行交换!`a,b = b,a`就可以交换a和b的值了。** 22 | 23 | ![image](https://hujiaweibujidao.github.io/images/bubblesort.png) 24 | 25 | ```python 26 | def short_bubble_sort(a_list): 27 | exchanges = True 28 | pass_num = len(a_list) - 1 29 | while pass_num > 0 and exchanges: 30 | exchanges = False 31 | for i in range(pass_num): 32 | if a_list[i] > a_list[i + 1]: 33 | exchanges = True 34 | # temp = a_list[i] 35 | # a_list[i] = a_list[i + 1] 36 | # a_list[i + 1] = temp 37 | a_list[i],a_list[i+1] = a_list[i+1], a_list[i] 38 | pass_num = pass_num - 1 39 | 40 | 41 | if __name__ == '__main__': 42 | a_list=[20, 40, 30, 90, 50, 80, 70, 60, 110, 100] 43 | short_bubble_sort(a_list) 44 | print(a_list) 45 | ``` 46 | 47 | 48 | 2.选择排序(selection sort):每个回合都选择出剩下的元素中最大的那个,选择的方法是首先默认第一元素是最大的,如果后面的元素比它大的话,那就更新剩下的最大的元素值,找到剩下元素中最大的之后将它放入到合适的位置就行了。和冒泡排序类似,只是找剩下的元素中最大的方式不同而已。时间复杂度$O(n^2)$ 49 | 50 | ![image](https://hujiaweibujidao.github.io/images/selectionsort.png) 51 | 52 | ``` 53 | def selection_sort(a_list): 54 | for fill_slot in range(len(a_list) - 1, 0, -1): 55 | pos_of_max = 0 56 | for location in range(1, fill_slot + 1): 57 | if a_list[location] > a_list[pos_of_max]: 58 | pos_of_max = location 59 | # temp = a_list[fill_slot] 60 | # a_list[fill_slot] = a_list[pos_of_max] 61 | # a_list[pos_of_max] = temp 62 | a_list[fill_slot],a_list[pos_of_max]=a_list[pos_of_max],a_list[fill_slot] 63 | 64 | 65 | a_list = [54, 26, 93, 17, 77, 31, 44, 55, 20] 66 | selection_sort(a_list) 67 | print(a_list) 68 | ``` 69 | 70 | 3.插入排序(insertion sort):每次假设前面的元素都是已经排好序了的,然后将当前位置的元素插入到原来的序列中,为了尽快地查找合适的插入位置,可以使用二分查找。时间复杂度$O(n^2)$,别误以为二分查找可以降低它的复杂度,因为插入排序还需要移动元素的操作! 71 | 72 | ![image](https://hujiaweibujidao.github.io/images/insertionsort.png) 73 | 74 | ```python 75 | def insertion_sort(a_list): 76 | for index in range(1, len(a_list)): 77 | current_value = a_list[index] 78 | position = index 79 | while position > 0 and a_list[position - 1] > current_value: 80 | a_list[position] = a_list[position - 1] 81 | position = position - 1 82 | a_list[position] = current_value 83 | 84 | 85 | def insertion_sort_binarysearch(a_list): 86 | for index in range(1, len(a_list)): 87 | current_value = a_list[index] 88 | position = index 89 | low=0 90 | high=index-1 91 | while low<=high: 92 | mid=(low+high)/2 93 | if a_list[mid]>current_value: 94 | high=mid-1 95 | else: 96 | low=mid+1 97 | while position > low: 98 | a_list[position] = a_list[position - 1] 99 | position = position -1 100 | a_list[position] = current_value 101 | 102 | 103 | a_list = [54, 26, 93, 15, 77, 31, 44, 55, 20] 104 | insertion_sort(a_list) 105 | print(a_list) 106 | insertion_sort_binarysearch(a_list) 107 | print(a_list) 108 | ``` 109 | 110 | 4.合并排序(merge sort):典型的是二路合并排序,将原始数据集分成两部分(不一定能够均分),分别对它们进行排序,然后将排序后的子数据集进行合并,这是典型的分治法策略。时间复杂度$O(nlogn)$ 111 | 112 | ![image](https://hujiaweibujidao.github.io/images/mergesort.png) 113 | 114 | ![image](https://hujiaweibujidao.github.io/images/mergesort2.png) 115 | 116 | ```python 117 | def merge_sort(a_list): 118 | print("Splitting ", a_list) 119 | if len(a_list) > 1: 120 | mid = len(a_list) // 2 121 | left_half = a_list[:mid] 122 | right_half = a_list[mid:] 123 | merge_sort(left_half) 124 | merge_sort(right_half) 125 | i=0;j=0;k=0; 126 | while i < len(left_half) and j < len(right_half): 127 | if left_half[i] < right_half[j]: 128 | a_list[k] = left_half[i] 129 | i=i+1 130 | else: 131 | a_list[k] = right_half[j] 132 | j=j+1 133 | k=k+1 134 | while i < len(left_half): 135 | a_list[k] = left_half[i] 136 | i=i+1 137 | k=k+1 138 | while j < len(right_half): 139 | a_list[k] = right_half[j] 140 | j=j+1 141 | k=k+1 142 | print("Merging ", a_list) 143 | 144 | 145 | a_list = [54, 26, 93, 17, 77, 31, 44, 55, 20] 146 | merge_sort(a_list) 147 | print(a_list) 148 | ``` 149 | 150 | 算法导论2-4题利用合并排序可以在$O(nlogn)$的最坏情况下得到包含n个元素的数组的逆序对的数目。[下面使用的是C++来实现的,合并排序的代码格式类似算法导论] 151 | 152 | ```cpp 153 | #include 154 | using namespace std; 155 | 156 | int calculateInversions(int arr[], int p, int r); 157 | int mergeInversions(int arr[], int p, int q, int r); 158 | 159 | int main(int argc, const char * argv[]) 160 | { 161 | int arr[] = {2,3,8,6,1}; 162 | int count = calculateInversions(arr, 0, 4); 163 | cout << "count inversions : " << count << endl; 164 | return 0; 165 | } 166 | 167 | int calculateInversions(int arr[], int p, int r) { 168 | int count=0; 169 | if(p < r) { 170 | int q = (p + r) / 2; 171 | count += calculateInversions(arr, p, q); 172 | count += calculateInversions(arr, q+1, r); 173 | count += mergeInversions(arr, p, q, r); 174 | } 175 | return count; 176 | } 177 | 178 | int mergeInversions(int arr[], int p, int q, int r){ 179 | int count=0; 180 | int n1=q-p+1, n2=r-q; 181 | int left[n1+1], right[n2+1]; 182 | for (int i=0; i= pivot_value and right_mark >= left_mark: 230 | right_mark = right_mark - 1 231 | if right_mark < left_mark: 232 | done = True 233 | else: 234 | temp = a_list[left_mark] 235 | a_list[left_mark] = a_list[right_mark] 236 | a_list[right_mark] = temp 237 | temp = a_list[first] 238 | a_list[first] = a_list[right_mark] 239 | a_list[right_mark] = temp 240 | return right_mark 241 | 242 | a_list = [54, 26, 93, 17, 77, 31, 44, 55, 20] 243 | quick_sort(a_list) 244 | print(a_list) 245 | ``` 246 | 247 | 想法二:如下图所示(摘自算法导论),它选择最后的那个元素作为主元,它的思路是将数组划分成4部分: 248 | 249 | 第一部分:$p \le k \le i, A[k] \le pivot$ 250 | 251 | 第二部分:$i+1 \le k \le j-1, A[k] \gt pivot$ 252 | 253 | 第三部分:$j \le k \le r-1, A[k]$可以取任何值(因为它们还没有进行处理)。 254 | 255 | 第四部分:$p \le k \le i, A[k] = pivot$ 256 | 257 | **首先,让i指向要排序的数组的第一个元素的前面,p和j都指向第一个元素;然后,一直移动j直到主元前一个位置,一旦发现一个小于主元的元素就让i指向它的下一个位置,然后交换i和j对应位置上的元素。这样一定是可行的,因为i一直都是指向已发现的小于主元的元素中的最后一个,从i+1开始就大于主元了(或者还未确定/未处理),而j一直都是指向大于主元的元素中最后一个的后面一个位置,所以i+1和j位置上的元素交换就可以使得j发现的这个小于主元的元素移动到第一部分,而i+1位置上大于主元的元素移动到j的位置上,即第二部分的最后一个位置上。** 258 | 259 | ![image](https://hujiaweibujidao.github.io/images/quicksort_cn.png) 260 | 261 | 根据算法导论中的伪代码的C++版本实现 262 | 263 | ```cpp 264 | #include 265 | using namespace std; 266 | 267 | // partition, locate the pivot value in properate position 268 | int partition(int a[], int low, int high){ 269 | int key = a[high];//pivot 270 | int i=low-1, temp; 271 | for (int j=low; j 0: 327 | for start_position in range(sublist_count): 328 | gap_insertion_sort(a_list, start_position, sublist_count) 329 | print("After increments of size", sublist_count, "The list is", a_list) 330 | sublist_count = sublist_count // 2 331 | 332 | def gap_insertion_sort(a_list, start, gap): 333 | #start+gap is the second element in this sublist 334 | for i in range(start + gap, len(a_list), gap): 335 | current_value = a_list[i] 336 | position = i 337 | while position >= gap and a_list[position - gap] > current_value: 338 | a_list[position] = a_list[position - gap] #move backward 339 | position = position - gap 340 | a_list[position] = current_value 341 | 342 | 343 | a_list = [54, 26, 93, 17, 77, 31, 44, 55, 20, 88] 344 | shell_sort(a_list) 345 | print(a_list) 346 | ``` 347 | 348 | 7.堆排序请参见该系列文章中的[DataStrctures章节中的二叉堆部分的内容](https://hujiaweibujidao.github.io/blog/2014/05/08/python-algorithms-datastructures/)。 349 | 350 | 8.其他线性排序可以参见算法导论第8章或者看下[这篇不错的文章](http://www.cnblogs.com/Anker/archive/2013/01/25/2876397.html) 351 | 352 | 其实看个图就明白了,图摘自上面的博客,版权归原作者,谢谢! 353 | 354 | 计数排序:在数的范围很小时还是不错的,当数的范围很大的时候就不适用了,计数排序一般用于基数排序中。需要注意的是,计数完了之后进行插入时,为了保证排序的稳定性,需要从后往前插入。 355 | 356 | ![image](https://hujiaweibujidao.github.io/images/sortcount.png) 357 | 358 | 下面是计数排序的python实现,摘自[Python Algorithms: Mastering Basic Algorithms in the Python Language](http://link.springer.com/book/10.1007%2F978-1-4302-3238-4) 359 | 360 | 361 | ``` 362 | from collections import defaultdict 363 | 364 | def counting_sort(A, key=lambda x: x): 365 | B, C = [], defaultdict(list) # Output and "counts" 366 | for x in A: 367 | C[key(x)].append(x) # "Count" key(x) 368 | for k in range(min(C), max(C) + 1): # For every key in the range 369 | B.extend(C[k]) # Add values in sorted order 370 | return B 371 | 372 | seq = [randrange(100) for i in range(10)] 373 | seq = counting_sort(seq) 374 | ``` 375 | 376 | 基数排序:因为每位上的数字范围一般都是有限的,所以常配合使用计数排序对每位进行排序。 377 | ![image](https://hujiaweibujidao.github.io/images/sortradix.png) 378 | 379 | 桶排序:适用于元素是均匀分布的,在每个桶内采用插入排序。 380 | 381 | ![image](https://hujiaweibujidao.github.io/images/sortbucket.png) 382 | 383 | 本节只是对各种排序进行一个介绍然后用python实现而已,更加详细地解释各种排序的内部思想的内容可以参见后面的[Python算法设计篇之Induction&Recursion&Reduction](https://hujiaweibujidao.github.io/blog/2014/07/01/python-algorithms-induction/) 384 | 385 | 386 | -------------------------------------------------------------------------------- /ReadingNotes-PythonAlgorithms/2014-05-08-python-algorithms-datastructures.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Python Data Structures - C3 Data Structures" 4 | date: 2014-05-08 10:00 5 | categories: algorithm 6 | --- 7 | Python数据结构篇(3) 数据结构 8 | 9 | 参考内容: 10 | 11 | 1.[Problem Solving with Python](http://interactivepython.org/courselib/static/pythonds/index.html) 12 | 13 | Chapter 2 Algorithm Analysis 14 | Chapter 3 Basic Data Structures 15 | Chapter 6 Trees and Tree Algorithms 16 | 17 | 2.[算法导论](http://en.wikipedia.org/wiki/Introduction_to_Algorithms) 18 | 19 | #### 数据结构总结 20 | 21 | 1.Python内置数据结构的性能分析 22 | 23 | (1)List 24 | 25 | List的各个操作的时间复杂度 26 | 27 | ![image](https://hujiaweibujidao.github.io/images/listoptime.png) 28 | 29 | 同样是执行1000次创建一个包含1-1000的列表,四种方式使用的时间差距很大!使用append比逐次增加要快很多,另外,使用python的列表产生式比append要快,而第四种方式更加快! 30 | 31 | ```python 32 | def test1(): 33 | l = [] 34 | for i in range(1000): 35 | l = l + [i] 36 | def test2(): 37 | l = [] 38 | for i in range(1000): 39 | l.append(i) 40 | def test3(): 41 | l = [i for i in range(1000)] 42 | def test4(): 43 | l = list(range(1000)) 44 | 45 | # Import the timeit module -> import timeit 46 | # Import the Timer class defined in the module 47 | from timeit import Timer 48 | # If the above line is excluded, you need to replace Timer with 49 | # timeit.Timer when defining a Timer object 50 | t1 = Timer("test1()", "from __main__ import test1") 51 | print("concat ",t1.timeit(number=1000), "milliseconds") 52 | t2 = Timer("test2()", "from __main__ import test2") 53 | print("append ",t2.timeit(number=1000), "milliseconds") 54 | t3 = Timer("test3()", "from __main__ import test3") 55 | print("comprehension ",t3.timeit(number=1000), "milliseconds") 56 | t4 = Timer("test4()", "from __main__ import test4") 57 | print("list range ",t4.timeit(number=1000), "milliseconds") 58 | 59 | # ('concat ', 1.7890608310699463, 'milliseconds') 60 | # ('append ', 0.13796091079711914, 'milliseconds') 61 | # ('comprehension ', 0.05671119689941406, 'milliseconds') 62 | # ('list range ', 0.014147043228149414, 'milliseconds') 63 | ``` 64 | 65 | `timeit`模块的解释: 66 | 67 | ![image](https://hujiaweibujidao.github.io/images/timeit.png) 68 | 69 | 测试pop操作:从结果可以看出,pop最后一个元素的效率远远高于pop第一个元素 70 | 71 | ``` 72 | x = list(range(2000000)) 73 | pop_zero = Timer("x.pop(0)","from __main__ import x") 74 | print("pop_zero ",pop_zero.timeit(number=1000), "milliseconds") 75 | x = list(range(2000000)) 76 | pop_end = Timer("x.pop()","from __main__ import x") 77 | print("pop_end ",pop_end.timeit(number=1000), "milliseconds") 78 | 79 | # ('pop_zero ', 1.9101738929748535, 'milliseconds') 80 | # ('pop_end ', 0.00023603439331054688, 'milliseconds') 81 | ``` 82 | 83 | 还有一个有意思的对比是list的`append(value)`和`insert(0, value)`,即一个从后面插入(后插),另一个从前面插入(前插),前者的效率远远高于后者。 84 | 85 | **Python的list的实现不是类似数据结构中的单链表,而是类似数组,也就是说list中的元素保存在一片连续的内存区域中,这样的话只有知道元素索引就能确定元素的内存位置,从而直接取出该位置上的值,但是它的缺点在于前插需要移动元素,而且随着list中元素的增多需要移动的元素也就越多,花费的时间也就自然多了。而单链表不同,单链表要得到某个位置上的元素必须要从头开始遍历,但是它的插入操作(前插或者后插)基本上都是恒定的时间,与链表中元素的多少没有关系,因为元素之间用“指针”维护着他们的关系。** 86 | 87 | **个人认为,Python的list其实是动态表,类似C++中的`vector`等或者Java中的`ArrayList`等,在算法导论的平摊分析章节的最后,详细讲解了动态表的扩张和收缩,根据装载因子的取值大小,当它高于某个固定值(例如$\frac{1}{2}$)时扩张表,当它低于某个固定值(例如$\frac{1}{4}$)时收缩表,这样就能保证表的利用率达到一个满意的程度(例如50%以上的位置都是有元素的),感兴趣可以阅读原书。** 88 | 89 | (2)Dictionary 90 | 91 | Dictionary的各个操作的性能 92 | 93 | ![image](https://hujiaweibujidao.github.io/images/dictionary.png) 94 | 95 | Dictionary和List的性能比较:list基本上随着其元素的数目呈线性增长,而dictionary一直维持在很短很短的时间内(我的机子测试的结果都是`0.001ms`)。Dictionary类似Java中的`HashMap`,内部实现使用了前面提到的hash函数,所以查找和删除都是常数时间的。 96 | 97 | ``` 98 | import timeit 99 | import random 100 | 101 | for i in range(10000,1000001,20000): 102 | t = timeit.Timer("random.randrange(%d) in x"%i,"from __main__ import random,x") 103 | x = list(range(i)) 104 | lst_time = t.timeit(number=1000) 105 | x = {j:None for j in range(i)} 106 | d_time = t.timeit(number=1000) 107 | print("%d,%10.3f,%10.3f" % (i, lst_time, d_time)) 108 | ``` 109 | 110 | 结果图 111 | 112 | ![image](https://hujiaweibujidao.github.io/images/compare.png) 113 | 114 | 2.栈:LIFO结构,后进先出 115 | 116 | 栈能解决的问题很多,比如逆波兰表达式求值,求一个十进制数的二进制表达,检查括号匹配问题以及图的深度搜索等等,都很简单,可查看参考内容1学习。 117 | 118 | ![image](https://hujiaweibujidao.github.io/images/stack.png) 119 | 120 | ```python 121 | # Completed implementation of a stack ADT 122 | class Stack: 123 | def __init__(self): 124 | self.items = [] 125 | def is_empty(self): 126 | return self.items == [] 127 | def push(self, item): 128 | self.items.append(item) 129 | def pop(self): 130 | return self.items.pop() 131 | def peek(self): 132 | return self.items[len(self.items)-1] 133 | def size(self): 134 | return len(self.items) 135 | 136 | s = Stack() 137 | print(s.is_empty()) 138 | s.push(4) 139 | s.push('dog') 140 | print(s.peek()) 141 | s.push(True) 142 | print(s.size()) 143 | print(s.is_empty()) 144 | s.push(8.4) 145 | print(s.pop()) 146 | print(s.pop()) 147 | print(s.size()) 148 | ``` 149 | 150 | 3.队列:FIFO结构,先进先出 151 | 152 | 队列一般用于解决需要优先队列的问题或者进行广度优先搜索的问题,也很简单。 153 | 154 | ![image](https://hujiaweibujidao.github.io/images/queue.png) 155 | 156 | ```python 157 | # Completed implementation of a queue ADT 158 | class Queue: 159 | def __init__(self): 160 | self.items = [] 161 | def is_empty(self): 162 | return self.items == [] 163 | def enqueue(self, item): 164 | self.items.insert(0,item) 165 | def dequeue(self): 166 | return self.items.pop() 167 | def size(self): 168 | return len(self.items) 169 | 170 | q = Queue() 171 | q.enqueue('hello') 172 | q.enqueue('dog') 173 | print(q.items) 174 | q.enqueue(3) 175 | q.dequeue() 176 | print(q.items) 177 | ``` 178 | 179 | 4.双向队列:左右两边都可以插入和删除的队列 180 | 181 | ![image](https://hujiaweibujidao.github.io/images/deque.png) 182 | 183 | 下面的实现是以右端为front,左端为rear 184 | 185 | ```python 186 | # Completed implementation of a deque ADT 187 | class Deque: 188 | def __init__(self): 189 | self.items = [] 190 | def is_empty(self): 191 | return self.items == [] 192 | def add_front(self, item): 193 | self.items.append(item) 194 | def add_rear(self, item): 195 | self.items.insert(0,item) 196 | def remove_front(self): 197 | return self.items.pop() 198 | def remove_rear(self): 199 | return self.items.pop(0) 200 | def size(self): 201 | return len(self.items) 202 | 203 | dq=Deque(); 204 | dq.add_front('dog'); 205 | dq.add_rear('cat'); 206 | print(dq.items) 207 | dq.remove_front(); 208 | dq.add_front('pig'); 209 | print(dq.items) 210 | ``` 211 | 212 | 5.二叉树:一个节点最多有两个孩子节点的树。如果是从0索引开始存储,那么对应于节点p的孩子节点是2p+1和2p+2两个节点,相反,节点p的父亲节点是(p-1)/2位置上的点 213 | 214 | 二叉树的应用很多,比如对算术表达式建立一颗二叉树可以清楚看出表达式是如何计算的(详情请见参考内容1),二叉树的变种可以得到其他的有一定特性的数据结构,例如后面的二叉堆。二叉树的三种遍历方法(前序,中序,后序)同样有很多的应用,比较简单,略过。 215 | 216 | ![image](https://hujiaweibujidao.github.io/images/bt2.png) 217 | 218 | 第一种,直接使用list来实现二叉树,可读性差 219 | 220 | ```python 221 | def binary_tree(r): 222 | return [r, [], []] 223 | def insert_left(root, new_branch): 224 | t = root.pop(1) 225 | if len(t) > 1: 226 | #new_branch becomes the left node of root, and original left 227 | #node t becomes left node of new_branch, right node is none 228 | root.insert(1, [new_branch, t, []]) 229 | else: 230 | root.insert(1, [new_branch, [], []]) 231 | return root 232 | def insert_right(root, new_branch): 233 | t = root.pop(2) 234 | if len(t) > 1: 235 | root.insert(2, [new_branch, [], t]) 236 | else: 237 | root.insert(2, [new_branch, [], []]) 238 | return root 239 | def get_root_val(root): 240 | return root[0] 241 | def set_root_val(root, new_val): 242 | root[0] = new_val 243 | def get_left_child(root): 244 | return root[1] 245 | def get_right_child(root): 246 | return root[2] 247 | 248 | r = binary_tree(3) 249 | insert_left(r, 4) 250 | insert_left(r, 5) 251 | insert_right(r, 6) 252 | insert_right(r, 7) 253 | print(r) 254 | l = get_left_child(r) 255 | print(l) 256 | set_root_val(l, 9) 257 | print(r) 258 | insert_left(l, 11) 259 | print(r) 260 | print(get_right_child(get_right_child(r))) 261 | ``` 262 | 263 | 第二种,使用类的形式定义二叉树,可读性更好 264 | 265 | ![image](https://hujiaweibujidao.github.io/images/btclass.png) 266 | 267 | ``` 268 | class BinaryTree: 269 | def __init__(self, root): 270 | self.key = root 271 | self.left_child = None 272 | self.right_child = None 273 | def insert_left(self, new_node): 274 | if self.left_child == None: 275 | self.left_child = BinaryTree(new_node) 276 | else: 277 | t = BinaryTree(new_node) 278 | t.left_child = self.left_child 279 | self.left_child = t 280 | def insert_right(self, new_node): 281 | if self.right_child == None: 282 | self.right_child = BinaryTree(new_node) 283 | else: 284 | t = BinaryTree(new_node) 285 | t.right_child = self.right_child 286 | self.right_child = t 287 | def get_right_child(self): 288 | return self.right_child 289 | def get_left_child(self): 290 | return self.left_child 291 | def set_root_val(self, obj): 292 | self.key = obj 293 | def get_root_val(self): 294 | return self.key 295 | 296 | r = BinaryTree('a') 297 | print(r.get_root_val()) 298 | print(r.get_left_child()) 299 | r.insert_left('b') 300 | print(r.get_left_child()) 301 | print(r.get_left_child().get_root_val()) 302 | r.insert_right('c') 303 | print(r.get_right_child()) 304 | print(r.get_right_child().get_root_val()) 305 | r.get_right_child().set_root_val('hello') 306 | print(r.get_right_child().get_root_val()) 307 | ``` 308 | 309 | 6.二叉堆:根据堆的性质又可以分为最小堆和最大堆,是一种非常好的优先队列。在最小堆中孩子节点一定大于等于其父亲节点,最大堆反之。二叉堆实际上一棵完全二叉树,并且满足堆的性质。对于插入和查找操作的时间复杂度度都是$O(logn)$。 310 | 311 | 它的插入操作图示: 312 | 313 | ![image](https://hujiaweibujidao.github.io/images/heapinsert.png) 314 | 315 | 去除根节点的操作图示: 316 | 317 | ![image](https://hujiaweibujidao.github.io/images/heapdel.png) 318 | 319 | 注意,下面的实现中默认在初始的堆列表中插入了一个元素0,这样做可以保证堆的真实有效的元素个数和current_size值对应,而且最后一个元素的索引就对应了current_size。 320 | 321 | 此外,从list中建堆的过程需要从最后一个非叶子节点开始到第一个非叶子节点(根节点)进行。这篇文章[来自博客园](http://www.cnblogs.com/Anker/archive/2013/01/23/2873422.html)解释了这个问题。建堆的过程如下:[下图摘自原博客,版权归原作者,谢谢] 322 | 323 | ![image](https://hujiaweibujidao.github.io/images/heapbuild.png) 324 | 325 | ```python 326 | class BinHeap: 327 | def __init__(self): 328 | self.heap_list = [0] 329 | self.current_size = 0 330 | def perc_up(self, i): 331 | while i // 2 > 0: # >0 means this node is still available 332 | if self.heap_list[i] < self.heap_list[i // 2]: 333 | tmp = self.heap_list[i // 2] 334 | self.heap_list[i // 2] = self.heap_list[i] 335 | self.heap_list[i] = tmp 336 | i = i // 2 337 | def insert(self, k): 338 | self.heap_list.append(k) 339 | self.current_size = self.current_size + 1 340 | self.perc_up(self.current_size) 341 | def perc_down(self, i): 342 | while (i * 2) <= self.current_size: 343 | mc = self.min_child(i) 344 | if self.heap_list[i] > self.heap_list[mc]: 345 | tmp = self.heap_list[i] 346 | self.heap_list[i] = self.heap_list[mc] 347 | self.heap_list[mc] = tmp 348 | i = mc 349 | def min_child(self, i): 350 | if i * 2 + 1 > self.current_size: 351 | return i * 2 352 | else: 353 | if self.heap_list[i * 2] < self.heap_list[i * 2 + 1]: 354 | return i * 2 355 | else: 356 | return i * 2 + 1 357 | def del_min(self): 358 | ret_val = self.heap_list[1] 359 | self.heap_list[1] = self.heap_list[self.current_size] 360 | self.current_size = self.current_size - 1 361 | self.heap_list.pop() 362 | self.perc_down(1) 363 | return ret_val 364 | 365 | def build_heap(self, a_list): 366 | i = len(a_list) // 2 367 | self.current_size = len(a_list) 368 | self.heap_list = [0] + a_list[:] #append original list 369 | while (i > 0): 370 | #build the heap we only need to deal the first part! 371 | self.perc_down(i) 372 | i=i-1 373 | 374 | a_list=[9, 6, 5, 2, 3]; 375 | bh=BinHeap(); 376 | bh.build_heap(a_list); 377 | print(bh.heap_list) 378 | print(bh.current_size) 379 | bh.insert(10) 380 | bh.insert(7) 381 | print(bh.heap_list) 382 | bh.del_min(); 383 | print(bh.heap_list) 384 | print(bh.current_size) 385 | ``` 386 | 387 | 关于二叉查找树等内容请见[树的总结](https://hujiaweibujidao.github.io/blog/2014/05/08/python-algorithms-Trees/)。 388 | 389 | 390 | -------------------------------------------------------------------------------- /ReadingNotes-PythonAlgorithms/2014-07-01-python-algorithms-counting-101.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Python Algorithms - C3 Counting 101" 4 | date: 2014-07-01 10:30 5 | categories: algorithm 6 | --- 7 | Python算法设计篇(3) Chapter 3: Counting 101 8 | 9 | > The greatest shortcoming of the human race is our inability to understand the exponential function. 10 | —— Dr. Albert A. Bartlett, World Population Balance Board of Advisors 11 | 12 | 13 | 原书主要介绍了一些基础数学,例如排列组合以及递归循环等,但是本节只重点介绍计算算法的运行时间的三种方法 14 | 15 | 因为本节内容都很简单,所以我只是浏览了一下,重要的只有计算算法的运行时间的三种方法:1.代换法; 2.递归树法; 3.主定理法。 16 | 17 | 1.代换法 18 | 19 | 代换法一般是先猜测解的形式,然后用数学归纳法来证明它 20 | 21 | 下面是算法导论中的一个求解例子 22 | 23 | ![image](https://hujiaweibujidao.github.io/images/algos/sub1.png) 24 | 25 | 有意思的是,还有一类问题可以通过变量替换变成容易求解的形式 26 | 27 | ![image](https://hujiaweibujidao.github.io/images/algos/sub2.png) 28 | 29 | 下面是常用的一些递归式以及它们对应的结果还有实际算法实例 30 | 31 | ![image](https://hujiaweibujidao.github.io/images/algos/sub3.png) 32 | 33 | 2.递归树法 34 | 35 | 这种方法就是通过画递归树,然后对每层进行求和,最后将每层的结果相加得到对总的算法运行时间的估计 36 | 37 | ![image](https://hujiaweibujidao.github.io/images/algos/rectree.png) 38 | 39 | 3.主定理法 40 | 41 | 这种方法大家最喜欢,给出了一种就像是公式一样的结论,虽然它没有覆盖所有的情况,而且证明非常复杂,但是很多情况下都是可以直接使用的,还有,需要注意主定理的不同情况下的条件,尤其是多项式大于和多项式小于! 42 | 43 | ![image](https://hujiaweibujidao.github.io/images/algos/master.png) 44 | 45 | 不喜欢本节的可以跳过,不留练习了这次,嘿嘿,想练习的话刷算法导论的题目吧 46 | 47 | 返回[Python数据结构与算法设计篇目录](https://hujiaweibujidao.github.io/python/) 48 | 49 | 50 | -------------------------------------------------------------------------------- /ReadingNotes-PythonAlgorithms/2014-07-01-python-algorithms-divide-and-combine-and-conquer.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Python Algorithms - C6 Divide and Combine and Conquer" 4 | date: 2014-07-01 11:00 5 | categories: algorithm 6 | --- 7 | Python算法设计篇(6) Chapter 6: Divide and Combine and Conquer 8 | 9 | > Divide and rule, a sound motto; Unite and lead, a better one. 10 | ——Johann Wolfgang von Goethe, Gedichte 11 | 12 | 本节主要介绍分治法策略,提到了树形问题的平衡性以及基于分治策略的排序算法 13 | 14 | 本节的标题写全了就是:**divide the problem instance, solve subproblems recursively, combine the results, and thereby conquer the problem** 15 | 16 | 简言之就是将原问题划分成几个小问题,然后递归地解决这些小问题,最后综合它们的解得到问题的解。分治法的思想我想大家都已经很清楚了,所以我就不过多地介绍它了,下面摘录些原书中的重点内容。 17 | 18 | 1.平衡性是树形问题的关键 19 | 20 | 如果我们将子问题看做节点,将问题之间的依赖关系(dependencies or reductions)看做边,那么我们就得到了子问题图(subproblem graph ),最简单的子问题图就是树形结构问题,例如我们之前提到过的递归树的形式。也许子问题之间有依赖关系,但是对于每个子问题我们都是可以独立求解的,根据我们前面学的内容,只要我们能够找到合适的规约,我们就可以直接使用递归形式的算法将这个问题解决。[至于子问题间有重叠的话我们后面会详细介绍动态规划的方法来解决这类问题,这里我们不考虑] 21 | 22 | 前面我们学的内容已经完全足够我们理解分治法了,第3节的Divide-and-conquer recurrences,第4节的Strong induction,还有第5节的Recursive traversal 23 | 24 | The recurrences tell you something about the performance involved, the induction gives you a tool for understanding how the algorithms work, and the recursive traversal (DFS in trees) is a raw skeleton for the algorithms. 25 | 26 | 但是,我们前面介绍Induction时总是从 n-1 到 n,这节我们要考虑平衡性,我们希望从 n/2 到 n,也就是说我们假设我们能够解决规模为原问题一半的子问题。 27 | 28 | 假设对于同一个问题,我们有下面两个解决方案,哪个方案更好些呢? 29 | 30 | (1)T(n)=T(n-1)+T(1)+n 31 | 32 | (2)T(n)=2T(n/2)+n 33 | 34 | 如果从时间复杂度来评价的话,前者是$O(n^2)$的,而后者是$O(n lg n)$的,所以是后者更好些。下图以递归树的形式显示了两种方案的不同 35 | 36 | ![image](https://hujiaweibujidao.github.io/images/algos/balance.png) 37 | 38 | 2.典型的分治法 39 | 40 | 下面是典型分治法的伪代码,很容易理解对吧 41 | 42 | ```python 43 | # Pseudocode(ish) 44 | def divide_and_conquer(S, divide, combine): 45 | if len(S) == 1: return S 46 | L, R = divide(S) 47 | A = divide_and_conquer(L, divide, combine) 48 | B = divide_and_conquer(R, divide, combine) 49 | return combine(A, B) 50 | ``` 51 | 52 | 用图形来表示如下,上面部分是分(division),下面部分是合(combination) 53 | 54 | ![image](https://hujiaweibujidao.github.io/images/algos/dcc.png) 55 | 56 | 二分查找是最常用的采用分治策略的算法,我们经常使用的版本控制系统(evision control systems=RCSs)查找代码中发生某个变化是在哪个版本时采用的正是二分查找策略。 57 | 58 | Python中`bisect`模块也正是利用了二分查找策略,其中方法`bisect`的作用是返回要找到元素的位置,`bisect_left`是其左边的那个位置,而`bisect_right`和`bisect`的作用是一样的,函数`insort`也是这样设计的。 59 | 60 | ``` 61 | from bisect import bisect 62 | a = [0, 2, 3, 5, 6, 7, 8, 8, 9] 63 | print bisect(a, 5) #4 64 | from bisect import bisect_left, bisect_right 65 | print bisect_left(a, 5) #3 66 | print bisect_right(a, 5) #4 67 | ``` 68 | 69 | 二分查找策略很好,但是它有个前提,序列必须是有序的才可以这样做,为了高效地得到中间位置的元素,于是就有了二叉搜索树,这个我们在[数据结构篇中已经详细介绍过了](https://hujiaweibujidao.github.io/blog/2014/05/08/python-algorithms-Trees/),下面给出一份完整的二叉搜索树的实现,不过多介绍了。 70 | 71 | ``` 72 | class Node: 73 | lft = None 74 | rgt = None 75 | def __init__(self, key, val): 76 | self.key = key 77 | self.val = val 78 | 79 | def insert(node, key, val): 80 | if node is None: return Node(key, val) # Empty leaf: Add node here 81 | if node.key == key: node.val = val # Found key: Replace val 82 | elif key < node.key: # Less than the key? 83 | node.lft = insert(node.lft, key, val) # Go left 84 | else: # Otherwise... 85 | node.rgt = insert(node.rgt, key, val) # Go right 86 | return node 87 | 88 | def search(node, key): 89 | if node is None: raise KeyError # Empty leaf: It's not here 90 | if node.key == key: return node.val # Found key: Return val 91 | elif key < node.key: # Less than the key? 92 | return search(node.lft, key) # Go left 93 | else: # Otherwise... 94 | return search(node.rgt, key) # Go right 95 | 96 | class Tree: # Simple wrapper 97 | root = None 98 | def __setitem__(self, key, val): 99 | self.root = insert(self.root, key, val) 100 | def __getitem__(self, key): 101 | return search(self.root, key) 102 | def __contains__(self, key): 103 | try: search(self.root, key) 104 | except KeyError: return False 105 | return True 106 | ``` 107 | 108 | 比较:二分法,二叉搜索树,字典 109 | 110 | 三者都是用来提高搜索效率的,但是各有区别。二分法只能作用于有序数组(例如排序后的Python的list),但是有序数组较难维护,因为插入需要线性时间;二叉搜索树有些复杂,动态变化着,但是插入和删除效率高了些;字典的效率相比而言就比较好了,插入删除操作的平均时间都是常数的,只不过它还需要计算下hash值才能确定元素的位置。 111 | 112 | 3.顺序统计量 113 | 114 | 在算法导论中一组序列中的第 k 大的元素定义为顺序统计量 115 | 116 | 如果我们想要在线性时间内找到一组序列中的前 k 大的元素怎么做呢?很显然,如果这组序列中的数字范围比较大的话,我们就不能使用线性排序算法,而其他的基于比较的排序算法的最好的平均时间复杂度($O(n lg n)$)都超过了线性时间,怎么办呢? 117 | 118 | [扩展知识:在Python中如果泥需要求前 k 小或者前 k 大的元素,可以使用`heapq`模块中的`nsmallest`或者`nlargest`函数,如果 k 很小的话这种方式会好些,但是如果 k 很大的话,不如直接去调用`sort`函数] 119 | 120 | 要想解决这个问题,我们还是要用分治法,采用类似快排中的`partition`将序列进行划分(divide),也就是说找一个主元(pivot),然后用主元作为基准将序列分成两部分,一部分小于主元,另一半大于主元,比较下主元最终的位置值和 k的大小关系,然后确定后面在哪个部分继续进行划分。如果这里不理解的话请移步阅读前面[数据结构篇之排序中的快速排序](https://hujiaweibujidao.github.io/blog/2014/05/07/python-algorithms-sort/) 121 | 122 | 基于上面的想法就有了下面的实现,需要注意的是下面的`partition`函数不是就地划分的哟 123 | 124 | ``` 125 | #A Straightforward Implementation of Partition and Select 126 | def partition(seq): 127 | pi, seq = seq[0], seq[1:] # Pick and remove the pivot 128 | lo = [x for x in seq if x <= pi] # All the small elements 129 | hi = [x for x in seq if x > pi] # All the large ones 130 | return lo, pi, hi # pi is "in the right place" 131 | 132 | def select(seq, k): 133 | lo, pi, hi = partition(seq) # [<= pi], pi, [> pi] 134 | m = len(lo) 135 | if m == k: return pi # We found the kth smallest 136 | elif m < k: # Too far to the left 137 | return select(hi, k-m-1) # Remember to adjust k 138 | else: # Too far to the right 139 | return select(lo, k) # Just use original k here 140 | 141 | seq = [3, 4, 1, 6, 3, 7, 9, 13, 93, 0, 100, 1, 2, 2, 3, 3, 2] 142 | print partition(seq) #([1, 3, 0, 1, 2, 2, 3, 3, 2], 3, [4, 6, 7, 9, 13, 93, 100]) 143 | print select([5, 3, 2, 7, 1], 3) #5 144 | print select([5, 3, 2, 7, 1], 4) #7 145 | ans = [select(seq, k) for k in range(len(seq))] 146 | seq.sort() 147 | print ans == seq #True 148 | ``` 149 | 150 | 细读上面的代码发现主元默认就是第一个元素,你也许会想这么选科学吗?事实证明这种随机选择的期望运行时间的确是线性的,但是如果每次都选择的不好,导致划分的时候每次都特别不平衡将会导致运行时间变成平方时间,那有没有什么选主元的办法能够保证算法的运行时间是线性的?的确有!但是比较麻烦,实际使用的并不多,感兴趣可以看下面的内容 151 | 152 | [**我还未完全理解,算法导论上也有相应的介绍,感兴趣不妨去阅读下**] 153 | 154 | It turns out guaranteeing that the pivot is even a small percentage into the sequence (that is, not at either end, or a constant number of steps from it) is enough for the running time to be linear. In 1973, a group of algorists (Blum, Floyd, Pratt, Rivest, and Tarjan) came up with a version of the algorithm that gives exactly this kind of guarantee. 155 | 156 | The algorithm is a bit involved, but the core idea is simple enough: first divide the sequence into groups of five (or some other small constant). Find the median in each, using (for example) a simple sorting algorithm. So far, we’ve used only linear time. Now, find the median among these medians, using the linear selection algorithm recursively. (This will work, because the number of medians is smaller than the size of the original sequence—still a bit mind-bending.) The resulting value is a pivot that is guaranteed to be good enough to avoid the degenerate recursion—use it as a pivot in your selection. 157 | 158 | In other words, the algorithm is used recursively in two ways: first, on the sequence of medians, to find a good pivot, and second, on the original sequence, using this pivot. 159 | 160 | While the algorithm is important to know about for theoretical reasons (because it means selection can be done in guaranteed linear time), you’ll probably never actually use it in practice. 161 | 162 | 3.二分排序 163 | 164 | 前面我们介绍了二分查找,下面看看如何进行二分排序,这里不再详细介绍快排和合并排序的思想了,如果不理解的话请移步阅读前面[数据结构篇之排序](https://hujiaweibujidao.github.io/blog/2014/05/07/python-algorithms-sort/) 165 | 166 | 利用前面的`partition`函数快排代码呼之欲出 167 | 168 | ``` 169 | def quicksort(seq): 170 | if len(seq) <= 1: return seq # Base case 171 | lo, pi, hi = partition(seq) # pi is in its place 172 | return quicksort(lo) + [pi] + quicksort(hi) # Sort lo and hi separately 173 | 174 | seq = [7, 5, 0, 6, 3, 4, 1, 9, 8, 2] 175 | print quicksort(seq) #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 176 | ``` 177 | 178 | 合并排序是更加典型的采用分治法策略来进行的排序,注意后半部分是比较谁大然后调用`append`函数,最后`reverse`一下,因为如果是比较谁小的话就要调用`insert`函数,它的效率不如`append` 179 | 180 | ``` 181 | # Mergesort, repeated from Chapter 3 (with some modifications) 182 | def mergesort(seq): 183 | mid = len(seq)//2 # Midpoint for division 184 | lft, rgt = seq[:mid], seq[mid:] 185 | if len(lft) > 1: lft = mergesort(lft) # Sort by halves 186 | if len(rgt) > 1: rgt = mergesort(rgt) 187 | res = [] 188 | while lft and rgt: # Neither half is empty 189 | if lft[-1] >= rgt[-1]: # lft has greatest last value 190 | res.append(lft.pop()) # Append it 191 | else: # rgt has greatest last value 192 | res.append(rgt.pop()) # Append it 193 | res.reverse() # Result is backward 194 | return (lft or rgt) + res # Also add the remainder 195 | ``` 196 | 197 | [扩展知识:Python内置的排序算法TimSort,看起来好复杂的样子啊,我果断只是略读了一下下] 198 | 199 | ![image](https://hujiaweibujidao.github.io/images/algos/timsort.png) 200 | 201 | [**章节最后作者介绍了一些关于树平衡的内容,提到2-3树,我对树平衡不是特别感兴趣,也不是很明白,所以跳过不总结,感兴趣的不妨阅读下**] 202 | 203 | 问题6-2. 三分查找 204 | 205 | Binary search divides the sequence into two approximately equal parts in each recursive step. Consider ternary search, which divides the sequence into three parts. What would its asymptotic complexity be? What can you say about the number of comparisons in binary and ternary search? 206 | 207 | 题目就是说让我们分析下三分查找的时间复杂度,和二分查找进行下对比 208 | 209 | The asymptotic running time would be the same. The number of comparison goes up, however. To see this, consider the recurrences B(n) = B(n/2) + 1 and T(n) = T(n/3) + 2 for binary and ternary search, respectively (with base cases B(1) = T(1) = 0 and B(2) = T(2) = 1). You can show (by induction) that 210 | B(n) < lg n + 1 < T(n). 211 | 212 | 返回[Python数据结构与算法设计篇目录](https://hujiaweibujidao.github.io/python/) 213 | 214 | 215 | -------------------------------------------------------------------------------- /ReadingNotes-PythonAlgorithms/2014-07-01-python-algorithms-dynamic-programming.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Python Algorithms - C8 Dynamic Programming" 4 | date: 2014-07-01 11:20 5 | categories: algorithm 6 | --- 7 | Python算法设计篇(8) Chapter 8 Tangled Dependencies and Memoization 8 | 9 | > Twice, adv. Once too often. 10 | —— Ambrose Bierce, The Devil’s Dictionary 11 | 12 | 本节主要结合一些经典的动规问题介绍动态规划的备忘录法和迭代法这两种实现方式,并对这两种方式进行对比 13 | 14 | [**这篇文章实际写作时间在这个系列文章之前,所以写作风格可能略有不同,嘿嘿**] 15 | 16 | 大家都知道,动态规划算法一般都有下面两种实现方式,前者我称为递归版本,后者称为迭代版本,根据前面的知识可知,这两个版本是可以相互转换的 17 | 18 | **1.直接自顶向下实现递归式,并将中间结果保存,这叫备忘录法;** 19 | 20 | **2.按照递归式自底向上地迭代,将结果保存在某个数据结构中求解。** 21 | 22 | 编程有一个原则`DRY=Don’t Repeat Yourself`,就是说你的代码不要重复来重复去的,这个原则同样可以用于理解动态规划,动态规划除了满足最优子结构,它还存在子问题重叠的性质,我们不能重复地去解决这些子问题,所以我们将子问题的解保存起来,类似缓存机制,之后遇到这个子问题时直接取出子问题的解。 23 | 24 | 举个简单的例子,斐波那契数列中的元素的计算,很简单,我们写下如下的代码: 25 | 26 | ```python 27 | def fib(i): 28 | if i<2: return 1 29 | return fib(i-1)+fib(i-2) 30 | ``` 31 | 32 | 好,来测试下,运行`fib(10)`得到结果69,不错,速度也还行,换个大的数字,试试100,这时你会发现,这个程序执行不出结果了,为什么?递归太深了!要计算的子问题太多了! 33 | 34 | 所以,我们需要改进下,我们保存每次计算出来的子问题的解,用什么保存呢?用Python中的dict!那怎么实现保存子问题的解呢?用Python中的装饰器! 35 | 36 | 如果不是很了解Python的装饰器,可以快速看下[这篇总结中关于装饰器的解释:Python Basics](https://hujiaweibujidao.github.io/blog/2014/05/10/python-tips1/) 37 | 38 | 修改刚才的程序,得到如下代码,定义一个函数`memo`返回我们需要的装饰器,这里用`cache`保存子问题的解,key是方法的参数,也就是数字`n`,值就是`fib(n)`返回的解。 39 | 40 | ``` 41 | from functools import wraps 42 | 43 | def memo(func): 44 | cache={} 45 | @wraps(func) 46 | def wrap(*args): 47 | if args not in cache: 48 | cache[args]=func(*args) 49 | return cache[args] 50 | return wrap 51 | 52 | @memo 53 | def fib(i): 54 | if i<2: return 1 55 | return fib(i-1)+fib(i-2) 56 | ``` 57 | 重新运行下`fib(100)`,你会发现这次很快就得到了结果`573147844013817084101`,这就是动态规划的威力,上面使用的是第一种带备忘录的递归实现方式。 58 | 59 | **带备忘录的递归方式的优点就是易于理解,易于实现,代码简洁干净,运行速度也不错,直接从需要求解的问题出发,而且只计算需要求解的子问题,没有多余的计算。但是,它也有自己的缺点,因为是递归形式,所以有限的栈深度是它的硬伤,有些问题难免会出现栈溢出了。** 60 | 61 | 于是,迭代版本的实现方式就诞生了! 62 | 63 | **迭代实现方式有2个好处:1.运行速度快,因为没有用栈去实现,也避免了栈溢出的情况;2.迭代实现的话可以不使用dict来进行缓存,而是使用其他的特殊cache结构,例如多维数组等更为高效的数据结构。** 64 | 65 | 那怎么把递归版本转变成迭代版本呢? 66 | 67 | **这就是递归实现和迭代实现的重要区别:递归实现不需要去考虑计算顺序,只要给出问题,然后自顶向下去解就行;而迭代实现需要考虑计算顺序,并且顺序很重要,算法在运行的过程中要保证当前要计算的问题中的子问题的解已经是求解好了的。** 68 | 69 | 斐波那契数列的迭代版本很简单,就是按顺序来计算就行了,不解释,关键是你可以看到我们就用了3个简单变量就求解出来了,没有使用任何高级的数据结构,节省了大量的空间。 70 | 71 | ```python 72 | def fib_iter(n): 73 | if n<2: return 1 74 | a,b=1,1 75 | while n>=2: 76 | c=a+b 77 | a=b 78 | b=c 79 | n=n-1 80 | return c 81 | ``` 82 | 83 | 斐波那契数列的变种经常出现在上楼梯的走法问题中,每次只能走一个台阶或者两个台阶,广义上思考的话,**动态规划也就是一个连续决策问题,到底当前这一步是选择它(走一步)还是不选择它(走两步)呢?** 84 | 85 | 其他问题也可以很快地变相思考发现它们其实是一样的,例如求二项式系数`C(n,k)`,杨辉三角(求从源点到目标点有多少种走法)等等问题。 86 | 87 | 二项式系数`C(n,k)`表示从n个中选k个,假设我们现在处理n个中的第1个,考虑是否选择它。如果选择它的话,那么我们还需要从剩下的n-1个中选k-1个,即`C(n-1,k-1)`;如果不选择它的话,我们需要从剩下的n-1中选k个,即`C(n-1,k)`。所以,`C(n,k)=C(n-1,k-1)+C(n-1,k)`。 88 | 89 | 结合前面的装饰器,我们很快便可以实现求二项式系数的递归实现代码,其中的`memo`函数完全没变,只是在函数`cnk`前面添加了`@memo`而已,就这么简单! 90 | 91 | ``` 92 | from functools import wraps 93 | 94 | def memo(func): 95 | cache={} 96 | @wraps(func) 97 | def wrap(*args): 98 | if args not in cache: 99 | cache[args]=func(*args) 100 | return cache[args] 101 | return wrap 102 | 103 | @memo 104 | def cnk(n,k): 105 | if k==0: return 1 #the order of `if` should not change!!! 106 | if n==0: return 0 107 | return cnk(n-1,k)+cnk(n-1,k-1) 108 | ``` 109 | 110 | 它的迭代版本也比较简单,这里使用了`defaultdict`,略高级的数据结构,和dict不同的是,当查找的key不存在对应的value时,会返回一个默认的值,这个很有用,下面的代码可以看到。 111 | 如果不了解`defaultdict`的话可以看下[Python中的高级数据结构](http://blog.jobbole.com/65218/) 112 | 113 | ``` 114 | from collections import defaultdict 115 | 116 | n,k=10,7 117 | C=defaultdict(int) 118 | for row in range(n+1): 119 | C[row,0]=1 120 | for col in range(1,k+1): 121 | C[row,col]=C[row-1,col-1]+C[row-1,col] 122 | 123 | print(C[n,k]) #120 124 | ``` 125 | 126 | 杨辉三角大家都熟悉,在国外这个叫`Pascal Triangle`,它和二项式系数特别相似,看下图,除了两边的数字之外,里面的任何一个数字都是由它上面相邻的两个元素相加得到,想想`C(n,k)=C(n-1,k-1)+C(n-1,k)`不也就是这个含义吗? 127 | 128 | ![image](https://hujiaweibujidao.github.io/images/algos/sanjiao.png) 129 | 130 | 所以说,顺序对于迭代版本的动态规划实现很重要,下面举个实例,用动态规划解决有向无环图的单源最短路径问题。假设有如下图所示的图,当然,我们看到的是这个有向无环图经过了拓扑排序之后的结果,从a到f的最短路径用灰色标明了。 131 | 132 | ![image](https://hujiaweibujidao.github.io/images/algos/dag_sp.png) 133 | 134 | 好,怎么实现呢? 135 | 136 | 我们有两种思考方式: 137 | 138 | **1."去哪里?":我们顺向思维,首先假设从a点出发到所有其他点的距离都是无穷大,然后,按照拓扑排序的顺序,从a点出发,接着更新a点能够到达的其他的点的距离,那么就是b点和f点,b点的距离变成2,f点的距离变成9。因为这个有向无环图是经过了拓扑排序的,所以按照拓扑顺序访问一遍所有的点(到了目标点就可以停止了)就能够得到a点到所有已访问到的点的最短距离,也就是说,当到达哪个点的时候,我们就找到了从a点到该点的最短距离,拓扑排序保证了后面的点不会指向前面的点,所以访问到后面的点时不可能再更新它前面的点的最短距离!(这里的更新也就是[前面第4节介绍过的relaxtion](https://hujiaweibujidao.github.io/blog/2014/07/01/python-algorithms-induction/))这种思维方式的代码实现就是迭代版本。** 139 | 140 | [**这里涉及到了拓扑排序,[前面第5节Traversal中介绍过了](https://hujiaweibujidao.github.io/blog/2014/07/01/python-algorithms-traversal/),这里为了方便没看前面的童鞋理解,W直接使用的是经过拓扑排序之后的结果。**] 141 | 142 | ``` 143 | def topsort(W): 144 | return W 145 | 146 | def dag_sp(W, s, t): 147 | d = {u:float('inf') for u in W} # 148 | d[s] = 0 149 | for u in topsort(W): 150 | if u == t: break 151 | for v in W[u]: 152 | d[v] = min(d[v], d[u] + W[u][v]) 153 | return d[t] 154 | 155 | #邻接表 156 | W={0:{1:2,5:9},1:{2:1,3:2,5:6},2:{3:7},3:{4:2,5:3},4:{5:4},5:{}} 157 | s,t=0,5 158 | print(dag_sp(W,s,t)) #7 159 | ``` 160 | 161 | 用图来表示计算过程就是下面所示: 162 | 163 | ![image](https://hujiaweibujidao.github.io/images/algos/dag_sp_iter.png) 164 | 165 | **2."从哪里来?":我们逆向思维,目标是要到f,那从a点经过哪个点到f点会近些呢?只能是求解从a点出发能够到达的那些点哪个距离f点更近,这里a点能够到达b点和f点,f点到f点距离是0,但是a到f点的距离是9,可能不是最近的路,所以还要看b点到f点有多近,看b点到f点有多近就是求解从b点出发能够到达的那些点哪个距离f点更近,所以又绕回来了,也就是递归下去,直到我们能够回答从a点经过哪个点到f点会更近。这种思维方式的代码实现就是递归版本。** 166 | 167 | 这种情况下,不需要输入是经过了拓扑排序的,所以你可以任意修改输入`W`中节点的顺序,结果都是一样的,而上面采用迭代实现方式必须要是拓扑排序了的,从中你就可以看出迭代版本和递归版本的区别了。 168 | 169 | ``` 170 | from functools import wraps 171 | def memo(func): 172 | cache={} 173 | @wraps(func) 174 | def wrap(*args): 175 | if args not in cache: 176 | cache[args]=func(*args) 177 | # print('cache {0} = {1}'.format(args[0],cache[args])) 178 | return cache[args] 179 | return wrap 180 | 181 | def rec_dag_sp(W, s, t): 182 | @memo 183 | def d(u): 184 | if u == t: return 0 185 | return min(W[u][v]+d(v) for v in W[u]) 186 | return d(s) 187 | 188 | #邻接表 189 | W={0:{1:2,5:9},1:{2:1,3:2,5:6},2:{3:7},3:{4:2,5:3},4:{5:4},5:{}} 190 | s,t=0,5 191 | print(rec_dag_sp(W,s,t)) #7 192 | ``` 193 | 194 | 用图来表示计算过程就如下图所示: 195 | 196 | ![image](https://hujiaweibujidao.github.io/images/algos/dag_sp_rec.png) 197 | 198 | [扩展内容:对DAG求单源最短路径的动态规划问题的总结,比较难理解,附上原文] 199 | 200 | Although the basic algorithm is the same, there are many ways of finding the shortest path in a DAG, and, by extension, solving most DP problems. You could do it recursively, with memoization, or you could do it iteratively, with relaxation. For the recursion, you could start at the first node, try various “next steps,” and then recurse on the remainder, or (if you graph representation permits) you could look at the last node and try “previous steps” and recurse on the initial part. The former is usually much more natural, while the latter corresponds more closely to what happens in the iterative version. 201 | 202 | Now, if you use the iterative version, you also have two choices: you can relax the edges out of each node (in topologically sorted order), or you can relax all edges into each node. The latter more obviously yields a correct result but requires access to nodes by following edges backward. This isn’t as far-fetched as it seems when you’re working with an implicit DAG in some nongraph problem. (For example, in the longest increasing subsequence problem, discussed later in this chapter, looking at all backward “edges” can be a useful perspective.) 203 | 204 | Outward relaxation, called reaching, is exactly equivalent when you relax all edges. As explained, once you get to a node, all its in-edges will have been relaxed anyway. However, with reaching, you can do something that’s hard in the recursive version (or relaxing in-edges): pruning. If, for example, you’re only interested in finding all nodes that are within a distance r, you can skip any node that has distance estimate greater than r. You will still need to visit every node, but you can potentially ignore lots of edges during the relaxation. This won’t affect the asymptotic running time, though (Exercise 8-6). 205 | 206 | Note that finding the shortest paths in a DAG is surprisingly similar to, for example, finding the longest path, or even counting the number of paths between two nodes in a DAG. The latter problem is exactly what we did with Pascal’s triangle earlier; the exact same approach would work for an arbitrary graph. These things aren’t quite as easy for general graphs, though. Finding shortest paths in a general graph is a bit harder (in fact, Chapter 9 is devoted to this topic), while finding the longest path is an unsolved problem (see Chapter 11 for more on this). 207 | 208 | 211 | 212 | 好,我们差不多搞清楚了动态规划的本质以及两种实现方式的优缺点,下面我们来实践下,举最常用的例子:[矩阵链乘问题,内容较多,所以请点击链接过去阅读完了之后回来看总结](https://hujiaweibujidao.github.io/blog/2014/05/18/matrix-chain/)! 213 | 214 | OK,希望我把动态规划讲清楚了,总结下:**动态规划其实就是一个连续决策的过程,每次决策我们可能有多种选择(二项式系数和0-1背包问题中我们只有两个选择,DAG图的单源最短路径中我们的选择要看点的出边或者入边,矩阵链乘问题中就是矩阵链可以分开的位置总数...),我们每次选择最好的那个作为我们的决策。所以,动态规划的时间复杂度其实和这两者有关,也就是子问题的个数以及子问题的选择个数,一般情况下动态规划算法的时间复杂度就是两者的乘积。** 215 | 216 | **动态规划有两种实现方式:一种是带备忘录的递归形式,这种方式直接从原问题出发,遇到子问题就去求解子问题并存储子问题的解,下次遇到的时候直接取出来,问题求解的过程看起来就像是先自顶向下地展开问题,然后自下而上的进行决策;另一个实现方式是迭代方式,这种方式需要考虑如何给定一个子问题的求解方式,使得后面求解规模较大的问题是需要求解的子问题都已经求解好了,它的缺点就是可能有些子问题不要算但是它还是算了,而递归实现方式只会计算它需要求解的子问题。** 217 | 218 | 练习1:来试试写写最长公共子序列吧,[这篇文章中给出了Python版本的5种实现方式](https://hujiaweibujidao.github.io/blog/2014/05/19/longest-common-subsequence/)哟! 219 | 220 | 练习2:算法导论问题 15-4: Planning a company party 计划一个公司聚会 221 | 222 | Start example 223 | Professor Stewart is consulting for the president of a corporation that is planning a company party. The company has a hierarchical structure; that is, the supervisor relation forms a tree rooted at the president. The personnel office has ranked each employee with a conviviality rating, which is a real number. In order to make the party fun for all attendees, the president does not want both an employee and his or her immediate supervisor to attend. 224 | 225 | Professor Stewart is given the tree that describes the structure of the corporation, using the left-child, right-sibling representation described in Section 10.4. Each node of the tree holds, in addition to the pointers, the name of an employee and that employee's conviviality ranking. Describe an algorithm to make up a guest list that maximizes the sum of the conviviality ratings of the guests. Analyze the running time of your algorithm. 226 | 227 | 原问题可以转换成:假设有一棵树,用左孩子右兄弟的表示方式表示,树的每个结点有个值,选了某个结点,就不能选择它的父结点,求整棵树选的节点值最大是多少。 228 | 229 | 假设如下: 230 | 231 | dp[i][0]表示不选i结点时,i子树的最大价值 232 | 233 | dp[i][1]表示选i结点时,i子树的最大价值 234 | 235 | 列出状态方程 236 | 237 | dp[i][0] = sum(max(dp[u][0], dp[u][1])) $\quad$ (如果不选i结点,u为结点i的儿子) 238 | 239 | dp[i][1] = sum(dp[u][0]) + val[i] $\quad$ (如果选i结点,val[i]表示i结点的价值) 240 | 241 | 最后就是求max(dp[root][0], dp[root][1]) 242 | 243 | 返回[Python数据结构与算法设计篇目录](https://hujiaweibujidao.github.io/python/) 244 | 245 | 246 | -------------------------------------------------------------------------------- /ReadingNotes-PythonAlgorithms/2014-07-01-python-algorithms-induction.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Python Algorithms - C4 Induction and Recursion and Reduction" 4 | date: 2014-07-01 10:40 5 | categories: algorithm 6 | --- 7 | Python算法设计篇(4) Chapter 4: Induction and Recursion and Reduction 8 | 9 | > You must never think of the whole street at once, understand? You must only concentrate on the next step, the next breath, the next stroke of the broom, and the next, and the next. Nothing else. 10 | ——Beppo Roadsweeper, in Momo by Michael Ende 11 | 12 | **注:本节中我给定下面三个重要词汇的中文翻译分别是:Induction(推导)、Recursion(递归)和Reduction(规约)** 13 | 14 | 本节主要介绍算法设计的三个核心知识:Induction(推导)、Recursion(递归)和Reduction(规约),这是原书的重点和难点部分 15 | 16 | 正如标题所示,本节主要介绍下面三部分内容: 17 | 18 | • Reduction means transforming one problem to another. We normally reduce an unknown problem to one we know how to solve. The reduction may involve transforming both the input (so it works with the new problem) and the output (so it’s valid for the original problem). 19 | 20 | Reduction(规约)意味着对问题进行转换,例如将一个未知的问题转换成我们能够解决的问题,转换的过程可能涉及到对问题的输入输出的转换。[问题规约在证明一个问题是否是NP完全问题时经常用到,如果我们能够将一个问题规约成一个我们已知的NP完全问题的话,那么这个问题也是NP完全问题] 21 | 22 | 下面给幅图你就能够明白了,实际上很多时候我们遇到一个问题时都是找一个我们已知的类似的能够解决的问题,然后将这个我们新问题A规约到那个已知的问题B,中间经过一些输入输出的转换,我们就能够解决新问题A了。 23 | 24 | ![image](https://hujiaweibujidao.github.io/images/algos/reduction1.png) 25 | 26 | • Induction (or, mathematical induction) is used to show that a statement is true for a large class of objects (often the natural numbers). We do this by first showing it to be true for a base case (such as the number 1) and then showing that it “carries over” from one object to the next (if it’s true for n –1, then it’s true for n). 27 | 28 | Induction(推导)是一个数学意义上的推导,类似数学归纳法,主要是用来证明某个命题是正确的。首先我们证明对于基础情况(例如在k=1时)是正确的,然后证明该命题递推下去都是正确的(一般假设当k=n-1时是正确的,然后证明当k=n时也是正确的即可) 29 | 30 | • Recursion is what happens when a function calls itself. Here we need to make sure the function works correctly for a (nonrecursive) base case and that it combines results from the recursive calls into a valid solution. 31 | 32 | Recursion(递归)经常发生于一个函数调用自身的情况。递归函数说起来简单,但是实现不太容易,我们要确保对于基础情况(不递归的情况)能够正常工作,此外,对于递归情况能够将递归调用的结果组合起来得到一个有效的结果。 33 | 34 | 以上三个核心有很多相似点,比如它们都专注于求出目标解的某一步,我们只需要仔细思考这一步,剩下的就能够自动完成了。如果我们更加仔细地去理解它们,我们会发现,**Induction(推导)和Recursion(递归)其实彼此相互对应,也就是说一个Induction能够写出一个相应的Recursion,而一个Recursion也正好对应着一个Induction式子,也可以换个方式理解,Induction是从n-1到n的推导,而Recursion是从n到n-1的递归(下面有附图可以帮助理解)。此外,Induction和Recursion其实都是某种Reduction,即Induction和Recursion的本质就是对问题进行规约!为了能够对问题使用Induction或者说Recursion,Reduction一般是将一个问题变成另一个只是规模减小了的相同问题。** 35 | 36 | 你也许会觉得奇怪,不对啊,刚才不是说Reduction是将一个问题规约成另一个问题吗?现在怎么又说成是将一个问题变成另一个只是规模减小了的相同问题了?其实,Reduction是有两种的,上面的两种都是Reduction!还记得前面介绍过的递归树吗?那其实就是将规模较大的问题转换成几个规模较小的问题,而且问题的形式并没有改变,这就是一种Reduction。你可以理解这种情况下Reduction是降维的含义,也就类似机器学习中的Dimension Reduction,对高维数据进行降维了,问题保持不变。 37 | 38 | These are two major variations of reductions: reducing to a different problem and reducing to a shrunken version of the same. 39 | 40 | 再看下下面这幅图理解Induction和Recursion之间的关系 41 | 42 | ![image](https://hujiaweibujidao.github.io/images/algos/Inductionrecursion.png) 43 | 44 | [关于它们三个的关系的原文阐述:Induction and recursion are, in a sense, mirror images of one another, and both can be seen as examples of reduction. To use induction (or recursion), the reduction must (generally) be between instances of the same problem of different sizes. ] 45 | 46 | [看了原书你会觉得,作者介绍算法的方式很特别,作者有提到他的灵感来自哪里:In fact, much of the material was inspired by Udi Manber’s wonderful paper `“Using induction to design algorithms”` from 1988 and his book from the following year, `Introduction to Algorithms: A Creative Approach`.] 47 | 48 | 也许你还感觉很晕,慢慢地看了后面的例子你就明白了。在介绍例子之前呢,先看下递归和迭代的异同,这个很重要,在后面介绍动态规划算法时我们还会反复提到它们的异同。 49 | 50 | [Induction is what you use to show that recursion is correct, and recursion is a very direct way of implementing most inductive algorithm ideas. However, rewriting the algorithm to be iterative can avoid the overhead and limitations of recursive functions in most (nonfunctional) programming languages. ] 51 | 52 | 有了Induction和Recursion,我们很容易就可以将一个inductive idea采用递归(recursion)的方式实现,根据我们的编程经验(事实也是如此),任何一个递归方式的实现都可以改成非递归方式(即迭代方式)实现(反之亦然),而且非递归方式要好些,为什么呢?因为非递归版本相对来讲运行速度更快,因为没有用栈去实现,也避免了栈溢出的情况,python中对栈深度是有限制的。 53 | 54 | 举个例子,下面是一段遍历序列的代码,如果大小设置为100没有问题,如果设置为1000就会报`RuntimeError`的错误,提示超出了最大的递归深度。[当然,大家都不会像下面那样写代码对吧,这只是一个例子] 55 | 56 | ```python 57 | def trav(seq, i=0): 58 | if i == len(seq): return 59 | #print seq[i] 60 | trav(seq, i + 1) 61 | 62 | trav(range(1000)) # RuntimeError: maximum recursion depth exceeded 63 | ``` 64 | 65 | 所以呢,很多时候虽然递归的思路更好想,代码也更好写,但是迭代的代码更加高效一些,在动态规划中还可以看到迭代版本还有其他的优点,当然,它还有些缺点,比如要考虑迭代的顺序,如果迫不及待想知道请移步阅读[Python算法设计篇之动态规划](https://hujiaweibujidao.github.io/blog/2014/07/01/python-algorithms-dynamic-programming/),不过还是建议且听我慢慢道来 66 | 67 | 下面我们通过排序来梳理下我们前面介绍的三个核心内容 68 | 69 | **我们如何对排序问题进行reduce呢?很显然,有很多种方式,假如我们将原问题reduce成两个规模为原来一半的子问题,我们就得到了合并排序(这个我们以后还会详细介绍);假如我们每次只是reduce一个元素,比如假设前n-1个元素都排好序了,那么我们只需要将第n个元素插入到前面的序列即可,这样我们就得到了插入排序;再比如,假设我们找到其中最大的元素然后将它让在位置n上,一直这么下去我们就得到了选择排序;继续思考下去,假设我们找到某个元素(比如第k大的元素),然后将它放在位置k上,一直这么下去我们就得到了快速排序(这个我们以后还会详细介绍)。怎么样?我们前面学过的排序经过这么一些reduce基本上都很清晰了对吧?** 70 | 71 | 下面通过代码来体会下插入排序和选择排序的两个不同版本 72 | 73 | 递归版本的插入排序 74 | 75 | ``` 76 | def ins_sort_rec(seq, i): 77 | if i == 0: return # Base case -- do nothing 78 | ins_sort_rec(seq, i - 1) # Sort 0..i-1 79 | j = i # Start "walking" down 80 | while j > 0 and seq[j - 1] > seq[j]: # Look for OK spot 81 | seq[j - 1], seq[j] = seq[j], seq[j - 1] # Keep moving seq[j] down 82 | j -= 1 # Decrement j 83 | 84 | from random import randrange 85 | seq = [randrange(1000) for i in range(100)] 86 | ins_sort_rec(seq, len(seq)-1) 87 | ``` 88 | 89 | 改成迭代版本的插入排序如下 90 | 91 | ``` 92 | def ins_sort(seq): 93 | for i in range(1, len(seq)): # 0..i-1 sorted so far 94 | j = i # Start "walking" down 95 | while j > 0 and seq[j - 1] > seq[j]: # Look for OK spot 96 | seq[j - 1], seq[j] = seq[j], seq[j - 1] # Keep moving seq[j] down 97 | j -= 1 # Decrement j 98 | 99 | seq2 = [randrange(1000) for i in range(100)] 100 | ins_sort(seq2) 101 | ``` 102 | 103 | 你会发现,两个版本差不多,但是递归版本中list的size不能太大,否则就会栈溢出,而迭代版本不会有问题,还有一个区别就是方法参数,一般来说递归版本的参数都会多些 104 | 105 | 递归版本和迭代版本的选择排序 106 | 107 | ``` 108 | def sel_sort_rec(seq, i): 109 | if i == 0: return # Base case -- do nothing 110 | max_j = i # Idx. of largest value so far 111 | for j in range(i): # Look for a larger value 112 | if seq[j] > seq[max_j]: max_j = j # Found one? Update max_j 113 | seq[i], seq[max_j] = seq[max_j], seq[i] # Switch largest into place 114 | sel_sort_rec(seq, i - 1) # Sort 0..i-1 115 | 116 | seq = [randrange(1000) for i in range(100)] 117 | sel_sort_rec(seq, len(seq)-1) 118 | 119 | def sel_sort(seq): 120 | for i in range(len(seq) - 1, 0, -1): # n..i+1 sorted so far 121 | max_j = i # Idx. of largest value so far 122 | for j in range(i): # Look for a larger value 123 | if seq[j] > seq[max_j]: max_j = j # Found one? Update max_j 124 | seq[i], seq[max_j] = seq[max_j], seq[i] # Switch largest into place 125 | 126 | seq2 = [randrange(1000) for i in range(100)] 127 | sel_sort(seq2) 128 | ``` 129 | 130 | 下面我们来看个例子,这是一个经典的“名人问题”,我们要从人群中找到那个名人,所有人都认识名人,而名人则任何人都不认识。 131 | 132 | [这个问题的一个变种就是从一系列有依赖关系的集合中找到那个依赖关系最开始的元素,比如多线程环境下的线程依赖问题,后面将要介绍的拓扑排序是解决这类问题更实际的解法。A more down-to-earth version of the same problem would be examining a set of dependencies and trying to find a place to start. For example, you might have threads in a multithreaded application waiting for each other, with even some cyclical dependencies (so-called deadlocks), and you’re looking for one thread that isn’t waiting for any of the others but that all of the others are dependent on. ] 133 | 134 | 在进一步分析之前我们可以发现,很显然,我们可以暴力求解下,G[u][v]为True表示 u 认识 v。 135 | 136 | ``` 137 | def naive_celeb(G): 138 | n = len(G) 139 | for u in range(n): # For every candidate... 140 | for v in range(n): # For everyone else... 141 | if u == v: continue # Same person? Skip. 142 | if G[u][v]: break # Candidate knows other 143 | if not G[v][u]: break # Other doesn't know candidate 144 | else: 145 | return u # No breaks? Celebrity! 146 | return None # Couldn't find anyone 147 | ``` 148 | 149 | 用下面代码进行测试,得到正确结果57 150 | 151 | ``` 152 | from random import * 153 | n = 100 154 | G = [[randrange(2) for i in range(n)] for i in range(n)] 155 | c = 57 # For testing 156 | for i in range(n): 157 | G[i][c] = True 158 | G[c][i] = False 159 | 160 | print naive_celeb(G) #57 161 | ``` 162 | 163 | 上面的暴力求解其实可以看做是一个reduce,每次reduce一个人,确定他是否是名人,显然这样做并不高效。那么,对于名人问题我们还可以怎么reduce呢?**假设我们还是将规模为n的问题reduce成规模为n-1的问题,那么我们要找到一个非名人(u),也就是找到一个人(u),他要么认识其他某个人(v),要么某个人(v)不认识他,也就是说,对于任何G[u][v],如果G[u][v]为True,那么消去u;如果G[u][v]为False,那么消去v,这样就可以明显加快查找的速度!** 164 | 165 | 基于上面的想法就有了下面的python实现,第二个for循环是用来验证我们得到的结果是否正确(因为如果我们保证有一个名人的话那么结果肯定正确,但是如果不能保证的话,那么结果就要进行验证) 166 | 167 | ``` 168 | def celeb(G): 169 | n = len(G) 170 | u, v = 0, 1 # The first two 171 | for c in range(2, n + 1): # Others to check 172 | if G[u][v]: 173 | u = c # u knows v? Replace u 174 | else: 175 | v = c # Otherwise, replace v 176 | if u == n: 177 | c = v # u was replaced last; use v 178 | else: 179 | c = u # Otherwise, u is a candidate 180 | for v in range(n): # For everyone else... 181 | if c == v: continue # Same person? Skip. 182 | if G[c][v]: break # Candidate knows other 183 | if not G[v][c]: break # Other doesn't know candidate 184 | else: 185 | return c # No breaks? Celebrity! 186 | return None # Couldn't find anyone 187 | ``` 188 | 189 | 看起来还不错吧,我们将一个$O(n^2)$的暴力解法变成了一个$O(n)$的快速解法。 190 | 191 | [看书看到这里时,我想起了另一个看起来很相似的问题,从n个元素中找出最大值和最小值。如果我们单独地来查找最大值和最小值,共需要(2n-2)次比较(也许你觉得还可以少几次,但都还是和2n差不多对吧),但是,如果我们成对来处理,首先比较第一个元素和第二个元素,较大的那个作为当前最大值,较小的那个作为当前最小值(如果n是奇数的话,为了方便可以直接令第一个元素既是最大值又是最小值),然后向后移动,每次取两个元素出来先比较,较小的那个去和当前最小值比较,较大的那个去和当前最大值比较,这样的策略至多需要 $3\lfloor \frac{n}{2} \rfloor$ 次比较。两个问题虽然完全没关系,但是解决方式总有那么点千丝万缕有木有?] 192 | 193 | 接下来我们看另一个更加重要的例子,拓扑排序,这是图中很重要的一个算法,在后面介绍到图算法的时候我们还会提到拓扑排序的另一个解法,它的应用范围也非常广,除了前面的依赖关系例子外,还有一个最突出的例子就是类Linux系统中软件的安装,每当我们在终端安装一个软件或者库时,它会自动检测它所依赖的那些部件(components)是否安装了,如果没有那么就先安装那些依赖项。此外,后面[介绍到动态规划时有一个单源最短路径问题]((https://hujiaweibujidao.github.io/blog/2014/07/01/python-algorithms-dynamic-programming/))就利用了拓扑排序。 194 | 195 | 下图是一个有向无环图(DAG)和它对应的拓扑排序结果 196 | 197 | ![image](https://hujiaweibujidao.github.io/images/algos/topsort.png) 198 | 199 | 拓扑排序这个问题怎么进行reduce呢?和前面一样,我们最直接的想法可能还是reduce one element,即去掉一个节点,先解决剩下的(n-1)个节点的拓扑排序问题,然后将这个去掉的节点插入到合适的位置,这个想法的实现非常类似前面的插入排序,插入的这个节点(也就是前面去掉的节点)的位置是在前面所有对它有依赖的节点之后。 200 | 201 | ``` 202 | def naive_topsort(G, S=None): 203 | if S is None: S = set(G) # Default: All nodes 204 | if len(S) == 1: return list(S) # Base case, single node 205 | v = S.pop() # Reduction: Remove a node 206 | seq = naive_topsort(G, S) # Recursion (assumption), n-1 207 | min_i = 0 208 | for i, u in enumerate(seq): 209 | if v in G[u]: min_i = i + 1 # After all dependencies 210 | seq.insert(min_i, v) 211 | return seq 212 | 213 | G = {'a': set('bf'), 'b': set('cdf'),'c': set('d'), 'd': set('ef'), 'e': set('f'), 'f': set()} 214 | print naive_topsort(G) # ['a', 'b', 'c', 'd', 'e', 'f'] 215 | ``` 216 | 217 | 上面这个算法是平方时间的,还有没有其他的reduction策略呢?前面的解法类似插入排序,既然又是reduce一个元素,很显然我们可以试试类似选择排序的策略,也就是说,我们找到一个节点,然后把它放在第一个位置上(后面有道练习题思考如果是放在最后一个位置上怎么办),假设我们直接就是将这个节点去掉会怎样呢?如果剩下的图还是一个DAG的话我们就将原来的问题规约成了一个相似但是规模更小的问题对不对?但是问题是我们选择哪个节点会使得剩下的图还是一个DAG呢?很显然,如果一个节点的入度为0,也就是说没有任何其他的节点依赖于它,那么它肯定可以直接安全地删除掉对不对?! 218 | 219 | 基于上面的思路就有了下面的解法,每次从图中删除一个入度为0的节点 220 | 221 | ``` 222 | def topsort(G): 223 | count = dict((u, 0) for u in G) # The in-degree for each node 224 | for u in G: 225 | for v in G[u]: 226 | count[v] += 1 # Count every in-edge 227 | Q = [u for u in G if count[u] == 0] # Valid initial nodes 228 | S = [] # The result 229 | while Q: # While we have start nodes... 230 | u = Q.pop() # Pick one 231 | S.append(u) # Use it as first of the rest 232 | for v in G[u]: 233 | count[v] -= 1 # "Uncount" its out-edges 234 | if count[v] == 0: # New valid start nodes? 235 | Q.append(v) # Deal with them next 236 | return S 237 | ``` 238 | 239 | [扩展知识:有意思的是,拓扑排序还和Python Method Resolution Order 有关,也就是用来确定某个方法是应该调用该实例的还是该实例的父类的还是继续往上调用祖先类的对应方法。对于单继承的语言这个很容易,顺着继承链一直往上找就行了,但是对于Python这类多重继承的语言则不简单,它需要更加复杂的策略,Python中使用了C3 Method Resolution Order,我不懂,[想要了解的可以查看 on python docs](https://www.python.org/download/releases/2.3/mro/)] 240 | 241 | 本章后面作者提到了一些其他的内容 242 | 243 | 1.Strong Assumptions 244 | 245 | 主要对于Induction,为了更加准确方便地从n-1递推到n,常常需要对问题做很强的假设。 246 | 247 | 2.Invariants and Correctness 248 | 249 | 循环不变式,这在算法导论上有详细介绍,循环不变式是用来证明某个算法是正确的一种方式,主要有下面三个步骤[这里和算法导论上介绍的不太一样,道理类似]: 250 | 251 | (1). Use induction to show that it is, in fact, true after each iteration. 252 | (2). Show that we’ll get the correct answer if the algorithm terminates. 253 | (3). Show that the algorithm terminates. 254 | 255 | 3.Relaxation and Gradual Improvement 256 | 257 | 松弛技术是指某个算法使得当前得到的解有进一步的提升,越来越接近最优解(准确解),这个技术非常实用,每次松弛可以看作是向最终解前进了“一步”,我们的目标自然是希望松弛的次数越少越好,关键就是要确定松弛的顺序(好的松弛顺序可以让我们直接朝着最优解前进,缩短算法运行时间),后面要介绍的[图中的Bellman-Ford算法、Dijkstra算法以及DAG图上的最短路径问题都是如此](https://hujiaweibujidao.github.io/blog/2014/07/01/python-algorithms-graphs/)。 258 | 259 | 4.Reduction + Contraposition = Hardness Proof 260 | 261 | 规约是用于证明一个问题是否是一个很难的问题的好方式,假设我们能够将问题A规约至问题B,如果问题B很简单,那么问题A肯定也很简单。逆反一下我们就得到,如果问题A很难,那么问题B就也很难。比如,我们知道了哈密顿回路问题是NP完全问题,要证明哈密顿路径问题也是NP完全问题,就可以将哈密顿回路问题规约为哈密顿路径问题。 262 | 263 | **[这里作者并没有过多的提到问题A规约至问题B的复杂度,算法导论中有提到,作者可能隐藏了规约的复杂度不大的含义,比如说多项式时间内能够完成,也就是下面的fast readuction]** 264 | 265 | “fast + fast = fast.” 的含义是:fast readuction + fast solution to B = fast solution to A 266 | 267 | 两条重要的规约经验: 268 | 269 | • If you can (easily) reduce A to B, then B is at least as hard as A. 270 | 271 | • If you want to show that X is hard and you know that Y is hard, reduce Y to X. 272 | 273 | 5.Problem Solving Advice 274 | 275 | 作者提供的解决一个问题的建议: 276 | 277 | (1)Make sure you really understand the problem. 278 | 279 | 搞明白你要解决的问题 280 | 281 | What is the input? The output? What’s the precise relationship between the two? Try to represent the problem instances as familiar structures, such as sequences or graphs. A direct, brute-force solution can sometimes help clarify exactly what the problem is. 282 | 283 | (2)Look for a reduction. 284 | 285 | 寻找一个规约方式 286 | 287 | Can you transform the input so it works as input for another problem that you can solve? Can you transform the resulting output so that you can use it? Can you reduce an instance if size n to an instance of size k < n and extend the recursive solution (inductive hypothesis) back to n? 288 | 289 | (3)Are there extra assumptions you can exploit? 290 | 291 | 还有其他的重要的假设条件吗,有时候我们如果只考虑该问题的特殊情况的话没准能够有所收获 292 | 293 | Integers in a fixed value range can be sorted more efficiently than arbitrary values. Finding the shortest path in a DAG is easier than in an arbitrary graph, and using only non-negative edge weights is often easier than arbitrary edge weights. 294 | 295 | 问题4-18. 随机生成DAG图 296 | 297 | Write a function for generating random DAGs. Write an automatic test that checks that topsort gives a valid orderings, using your DAG generator. 298 | 299 | You could generate DAGs by, for example, randomly ordering the nodes, and add a random number of forward-pointing edges to each of them. 300 | 301 | 问题4-19. 修改拓扑排序 302 | 303 | Redesign topsort so it selects the last node in each iteration, rather than the first. 304 | 305 | This is quite similar to the original. You now have to maintain the out-degrees of the remaining nodes, and insert each node before the ones you have already found. (Remember not to insert anything in the beginning of a list, though; rather, append, and then reverse it at the end, to avoid a quadratic running time.) 306 | 307 | [注意是使用`append`然后`reverse`,而不要使用`insert`] 308 | 309 | 返回[Python数据结构与算法设计篇目录](https://hujiaweibujidao.github.io/python/) 310 | 311 | 312 | -------------------------------------------------------------------------------- /ReadingNotes-PythonAlgorithms/2014-07-01-python-algorithms-introduction.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Python Algorithms - C1 Introduction" 4 | date: 2014-07-01 10:10 5 | categories: algorithm 6 | --- 7 | Python算法设计篇(1) Chapter 1: Introduction 8 | 9 | > 1. Write down the problem. 10 | 2. Think real hard. 11 | 3. Write down the solution. 12 | —— “The Feynman Algorithm” as described by Murray Gell-Mann 13 | 14 | 本节主要是对原书中的内容做些简单介绍,说明算法的重要性以及各章节的内容概要。 15 | 16 | #### 1.关于这本书的目的 17 | 18 | 算法导论是一本经典的大而全的算法书籍,而本书Python Algorithms不是来取代而是来补充算法导论的,因为算法导论提供的是简易的伪代码和详细的证明,而本书主要从作者的教学过程中从更高地层次来讲解算法,并使用Python代码来实现。 19 | 20 | **[实际阅读之后,我个人感觉这本书虽然貌似名声不大,但是绝对可以和算法导论平分秋色]** 21 | 22 | #### 2.这本书关于什么? 23 | 24 | 算法分析,算法设计的基本原则,如何使用Python实现基本的数据结构和算法 (以下内容不难理解就不做翻译了,原文原滋原味更加萌萌哒 ~\(^o^)/~) 25 | 26 | What the book is about: 27 | • Algorithm analysis, with a focus on asymptotic running time 28 | • Basic principles of algorithm design 29 | • How to represent well-known data structures in Python 30 | • How to implement well-known algorithms in Python 31 | 32 | What the book covers only briefly or partially: 33 | • Algorithms that are directly available in Python, either as part of the language or via the standard library 34 | • Thorough and deep formalism (although the book has its share of proofs and proof-like explanations) 35 | 36 | #### 3.为什么我们需要学习算法呢? 37 | 38 | 学习了算法之后可以帮助我们更加高效地解决问题! 39 | 40 | 下面是一个简单的线性时间和平方时间的对比例子,后者的运行速度远远慢于后者,为什么呢?这与Python中内置的list的实现机制有关,在前面的数据结构篇中介绍过了,list是类似数组一样的动态表,而不是标准的数组形式,所以对于append操作是常数时间,而对于insert操作是线性时间的!感兴趣的话移步阅读[Python数据结构篇3-数据结构](https://hujiaweibujidao.github.io/blog/2014/05/08/python-algorithms-datastructures/) 41 | 42 | ```python 43 | from time import * 44 | t0=time() 45 | count=10**5 46 | nums=[] 47 | for i in range(count): 48 | nums.append(i) 49 | 50 | nums.reverse() 51 | t1 = time() - t0 52 | print t1 #0.0240848064423 53 | t0=time() 54 | nums=[] 55 | for i in range(count): 56 | nums.insert(0, i) 57 | 58 | t2 = time() - t0 59 | print t2 #3.68582415581 60 | ``` 61 | 62 | #### 4.这本书完整的章节内容 63 | 64 | 除去平摊分析外,内容差不多和我本学期的算法课的内容一样 65 | 66 | Chapter 1: Introduction. You’ve already gotten through most of this. It gives an overview of the book. 67 | 68 | Chapter 2: The Basics. This covers the basic concepts and terminology, as well as some fundamental math. Among other things, you learn how to be sloppier with your formulas than ever before, and still get the right results, with asymptotic notation. 69 | 70 | Chapter 3: Counting 101. More math—but it’s really fun math, I promise! There’s some basic combinatorics for analyzing the running time of algorithms, as well as a gentle introduction to recursion and recurrence relations. 71 | 72 | Chapter 4: Induction and Recursion ... and Reduction. The three terms in the title are crucial, and they are closely related. Here we work with induction and recursion, which are virtually mirror images of each other, both for designing new algorithms and for proving correctness. We also have a somewhat briefer look at the idea of reduction, which runs as a common thread through almost all algorithmic work. 73 | 74 | Chapter 5: Traversal: A Skeleton Key to Algorithmics. Traversal can be understood using the ideas of induction and recursion, but it is in many ways a more concrete and specific technique. Several of the algorithms in this book are simply augmented traversals, so mastering traversal will give you a real jump start. 75 | 76 | Chapter 6: Divide, Combine, and Conquer. When problems can be decomposed into independent subproblems, you can recursively solve these subproblems and usually get efficient, correct algorithms as a result. This principle has several applications, not all of which are entirely obvious, and it is a mental tool well worth acquiring. 77 | 78 | Chapter 7: Greed is Good? Prove It! Greedy algorithms are usually easy to construct. One can even formulate a general scheme that most, if not all, greedy algorithms follow, yielding a plug-and-play solution. Not only are they easy to construct, but they are usually very efficient. The problem is, it can be hard to show that they are correct (and often they aren’t). This chapter deals with some well-known examples and some more general methods for constructing correctness proofs. 79 | 80 | Chapter 8: Tangled Dependencies and Memoization. This chapter is about the design method (or, historically, the problem) called, somewhat confusingly, dynamic programming. It is an advanced technique that can be hard to master but that also yields some of the most enduring insights and elegant solutions in the field. 81 | 82 | 83 | ---------- 84 | 85 | #### 问题1-2:(比较两个字符串是否满足回文构词法) 86 | 87 | Find a way of checking whether two strings are anagrams of each other (such as "debit card" and "bad credit"). How well do you think your solution scales? Can you think of a naïve solution that will scale very poorly? 88 | 89 | A simple and quite scalable solution would be to sort the characters in each string and compare the results. (In theory, counting the character frequencies, possibly using collections.Counter, would scale even better.) A really poor solution would be to compare all possible orderings of one string with the other. I can’t overstate how poor this solution is; in fact, algorithms don’t get much worse than this. Feel free to code it up, and see how large anagrams you can check. I bet you won’t get far. 90 | 91 | 返回[Python数据结构与算法设计篇目录](https://hujiaweibujidao.github.io/python/) 92 | 93 | 94 | -------------------------------------------------------------------------------- /ReadingNotes-PythonAlgorithms/2014-07-01-python-algorithms-the-basics.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Python Algorithms - C2 The basics" 4 | date: 2014-07-01 10:20 5 | categories: algorithm 6 | --- 7 | Python算法设计篇(2) Chapter 2: The basics 8 | 9 | > Tracey: I didn’t know you were out there. 10 | Zoe: Sort of the point. Stealth—you may have heard of it. 11 | Tracey: I don’t think they covered that in basic. 12 | —— From “The Message,” episode 14 of Firefly 13 | 14 | 本节主要介绍了三个内容:算法渐近运行时间的表示方法、六条算法性能评估的经验以及Python中树和图的实现方式。 15 | 16 | #### 1.计算模型 17 | 18 | 图灵机模型(Turing machine): **A Turing machine is a simple (abstract) device that can read from, write to, and move along an infinitely long strip of paper.** The actual behavior of the machines varies. Each is a so-called finite state machine: it has a finite set of states (some of which indicate that it has finished), and every symbol it reads potentially triggers reading and/or writing and switching to a different state. You can think of this machinery as a set of rules. (“If I am in state 4 and see an X, I move one step to the left, write a Y, and switch to state 9.”) 19 | 20 | RAM模型(random-access machine):标准的单核计算机,它大致有下面三个性质 21 | 22 | • We don’t have access to any form of concurrent execution; the machine simply executes one instruction after the other. 23 | 24 | 计算机不能并发执行而只是按照指令顺序依次执行指令。 25 | 26 | • Standard, basic operations (such as arithmetic, comparisons, and memory access) all take constant (although possibly different) amounts of time. There are no more complicated basic operations (such as sorting). 27 | 28 | 基本的操作都是常数时间完成的,没有其他的复杂操作。 29 | 30 | • One computer word (the size of a value that we can work with in constant time) is not unlimited but is big enough to address all the memory locations used to represent our problem, plus an extra percentage for our variables. 31 | 32 | 计算机的字长足够大以使得它能够访问所有的内存地址。 33 | 34 | 算法的本质: **An algorithm is a procedure, consisting of a finite set of steps (possibly including loops and conditionals) that solves a given problem in finite time.** 35 | 36 | the notion of running time complexity (as described in the next section) is based on knowing how big a problem instance is, and that size is simply the amount of memory needed to encode it. 37 | 38 | [算法的运行时间是基于问题的大小,这个大小是指问题的输入占用的内存空间大小] 39 | 40 | #### 2.算法渐近运行时间 41 | 42 | 主要介绍了大O符号、大$\Omega$符号以及大$\Theta$符号,这部分内容网上很多资料,大家也都知道了,此处略过,可以参考[wikipedia_大O符号](http://en.wikipedia.org/wiki/Big_O_notation) 43 | 44 | 算法导论介绍到,对于三个符号可以做如下理解:$O$ = $\le$,$\Omega$ = $\ge$, $\Theta$ = $=$ 45 | 46 | 运行时间的三种特殊的情况:最优情况,最差情况,平均情况 47 | 48 | 几种常见的运行时间以及算法实例 [点击这里可以参考下wiki中的时间复杂度](http://zh.wikipedia.org/zh-cn/时间复杂度) 49 | 50 | ![image](https://hujiaweibujidao.github.io/images/algos/complexity.png) 51 | 52 | #### 3.算法性能评估的经验 53 | 54 | (1)Tip 1: If possible, don’t worry about it. 55 | 56 | 如果暴力求解也还行就算了吧,别去担心了 57 | 58 | (2)Tip 2: For timing things, use timeit. 59 | 60 | 使用`timeit`模块对运行时间进行分析,在前面的[数据结构篇中第三部分数据结构](https://hujiaweibujidao.github.io/blog/2014/05/08/python-algorithms-datastructures/)的list中已经介绍过了timeit模块,在使用的时候需要注意前面的运行不会影响后面的重复的运行(例如,分析排序算法运行时间,如果将前面已经排好序的序列传递给后面的重复运行是不行的) 61 | 62 | ```python 63 | #timeit模块简单使用实例 64 | timeit.timeit("x = sum(range(10))") 65 | ``` 66 | 67 | (3)Tip 3: To find bottlenecks, use a profiler. 68 | 69 | 使用`cProfile`模块来获取更多的关于运行情况的内容,从而可以发现问题的瓶颈,如果系统没有`cProfile`模块,可以使用`profile`模块代替,关于这两者的更多内容可以查看[Python standard library-Python Profilers](https://docs.python.org/2/library/profile.html) 70 | 71 | ```python 72 | #cProfile模块简单使用实例 73 | import cProfile 74 | import re 75 | cProfile.run('re.compile("foo|bar")') 76 | 77 | #运行结果: 78 | 79 | 194 function calls (189 primitive calls) in 0.000 seconds 80 | 81 | Ordered by: standard name 82 | 83 | ncalls tottime percall cumtime percall filename:lineno(function) 84 | 1 0.000 0.000 0.000 0.000 :1() 85 | 1 0.000 0.000 0.000 0.000 re.py:188(compile) 86 | 1 0.000 0.000 0.000 0.000 re.py:226(_compile) 87 | 1 0.000 0.000 0.000 0.000 sre_compile.py:178(_compile_charset) 88 | 1 0.000 0.000 0.000 0.000 sre_compile.py:207(_optimize_charset) 89 | ... 90 | ``` 91 | 92 | (4)Tip 4: Plot your results. 93 | 94 | 画出算法性能结果图,如下图所示,可以使用的模块有`matplotlib` 95 | 96 | ![image](https://hujiaweibujidao.github.io/images/algos/plotresult.png) 97 | 98 | (5)Tip 5: Be careful when drawing conclusions based on timing comparisons. 99 | 100 | 在对基于运行时间的比较而要下结论时需要小心 101 | 102 | First, any differences you observe may be because of random variations. 103 | 104 | 首先,你观察到的差异可能是由于输入中的随机变化而引起的 105 | 106 | Second, there are issues when comparing averages. 107 | 108 | 其次,比较算法的平均情况下的运行时间是存在问题的[这个我未理解,以下是作者的解释] 109 | 110 | At the very least, you should stick to comparing averages of actual timings. A common practice to get more meaningful numbers when performing timing experiments is to normalize the running time of each program, dividing it by the running time of some standard, simple algorithm. This can indeed be useful but can in some cases make your results less than meaningful. See the paper “How not to lie with statistics: The correct way to summarize benchmark results” by Fleming and Wallace for a few pointers. For some other perspectives, you could read Bast and Weber’s “Don’t compare averages,” or the more recent paper by Citron et al., “The harmonic or geometric mean: does it really matter?” 111 | 112 | Third, your conclusions may not generalize. 113 | 114 | 最后,你的结论未必适用于一般情况 (感谢评论者@梁植华的翻译建议) 115 | 116 | (6)Tip 6: Be careful when drawing conclusions about asymptotics from experiments. 117 | 118 | 在对从实验中得到关于渐近时间的信息下结论时需要小心,实验只是对于理论的一个支撑,可以通过实验来推翻一个渐近时间结果的假设,但是反过来一般不行 [以下是作者的解释] 119 | 120 | If you want to say something conclusively about the asymptotic behavior of an algorithm, you need to analyze it, as described earlier in this chapter. Experiments can give you hints, but they are by their nature finite, and asymptotics deal with what happens for arbitrarily large data sizes. On the other hand, unless you’re working in theoretical computer science, the purpose of asymptotic analysis is to say something about the behavior of the algorithm when implemented and run on actual problem instances, meaning that experiments should be relevant. 121 | 122 | 4.在Python中实现树和图 123 | 124 | **[Python中的dict和set]** 125 | Python中很多地方都使用了hash策略,在前面的[Python数据结构篇中的搜索部分](https://hujiaweibujidao.github.io/blog/2014/05/07/python-algorithms-search/)已经介绍了hash的内容。Python提供了`hash`函数,例如`hash("Hello, world!")`得到`-943387004357456228` (结果不一定相同)。Python中的dict和set都使用了hash机制,所以平均情况下它们获取元素都是常数时间的。 126 | 127 | (1)图的表示:最常用的两种表示方式是邻接表和邻接矩阵 [假设要表示的图如下] 128 | 129 | ![image](https://hujiaweibujidao.github.io/images/algos/graphrep.png) 130 | 131 | 邻接表 Adjacency Lists:因为历史原因,邻接表往往都是指链表list,但实际上也可以是其他的,例如在python中也可以是set或者dict,不同的表示方式有各自的优缺点,它们判断节点的连接关系和节点的度的方式甚至两个操作的性能都不太一样。 132 | 133 | ① adjacency lists 表示形式 134 | 135 | ```python 136 | # A Straightforward Adjacency List Representation 137 | a, b, c, d, e, f, g, h = range(8) 138 | N = [ 139 | [b, c, d, e, f], # a 140 | [c, e], # b 141 | [d], # c 142 | [e], # d 143 | [f], # e 144 | [c, g, h], # f 145 | [f, h], # g 146 | [f, g] # h 147 | ] 148 | 149 | b in N[a] # Neighborhood membership -> True 150 | len(N[f]) # Degree -> 3 151 | ``` 152 | 153 | ② adjacency sets 表示形式 154 | 155 | ```python 156 | # A Straightforward Adjacency Set Representation 157 | a, b, c, d, e, f, g, h = range(8) 158 | N = [ 159 | {b, c, d, e, f}, # a 160 | {c, e}, # b 161 | {d}, # c 162 | {e}, # d 163 | {f}, # e 164 | {c, g, h}, # f 165 | {f, h}, # g 166 | {f, g} # h 167 | ] 168 | 169 | b in N[a] # Neighborhood membership -> True 170 | len(N[f]) # Degree -> 3 171 | ``` 172 | 173 | 基本上和adjacency lists表示形式一样对吧?但是,对于list,判断一个元素是否存在是线性时间$O(N(v))$,而在set中是常数时间$O(1)$,所以对于稠密图使用adjacency sets要更加高效。 174 | 175 | ③ adjacency dicts 表示形式 176 | 177 | ```python 178 | # A Straightforward Adjacency Dict Representation 179 | a, b, c, d, e, f, g, h = range(8) 180 | N = [ 181 | {b:2, c:1, d:3, e:9, f:4}, # a 182 | {c:4, e:3}, # b 183 | {d:8}, # c 184 | {e:7}, # d 185 | {f:5}, # e 186 | {c:2, g:2, h:2}, # f 187 | {f:1, h:6}, # g 188 | {f:9, g:8} # h 189 | ] 190 | 191 | b in N[a] # Neighborhood membership -> True 192 | len(N[f]) # Degree -> 3 193 | N[a][b] # Edge weight for (a, b) -> 2 194 | ``` 195 | 196 | 这种情况下如果边是带权值的都没有问题! 197 | 198 | 除了上面三种方式外,还可以改变外层数据结构,上面三个都是list,其实也可以使用dict,例如下面的代码,此时节点是用字母表示的。在实际应用中,要根据问题选择最合适的表示形式。 199 | 200 | ``` 201 | N = { 202 | 'a': set('bcdef'), 203 | 'b': set('ce'), 204 | 'c': set('d'), 205 | 'd': set('e'), 206 | 'e': set('f'), 207 | 'f': set('cgh'), 208 | 'g': set('fh'), 209 | 'h': set('fg') 210 | } 211 | ``` 212 | 213 | 邻接矩阵 Adjacency Matrix 214 | 215 | 使用嵌套的list,用1和0表示点和点之间的连接关系,此时对于它们的连接性判断时间是常数,但是对于度的计算时间是线性的 216 | 217 | ``` 218 | # An Adjacency Matrix, Implemented with Nested Lists 219 | a, b, c, d, e, f, g, h = range(8) 220 | N = [[0,1,1,1,1,1,0,0], # a 221 | [0,0,1,0,1,0,0,0], # b 222 | [0,0,0,1,0,0,0,0], # c 223 | [0,0,0,0,1,0,0,0], # d 224 | [0,0,0,0,0,1,0,0], # e 225 | [0,0,1,0,0,0,1,1], # f 226 | [0,0,0,0,0,1,0,1], # g 227 | [0,0,0,0,0,1,1,0]] # h 228 | 229 | N[a][b] # Neighborhood membership -> 1 230 | sum(N[f]) # Degree -> 3 231 | ``` 232 | 233 | 如果边带有权值,也可以使用权值代替1,用inf代替0 234 | 235 | ``` 236 | a, b, c, d, e, f, g, h = range(8) 237 | _ = float('inf') 238 | 239 | W = [[0,2,1,3,9,4,_,_], # a 240 | [_,0,4,_,3,_,_,_], # b 241 | [_,_,0,8,_,_,_,_], # c 242 | [_,_,_,0,7,_,_,_], # d 243 | [_,_,_,_,0,5,_,_], # e 244 | [_,_,2,_,_,0,2,2], # f 245 | [_,_,_,_,_,1,0,6], # g 246 | [_,_,_,_,_,9,8,0]] # h 247 | 248 | W[a][b] < inf # Neighborhood membership 249 | sum(1 for w in W[a] if w < inf) - 1 # Degree 250 | ``` 251 | 252 | **NumPy**:这里作者提到了一个最常用的数值计算模块NumPy,它包含了很多与多维数组计算有关的函数。我可能会在以后的机器学习中详细学习它的使用,到时候可能会写篇文章介绍它的使用 253 | 254 | (2)树的表示 [假设要表示下面的树] 255 | 256 | ![image](https://hujiaweibujidao.github.io/images/algos/treerep.png) 257 | 258 | 树是一种特殊的图,所以可以使用图的表示方法,但是因为树的特殊性,其实有其他更好的表示方法,最简单的就是直接用一个list即可,缺点也很明显,可读性太差了,相当不直观 259 | 260 | ``` 261 | T = [["a", "b"], ["c"], ["d", ["e", "f"]]] 262 | T[2][1][0] # 'e' 263 | ``` 264 | 265 | 很多时候我们都能够肯定树中节点的孩子节点个数最多有多少个(比如二叉树,三叉树等等),所以比较方便的实现方式就是使用类class 266 | 267 | ``` 268 | # A Binary Tree Class 二叉树实例 269 | class Tree: 270 | def __init__(self, left, right): 271 | self.left = left 272 | self.right = right 273 | 274 | t = Tree(Tree("a", "b"), Tree("c", "d")) 275 | t.right.left # 'c' 276 | ``` 277 | 278 | 上面的实现方式的子节点都是孩子节点,但是还有一种很常用的树的表示方式,那就是“左孩子,右兄弟”表示形式,它就适用于孩子节点数目不确定的情况 279 | 280 | ``` 281 | # 左孩子,右兄弟 表示方式 282 | class Tree: 283 | def __init__(self, kids, next=None): 284 | self.kids = self.val = kids 285 | self.next = next 286 | return Tree 287 | 288 | t = Tree(Tree("a", Tree("b", Tree("c", Tree("d"))))) 289 | t.kids.next.next.val # 'c' 290 | ``` 291 | 292 | **[Bunch Pattern]**:有意思的是,上面的实现方式使用了Python中一种常用的设计模式,叫做Bunch Pattern,貌似来自经典书籍Python Cookbook,原书介绍如下: 293 | 294 | [因为这个不太好理解和翻译,还是原文比较有味,后期等我深刻理解了我可能会详细介绍它] 295 | 296 | When prototyping (or even finalizing) data structures such as trees, it can be useful to have a flexible class that will allow you to specify arbitrary attributes in the constructor. In these cases, the “Bunch” pattern (named by Alex Martelli in the Python Cookbook) can come in handy. There are many ways of implementing it, but the gist of it is the following: 297 | 298 | ``` 299 | class Bunch(dict): 300 | def __init__(self, *args, **kwds): 301 | super(Bunch, self).__init__(*args, **kwds) 302 | self.__dict__ = self 303 | return Bunch 304 | ``` 305 | 306 | There are several useful aspects to this pattern. First, it lets you create and set arbitrary attributes by supplying them as command-line arguments: 307 | 308 | ``` 309 | >>> x = Bunch(name="Jayne Cobb", position="Public Relations") 310 | >>> x.name 311 | 'Jayne Cobb' 312 | ``` 313 | 314 | Second, by subclassing dict, you get lots of functionality for free, such as iterating over the keys/attributes or easily checking whether an attribute is present. Here’s an example: 315 | 316 | ``` 317 | >>> T = Bunch 318 | >>> t = T(left=T(left="a", right="b"), right=T(left="c")) 319 | >>> t.left 320 | {'right': 'b', 'left': 'a'} 321 | >>> t.left.right 322 | 'b' 323 | >>> t['left']['right'] 324 | 'b' 325 | >>> "left" in t.right 326 | True 327 | >>> "right" in t.right 328 | False 329 | ``` 330 | 331 | This pattern isn’t useful only when building trees, of course. You could use it for any situation where you’d want a flexible object whose attributes you could set in the constructor. 332 | 333 | **[与图有关的python模块]**: 334 | 335 | • NetworkX: 336 | • python-graph: 337 | • Graphine: 338 | • Pygr: a graph database 339 | • Gato: a graph animation toolbox 340 | • PADS: a collection of graph algorithms 341 | 342 | 5.Python编程中的一些细节 343 | 344 | In general, the more important your program, the more you should mistrust such black boxes and seek to find out what’s going on under the cover. 345 | 346 | 作者在这里提到,如果你的程序越是重要的话,你就越是需要明白你所使用的数据结构的内部实现,甚至有些时候你要自己重新实现它。 347 | 348 | (1)Hidden Squares 隐藏的平方运行时间 349 | 350 | 有些情况下我们可能没有注意到我们的操作是非常不高效的,例如下面的代码,如果是判断某个元素是否在list中运行时间是线性的,如果是使用set,判断某个元素是否存在只需要常数时间,所以如果我们需要判断很多元素是否存在的话,使用set的性能会更加高效。 351 | 352 | ``` 353 | from random import randrange 354 | L = [randrange(10000) for i in range(1000)] 355 | 42 in L # False 356 | S = set(L) 357 | 42 in S #False 358 | ``` 359 | 360 | (2)The Trouble with Floats 精度带来的烦恼 361 | 362 | 现有的计算机系统都是不能精确表达小数的![该部分内容可以阅读与计算机组成原理相关的书籍了解计算机的浮点数系统]在python中,浮点数可能带来很多的烦恼,例如,运行下面的实例,本应该是相等,但是却返回False。 363 | 364 | ``` 365 | sum(0.1 for i in range(10)) == 1.0 # False 366 | ``` 367 | 368 | **永远不要使用小数比较结果来作为两者相等的判断依据!**你最多只能判断两个浮点数在有限位数上是相等的,也就是近似相等了。 369 | 370 | ``` 371 | def almost_equal(x, y, places=7): 372 | return round(abs(x-y), places) == 0 373 | 374 | almost_equal(sum(0.1 for i in range(10)), 1.0) # True 375 | ``` 376 | 377 | 除此之外,可以使用一些有用的第三方模块,例如`decimal`,在需要处理金融数据的时候很有帮助 378 | 379 | ``` 380 | from decimal import * 381 | sum(Decimal("0.1") for i in range(10)) == Decimal("1.0") # Ture 382 | ``` 383 | 384 | 还有一个有用的`Sage`模块,如下所示,它可以进行数学的符号运算得到准确值,如果需要也可以得到近似的浮点数解。[Sage的官方网址](http://sagemath.org) 385 | 386 | ``` 387 | sage: 3/5 * 11/7 + sqrt(5239) 388 | 13*sqrt(31) + 33/35 389 | ``` 390 | 391 | 更多和Python中的浮点数有关的内容可以查看[Floating Point Arithmetic: Issues and Limitations](https://docs.python.org/2/tutorial/floatingpoint.html) 392 | 393 | 问题2-12. (图的表示) 394 | 395 | Consider the following graph representation: you use a dictionary and let each key be a pair (tuple) of two nodes, with the corresponding value set to the edge weight. For example W[u, v] = 42. What would be the advantages and disadvantages of this representation? Could you supplement it to mitigate the downsides? 396 | 397 | The advantages and disadvantages depend on what you’re using it for. It works well for looking up edge weights efficiently but less well for iterating over the graph’s nodes or a node’s neighbors, for example. You could improve that part by using some extra structures (for example, a global list of nodes, if that’s what you need or a simple adjacency list structure, if that’s required). 398 | 399 | 返回[Python数据结构与算法设计篇目录](https://hujiaweibujidao.github.io/python/) 400 | 401 | 402 | -------------------------------------------------------------------------------- /images/contentprovider_ashmem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/AndroidInterviews/78e981a9233a87a91a69a467af0886ca0e5fbebb/images/contentprovider_ashmem.png -------------------------------------------------------------------------------- /images/http_cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/AndroidInterviews/78e981a9233a87a91a69a467af0886ca0e5fbebb/images/http_cache.png -------------------------------------------------------------------------------- /images/https_connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/AndroidInterviews/78e981a9233a87a91a69a467af0886ca0e5fbebb/images/https_connection.png -------------------------------------------------------------------------------- /images/resource_path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/AndroidInterviews/78e981a9233a87a91a69a467af0886ca0e5fbebb/images/resource_path.png -------------------------------------------------------------------------------- /images/service_lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/AndroidInterviews/78e981a9233a87a91a69a467af0886ca0e5fbebb/images/service_lifecycle.png -------------------------------------------------------------------------------- /images/tcp_connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/AndroidInterviews/78e981a9233a87a91a69a467af0886ca0e5fbebb/images/tcp_connection.png -------------------------------------------------------------------------------- /images/tcp_open_close.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/AndroidInterviews/78e981a9233a87a91a69a467af0886ca0e5fbebb/images/tcp_open_close.jpg -------------------------------------------------------------------------------- /pdfs/1-计算机基础相关知识.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/AndroidInterviews/78e981a9233a87a91a69a467af0886ca0e5fbebb/pdfs/1-计算机基础相关知识.pdf -------------------------------------------------------------------------------- /pdfs/2-ANDROID JAVA知识.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/AndroidInterviews/78e981a9233a87a91a69a467af0886ca0e5fbebb/pdfs/2-ANDROID JAVA知识.pdf -------------------------------------------------------------------------------- /pdfs/3-ANDROID 四大组件.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/AndroidInterviews/78e981a9233a87a91a69a467af0886ca0e5fbebb/pdfs/3-ANDROID 四大组件.pdf -------------------------------------------------------------------------------- /pdfs/4-ANDROID 其他知识.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/AndroidInterviews/78e981a9233a87a91a69a467af0886ca0e5fbebb/pdfs/4-ANDROID 其他知识.pdf -------------------------------------------------------------------------------- /pdfs/leetcode-cpp.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/AndroidInterviews/78e981a9233a87a91a69a467af0886ca0e5fbebb/pdfs/leetcode-cpp.pdf -------------------------------------------------------------------------------- /pdfs/leetcode-java.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/AndroidInterviews/78e981a9233a87a91a69a467af0886ca0e5fbebb/pdfs/leetcode-java.pdf --------------------------------------------------------------------------------