首页 文章 精选 留言 我的

精选列表

搜索[官方],共10000篇文章
优秀的个人博客,低调大师

【JAVA集合框架一 】java集合框架官方介绍 Collections Framework Overview 集合框架总览 翻译 jav...

原文链接: https://docs.oracle.com/javase/8/docs/technotes/guides/collections/overview.html 原文内容也一并附加在本文最后. 简介: Java平台包含一个集合框架。 集合是表示一组对象的对象(如经典的Vector类)。 集合框架是用于表示和操作集合的统一体系结构,使集合可以独立于实现细节而被操纵。 集合框架的主要有点在于: 通过提供数据结构和算法减少编程工作量,因此您不必亲自编写它们。 通过提供数据结构和算法的高性能实现来提高性能。由于每个接口的各种实现都是可以互换的,因此可以通过切换实现来调整程序。 通过建立通用语言来回传递集合,提供不相关的API之间的互操作性。--可以部分理解为向上转型 多态 通过学习多个特定的集合API来减少学习API所需的工作量。 --通过自顶而下的API设计风格 不要求您实现特定的集合API来减少设计和实现API所需的工作量。--通过提供抽象类提供了很多默认实现 通过为集合和算法提供标准接口来操纵它们,从而促进软件重用。 集合框架包括: 集合接口。表示不同类型的集合,例如Set,List和Map。这些接口构成了框架的基础。 通用实现。集合接口的主要实现。 遗留实现。早期版本Vector和Hashtable中的集合类进行了改进,以实现集合接口。 特殊用途的实现。设计用于特殊情况的实现。这些实现展示非标准的性能特征,使用限制或行为。 并发实现。为高并发使用而设计的实现。 包装实现。将功能(如同步)添加到其他实现。 更简化便利的实现. 集合接口的高性能低复杂实现. 抽象实现。集合接口的部分实现,以促进自定义实现。 算法。collections中非常有用的静态方法,比如例如排序列表。 基础架构。为集合即可欧提供重要支持的接口。--就是一些其他辅助的,比如Iterable 数组工具类。基本类型和引用对象数组的实用函数。严格来说,并不是集合框架的一部分,这个特性与集合框架同时添加到Java平台,并依赖于相同的基础结构。 集合接口: 集合接口分为两大阵营,最基础的接口java.util.Collection,有下面这些后代: java.util.Setjava.util.SortedSetjava.util.NavigableSetjava.util.Queuejava.util.concurrent.BlockingQueuejava.util.concurrent.TransferQueuejava.util.Dequejava.util.concurrent.BlockingDeque 另外的一些集合接口派生于 java.util.Map 不过他们并是真正的集合。 但是,这些接口包含集合视角的操作,这些操作可以将它们作为集合进行操作。Map有以下后代: java.util.SortedMapjava.util.NavigableMapjava.util.concurrent.ConcurrentMapjava.util.concurrent.ConcurrentNavigableMap 集合中的许多修改方法是可选的. 也就是允许实现类并不必须要实现一个或者多个这种操作. 如果你尝试使用,将会抛出运行时异常UnsupportedOperationException 每个实现的文档必须指定支持哪些可选操作。 引入了几个术语来帮助本规范: 不支持修改操作的集合(如添加,删除和清除)称为不可修改,非不可修改的集合是可修改的。; 如果集合额外保证集合对象中没有任何可见的修改那么称之为为不可变,非不可变的就是可变的; 保证集合大小不变的的集合,即使元素可以变化更改,称之为固定大小,非固定大小的列表称之为可变大小; 支持快速(通常是固定时间)索引元素访问的List是随机访问(RandomAccess) List,比如arrayList,不支持快速索引访问的称之为顺序访问 (sequential access)List ,RandomAccess标记接口指明一个list具有支持随机访问的特性,这一点保证了你可以编写通用的算法代码,通过选择不同的随机访问还是顺序访问List,以达到更好的性能. 一些实现可以做到限制哪些元素(或者在Map场景下的键和值)可以被存储。可能的限制包括要求元素: 特定类型 不能为null 遵从一些自定的断言判断式(Obey some arbitrary predicate) 如果尝试往有限制的接口实现中添加不符合的元素,会出现运行时异常,比如 ClassCastException, IllegalArgumentException, or a NullPointerException. 如果尝试在有限制的实现中移除一个不符合条件的元素或者测试是否存在,会导致异常 不过一些受限制的集合支持这种用法. 集合实现: 集合的实现类一般遵从这样子的形式 < 实现特性>加上<接口名称>的形式 下表中列出了通用实现 通用实现类支持集合框架中所有的可选操作,并且对于元素没有任何的限制 他们都是非同步的 不过Collections类中包含了很多静态的工厂方法-synchronization wrappers同步包装器 可以在很多非同的集合中提供同步的功能. 所有新的实现都具有快速失败迭代器,它可以检测到非法的并发修改,并且快速而干净地(而不是报错异常)失败。 ps: fail-fast java集合框架中的一种机制,检测并发修改 AbstractCollection, AbstractSet, AbstractList, AbstractSequentialList 和 AbstractMap 为了减少实现接口所需代价,提供了核心集合框架的基本实现。 这些类的API文档准确描述了每种方法的实现方式, 因此实现者知道哪些方法必须被重写,考虑到具体实现类的基本操作的性能。 并发集合: 应用程序中使用多个线程的集合的必须仔细编程,这个一般叫做并发编程 Java平台包含对并发编程的广泛支持。有关详细信息,请参阅Java Concurrency Utilities。 由于集合经常被使用,各种支持并发编程的接口和集合的实现都被包含在这些API中 这些类型,超出了前面提到过的同步包装,提供了并发编程中经常需要用到的特性 并发接口 BlockingQueueTransferQueueBlockingDequeConcurrentMapConcurrentNavigableMap 并发实现类 LinkedBlockingQueueArrayBlockingQueuePriorityBlockingQueueDelayQueueSynchronousQueueLinkedBlockingDequeLinkedTransferQueueCopyOnWriteArrayListCopyOnWriteArraySetConcurrentSkipListSetConcurrentHashMapConcurrentSkipListMap 设计目标: 主要的设计目标是编写一个体积小概念强大的API 新功能与当前程序差别不大,这一点非常重要.而且,需要在现有的基础上增加 而不是替代他们。 而且,新的API必须足够强大,才能够提供前面所述的所有优势。 为了使核心接口的数量保持较小, 接口不会尝试捕捉诸如易变性,可修改性和可调节性之类的细微区别 相反,在核心接口中一些调用是可选的 使得实现类可以抛出一个UnsupportedOperationException异常指示出他们不支持指定的可选操作 集合实现者必须清楚地在文档中表明实现支持哪些可选操作。 为了使每个核心接口中的方法数量保持较小, 只有在以下情况下,接口才包含方法: 这是一项真正的基本操作:就其他方面可以被合理定义而言, 它是一个基本的操作, 有一个不可抗拒的性能因素导致重要的实现类将会想要去重写它 所有关于集合的表现形式的互操作性至关重要。 这包括数组,在不改变语言的情况下,不能直接实现Collection接口。 因此, 该框架包含的方法可以将集合移入数组,将数组视为集合,将Map视为集合。 原文内容: Skip to Content Collections Framework Overview Introduction The Java platform includes acollections framework. Acollectionis an object that represents a group of objects (such as the classicVectorclass). A collections framework is a unified architecture for representing and manipulating collections, enabling collections to be manipulated independently of implementation details. The primary advantages of a collections framework are that it: Reduces programming effortby providing data structures and algorithms so you don't have to write them yourself. Increases performanceby providing high-performance implementations of data structures and algorithms. Because the various implementations of each interface are interchangeable, programs can be tuned by switching implementations. Provides interoperability between unrelated APIsby establishing a common language to pass collections back and forth. Reduces the effort required to learn APIsby requiring you to learn multiple ad hoc collection APIs. Reduces the effort required to design and implement APIsby not requiring you to produce ad hoc collections APIs. Fosters software reuseby providing a standard interface for collections and algorithms with which to manipulate them. The collections framework consists of: Collection interfaces. Represent different types of collections, such as sets, lists, and maps. These interfaces form the basis of the framework. General-purpose implementations. Primary implementations of the collection interfaces. Legacy implementations. The collection classes from earlier releases,VectorandHashtable, were retrofitted to implement the collection interfaces. Special-purpose implementations. Implementations designed for use in special situations. These implementations display nonstandard performance characteristics, usage restrictions, or behavior. Concurrent implementations. Implementations designed for highly concurrent use. Wrapper implementations. Add functionality, such as synchronization, to other implementations. Convenience implementations. High-performance "mini-implementations" of the collection interfaces. Abstract implementations. Partial implementations of the collection interfaces to facilitate custom implementations. Algorithms. Static methods that perform useful functions on collections, such as sorting a list. Infrastructure. Interfaces that provide essential support for the collection interfaces. Array Utilities. Utility functions for arrays of primitive types and reference objects. Not, strictly speaking, a part of the collections framework, this feature was added to the Java platform at the same time as the collections framework and relies on some of the same infrastructure. Collection Interfaces Thecollection interfacesare divided into two groups. The most basic interface,java.util.Collection, has the following descendants: java.util.Set java.util.SortedSet java.util.NavigableSet java.util.Queue java.util.concurrent.BlockingQueue java.util.concurrent.TransferQueue java.util.Deque java.util.concurrent.BlockingDeque The other collection interfaces are based onjava.util.Mapand are not true collections. However, these interfaces containcollection-viewoperations, which enable them to be manipulated as collections.Maphas the following offspring: java.util.SortedMap java.util.NavigableMap java.util.concurrent.ConcurrentMap java.util.concurrent.ConcurrentNavigableMap Many of the modification methods in the collection interfaces are labeledoptional. Implementations are permitted to not perform one or more of these operations, throwing a runtime exception (UnsupportedOperationException) if they are attempted. The documentation for each implementation must specify which optional operations are supported. Several terms are introduced to aid in this specification: Collections that do not support modification operations (such asadd,removeandclear) are referred to asunmodifiable. Collections that are not unmodifiable aremodifiable. Collections that additionally guarantee that no change in theCollectionobject will be visible are referred to asimmutable. Collections that are not immutable aremutable. Lists that guarantee that their size remains constant even though the elements can change are referred to asfixed-size. Lists that are not fixed-size are referred to asvariable-size. Lists that support fast (generally constant time) indexed element access are known asrandom accesslists. Lists that do not support fast indexed element access are known assequential accesslists. TheRandomAccessmarker interface enables lists to advertise the fact that they support random access. This enables generic algorithms to change their behavior to provide good performance when applied to either random or sequential access lists. Some implementations restrict what elements (or in the case ofMaps, keys and values) can be stored. Possible restrictions include requiring elements to: Be of a particular type. Be not null. Obey some arbitrary predicate. Attempting to add an element that violates an implementation's restrictions results in a runtime exception, typically aClassCastException, anIllegalArgumentException, or aNullPointerException. Attempting to remove or test for the presence of an element that violates an implementation's restrictions can result in an exception. Some restricted collections permit this usage. Collection Implementations Classes that implement the collection interfaces typically have names in the form of <Implementation-style><Interface>. The general purpose implementations are summarized in the following table: The general-purpose implementations support all of theoptional operationsin the collection interfaces and have no restrictions on the elements they may contain. They are unsynchronized, but theCollectionsclass contains static factories calledsynchronization wrappersthat can be used to add synchronization to many unsynchronized collections. All of the new implementations havefail-fast iterators, which detect invalid concurrent modification, and fail quickly and cleanly (rather than behaving erratically). TheAbstractCollection,AbstractSet,AbstractList,AbstractSequentialListandAbstractMapclasses provide basic implementations of the core collection interfaces, to minimize the effort required to implement them. The API documentation for these classes describes precisely how each method is implemented so the implementer knows which methods must be overridden, given the performance of the basic operations of a specific implementation. Concurrent Collections Applications that use collections from more than one thread must be carefully programmed. In general, this is known asconcurrent programming. The Java platform includes extensive support for concurrent programming. SeeJava Concurrency Utilitiesfor details. Collections are so frequently used that various concurrent friendly interfaces and implementations of collections are included in the APIs. These types go beyond the synchronization wrappers discussed previously to provide features that are frequently needed in concurrent programming. These concurrent-aware interfaces are available: BlockingQueue TransferQueue BlockingDeque ConcurrentMap ConcurrentNavigableMap The following concurrent-aware implementation classes are available. See the API documentation for the correct usage of these implementations. LinkedBlockingQueue ArrayBlockingQueue PriorityBlockingQueue DelayQueue SynchronousQueue LinkedBlockingDeque LinkedTransferQueue CopyOnWriteArrayList CopyOnWriteArraySet ConcurrentSkipListSet ConcurrentHashMap ConcurrentSkipListMap Design Goals The main design goal was to produce an API that was small in size and, more importantly, in "conceptual weight." It was critical that the new functionality not seem too different to current Java programmers; it had to augment current facilities, rather than replace them. At the same time, the new API had to be powerful enough to provide all the advantages described previously. To keep the number of core interfaces small, the interfaces do not attempt to capture such subtle distinctions as mutability, modifiability, and resizability. Instead, certain calls in the core interfaces areoptional, enabling implementations to throw anUnsupportedOperationExceptionto indicate that they do not support a specified optional operation. Collection implementers must clearly document which optional operations are supported by an implementation. To keep the number of methods in each core interface small, an interface contains a method only if either: It is a trulyfundamental operation: a basic operations in terms of which others could be reasonably defined, There is a compelling performance reason why an important implementation would want to override it. It was critical that all reasonable representations of collections interoperate well. This included arrays, which cannot be made to implement theCollectioninterface directly without changing the language. Thus, the framework includes methods to enable collections to be moved into arrays, arrays to be viewed as collections, and maps to be viewed as collections. Copyright ©1993, 2018, Oracle and/or its affiliates. All rights reserved. Contact Us

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:Activity测试之创建单元测试

原文地址:http://android.xsoftlab.net/training/activity-testing/activity-unit-testing.html Activity单元测试除了可以快速的验证Activity的状态之外,还可以验证Activity与底层组件之间的交互。单元测试通常用于测试较小的代码单元(它们通常不依赖系统或者网络资源),它们可能是一个方法,一个类或者其它组件。例如,开发者可以通过单元测试来检查Activity是否含有正确的布局,或者是否触发了正确的Intent。 不过单元测试通常不适用于测试与系统有交互的UI组件,该类测试情况应当使用ActivityInstrumentationTestCase2。 这节课将会学习如何使用单元测试来验证用于启动Activity的Intent。因为测试运行于独立的环境之中,所以Intent并不会实际发送到Android系统,但是你可以检测该Intent所携带的数据是否正确。 创建用于Activity单元测试的测试用例 类ActivityUnitTestCase对单个的Activity测试提供了支持。要进行Activity的单元测试,需继承ActivityUnitTestCase。 在ActivityUnitTestCase中的Activity并不会由Android系统自动启动。如果要在这里启动Activity,必须在这里显式的调用startActivity()方法,并传入要执行的Intent。 例如: public class LaunchActivityTest extends ActivityUnitTestCase<LaunchActivity> { ... @Override protected void setUp() throws Exception { super.setUp(); mLaunchIntent = new Intent(getInstrumentation() .getTargetContext(), LaunchActivity.class); startActivity(mLaunchIntent, null, null); final Button launchNextButton = (Button) getActivity() .findViewById(R.id.launch_next_activity_button); } } 验证另一个Activity的启动 单元测试可能含有以下目的: 验证在Button按下后LaunchActivity是否启动了Intent. 验证被启动的Intent所包含的数据是否正确. 为了验证在Button按下后是否有Intent被触发,开发者可以使用getStartedActivityIntent()方法获得被触发的Intent。然后通过断言方法来验证该方法返回的Intent是否为null,以及该Intent所含的数据是否正确。如果两个断言方法都正确,那么可以断定成功了触发了该Intent。 开发者所实现的代码可能如下: @MediumTest public void testNextActivityWasLaunchedWithIntent() { startActivity(mLaunchIntent, null, null); final Button launchNextButton = (Button) getActivity() .findViewById(R.id.launch_next_activity_button); launchNextButton.performClick(); final Intent launchIntent = getStartedActivityIntent(); assertNotNull("Intent was null", launchIntent); assertTrue(isFinishCalled()); final String payload = launchIntent.getStringExtra(NextActivity.EXTRAS_PAYLOAD_KEY); assertEquals("Payload is empty", LaunchActivity.STRING_PAYLOAD, payload); } 因为LaunchActivity是独立运行的,所以不能够使用库TouchUtils来直接控制UI。为了可以模拟Button的点击时间,可以直接调用performClick()方法。

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:线程执行操作之线程间通讯

原文地址:http://android.xsoftlab.net/training/multiple-threads/communicate-ui.html 上节课我们学习了如何启动一项由ThreadPoolExecutor所管理的线程任务。最后这节课我们将学习如何从任务中发送结果数据给UI线程。这项手段可以使任务在执行完毕后将结果显示到UI中去。 每个APP拥有独立的UI线程。只有在UI线程中创建的对象才可以访问该线程中的其它对象。正因为运行任务的线程不是UI线程,所以它们不可以直接访问UI对象。为了将数据从后台线程转移到UI线程,需要使用运行在UI线程中的Handler对象。 在UI线程中定义Handler Handler是Android系统框架管理线程的一部分。Handler对象专门用于接收消息处理消息。 一般来说,可以为新线程创建一个Handler,也可以为一个已经连接好的线程创建Handler。当你将Handler连接到UI线程时,处理消息的代码都会运行在UI线程中。 在构建线程池的类的构造方法中实例化一个Handler对象,并将该对象的引用存储于全局变量中。通过Handler(Looper)重载构造方法所实例化Handler可以与UI线程产生关联。这个构造方法所使用的Looper参数是Android系统的线程管理框架的另一部分。当以指定的Looper实例初始化一个Handler对象时,Handler对象会运行在Looper对象所在的线程中。 private PhotoManager() { ... // Defines a Handler object that's attached to the UI thread mHandler = new Handler(Looper.getMainLooper()) { ... 在Handler内,重写handleMessage()方法。Android系统会在Handler所管理的线程中接收到一条新的消息时回调该方法: /* * handleMessage() defines the operations to perform when * the Handler receives a new Message to process. */ @Override public void handleMessage(Message inputMessage) { // Gets the image task from the incoming Message object. PhotoTask photoTask = (PhotoTask) inputMessage.obj; ... } ... } } 将数据从非UI线程转移到UI线程 为了将数据从后台进程转移到UI进程,首先将数据的引用以及UI对象存储于任务对象中。接下来,将该任务对象及状态码传给由Handler对象所初始化的对象。在这个对象内,发送一条包含状态码的任务对象给Handler。因为Handler是运行在UI线程的,所以它可以将数据交给UI对象。 存储数据于任务对象中 举个例子,这里有一个Runnable对象,运行于后台线程,它用于解码一个Bitmap对象,并将其存储于它所属的对象PhotoTask中。该Runnable还会存储一个状态码:DECODE_STATE_COMPLETED。 // A class that decodes photo files into Bitmaps class PhotoDecodeRunnable implements Runnable { ... PhotoDecodeRunnable(PhotoTask downloadTask) { mPhotoTask = downloadTask; } ... // Gets the downloaded byte array byte[] imageBuffer = mPhotoTask.getByteBuffer(); ... // Runs the code for this task public void run() { ... // Tries to decode the image buffer returnBitmap = BitmapFactory.decodeByteArray( imageBuffer, 0, imageBuffer.length, bitmapOptions ); ... // Sets the ImageView Bitmap mPhotoTask.setImage(returnBitmap); // Reports a status of "completed" mPhotoTask.handleDecodeState(DECODE_STATE_COMPLETED); ... } ... } ... PhotoTask还包含了一个用于展示Bitmap的ImageView的句柄。尽管引用Bitmap以及ImageView的是同一个对象,但是还是不能将Bitmap赋值给ImageView,因为当前并没有处在UI线程中。 发送状态到对象层级 PhotoTask在层级内处于第二高度。它维护了图像的解码数据以及一个View对象。它会接收PhotoDecodeRunnable中的状态码,并将其传给维护线程池以及Handler的那个对象。 public class PhotoTask { ... // Gets a handle to the object that creates the thread pools sPhotoManager = PhotoManager.getInstance(); ... public void handleDecodeState(int state) { int outState; // Converts the decode state to the overall state. switch(state) { case PhotoDecodeRunnable.DECODE_STATE_COMPLETED: outState = PhotoManager.TASK_COMPLETE; break; ... } ... // Calls the generalized state method handleState(outState); } ... // Passes the state to PhotoManager void handleState(int state) { /* * Passes a handle to this task and the * current state to the class that created * the thread pools */ sPhotoManager.handleState(this, state); } ... } 将数据展示到UI PhotoManager收到从PhotoTask中发来的状态码,然后处理这个PhotoTask对象。因为状态是TASK_COMPLETE,所以先创建一个Message,然后通过这个Message对象将状态以及人物对象发送给Handler: public class PhotoManager { ... // Handle status messages from tasks public void handleState(PhotoTask photoTask, int state) { switch (state) { ... // The task finished downloading and decoding the image case TASK_COMPLETE: /* * Creates a message for the Handler * with the state and the task object */ Message completeMessage = mHandler.obtainMessage(state, photoTask); completeMessage.sendToTarget(); break; ... } ... } 最后,在Handler.handleMessage()中检查每条消息的状态。如果消息的状态为TASK_COMPLETE,那么表明该任务已经终结,那么Message中所包含的PhotoTask对象包含了一个Bitmap以及一个ImageView。因为Handler.handleMessage()是运行在UI线程的,所以它可以安全的将Bitmap赋给ImageView: private PhotoManager() { ... mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message inputMessage) { // Gets the task from the incoming Message object. PhotoTask photoTask = (PhotoTask) inputMessage.obj; // Gets the ImageView for this task PhotoView localView = photoTask.getPhotoView(); ... switch (inputMessage.what) { ... // The decoding is done case TASK_COMPLETE: /* * Moves the Bitmap from the task * to the View */ localView.setImageBitmap(photoTask.getImage()); break; ... default: /* * Pass along other messages from the UI */ super.handleMessage(inputMessage); } ... } ... } ... } ... }

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:线程执行操作之线程池操作

原文地址:http://android.xsoftlab.net/training/multiple-threads/run-code.html#StopThread 上节课我们学习了如何定义一个类用于管理线程以及任务。这节课将会学习如何在线程池中运行任务。要做到这一点,只需要往线程池的工作队列中添加任务即可。当一条线程处于闲置状态时,那么ThreadPoolExecutor会从任务队列中取出一条任务并放入该线程中运行。 这节课还介绍了如何停止一个正在运行中的任务。如果在任务开始后,可能发现这项任务并不是必须的,那么就需要用到任务取消的功能了。这样可以避免浪费处理器的时间。举个例子,如果你正从网络上下载一张图像,如果侦测到这张图像已经在缓存中了,那么这时就需要停止这项网络任务了。 在线程池中的线程内运行任务 为了在指定的线程池中启动一项线程任务,需要将Runnable对象传给ThreadPoolExecutor的execute()方法。这个方法会将任务添加到线程池的工作队列中去。当其中一个线程变为闲置状态时,那么线程池管理器会从队列中取出一个已经等待了很久的任务,然后放到这个线程中运行: public class PhotoManager { public void handleState(PhotoTask photoTask, int state) { switch (state) { // The task finished downloading the image case DOWNLOAD_COMPLETE: // Decodes the image mDecodeThreadPool.execute( photoTask.getPhotoDecodeRunnable()); ... } ... } ... } 当ThreadPoolExecutor启动一个Runnable时,它会自动调用Runnable的run()方法。 中断执行中的代码 如果要停止一项任务,那么需要中断该任务所在的线程。为了可以预先做到这一点,那么需要在任务创建时存储该任务所在线程的句柄: class PhotoDecodeRunnable implements Runnable { // Defines the code to run for this task public void run() { /* * Stores the current Thread in the * object that contains PhotoDecodeRunnable */ mPhotoTask.setImageDecodeThread(Thread.currentThread()); ... } ... } 我们可以调用Thread.interrupt()方法来中断一个线程。这里要注意Thread对象是由系统控制的,系统会在应用进程的范围之外修改它们。正因为这个原因,在中断线程之前,需要对线程的访问加锁。通常需要将这部分代码放入同步代码块中: public class PhotoManager { public static void cancelAll() { /* * Creates an array of Runnables that's the same size as the * thread pool work queue */ Runnable[] runnableArray = new Runnable[mDecodeWorkQueue.size()]; // Populates the array with the Runnables in the queue mDecodeWorkQueue.toArray(runnableArray); // Stores the array length in order to iterate over the array int len = runnableArray.length; /* * Iterates over the array of Runnables and interrupts each one's Thread. */ synchronized (sInstance) { // Iterates over the array of tasks for (int runnableIndex = 0; runnableIndex < len; runnableIndex++) { // Gets the current thread Thread thread = runnableArray[taskArrayIndex].mThread; // if the Thread exists, post an interrupt to it if (null != thread) { thread.interrupt(); } } } } ... } 在多数情况下,Thread.interrupt()会使线程立刻停止。然而,它只会将那些正在等待的线程停下来,它并不会中止CPU或网络任务。为了避免使系统变慢或卡顿,你应当在开始任意一项操作之前测试是否有中断请求: /* * Before continuing, checks to see that the Thread hasn't * been interrupted */ if (Thread.interrupted()) { return; } ... // Decodes a byte array into a Bitmap (CPU-intensive) BitmapFactory.decodeByteArray( imageBuffer, 0, imageBuffer.length, bitmapOptions); ...

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:布局性能优化之ListView的优化

原文地址:http://android.xsoftlab.net/training/improving-layouts/smooth-scrolling.html 想要让ListView滑动流畅的关键所在是减轻主线程的负担。要确保任何的磁盘访问、网络访问、或者SQL访问都是在单独的线程中执行的。如果要测试APP的状态,可以开启StrictMode。 使用后台线程 使用工作线程可以使UI线程将所有的注意力都集中在UI的绘制上。在很多情况下,使用AsyncTask所提供的功能就可以在工作线程中处理耗时任务。AsyncTask会自动的将execute()发起的请求排队,并依次执行。这意味着你不要自己创建线程池。 在下面的示例代码中,AsyncTask被用来加载一张图像,并在加载结束后自动的将其渲染到UI上。它还在图像加载时展示了一个旋转的进度条。 // Using an AsyncTask to load the slow images in a background thread new AsyncTask<ViewHolder, Void, Bitmap>() { private ViewHolder v; @Override protected Bitmap doInBackground(ViewHolder... params) { v = params[0]; return mFakeImageLoader.getImage(); } @Override protected void onPostExecute(Bitmap result) { super.onPostExecute(result); if (v.position == position) { // If this item hasn't been recycled already, hide the // progress and set and show the image v.progress.setVisibility(View.GONE); v.icon.setVisibility(View.VISIBLE); v.icon.setImageBitmap(result); } } }.execute(holder); 从Android 3.0开始,AsyncTask提供了一项新特性:可以将任务运行在多核处理器上。你可以使用executeOnExecutor()方法发起执行请求,这样多个请求就可以同时进行,同时进行的任务数量取决于CPU的核心数量。 使用View Holder持有View对象 在滑动ListView时,代码可能会频繁的调用findViewById(),这会降低性能。就算是Adapter将已经加载过的View返回,但是在复用时还是需要去查询这些View来更新它们。杜绝重复使用findViewById()的方法就是使用”View Holder”设计模式。 ViewHolder对象将每个View组件存储于布局容器的tag属性内,所以你可以快速访问它们而不需要每次都去查询。首先,你需要创建一个类来持有已加载的View: static class ViewHolder { TextView text; TextView timestamp; ImageView icon; ProgressBar progress; int position; } 然后对ViewHolder的成员属性赋值,然后将其存放在布局容器内: ViewHolder holder = new ViewHolder(); holder.icon = (ImageView) convertView.findViewById(R.id.listitem_image); holder.text = (TextView) convertView.findViewById(R.id.listitem_text); holder.timestamp = (TextView) convertView.findViewById(R.id.listitem_timestamp); holder.progress = (ProgressBar) convertView.findViewById(R.id.progress_spinner); convertView.setTag(holder); 那么现在就可以很方便的对这些View组件进行访问,而不再需要对它们单独进行查询,如此便可以节省出宝贵的CPU资源。

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:布局性能优化之布局层级优化

原文地址:http://android.xsoftlab.net/training/improving-layouts/index.html 引言 布局是直接影响用户体验的关键部分。如果实现的不好,那么布局很有可能会导致内存的紧张。Android的SDK包含的一些工具可以用来检查布局性能上的问题。结合本章的课程学习,你将有能力以低成本的内存开销实现更为顺畅的UI体验。 优化布局层级 有一个常见的误解就是使用基本的布局结构会使布局更高效。然而却不是这样的,每一个控件、布局容器都需要执行初始化、排布、绘制等过程。举个例子,使用内嵌的LinearLayout会使布局层级过度加深。进一步讲,内嵌多个使用了layout_weight参数的控件所花费的代价尤其高昂,因为每个子View都需要被测量两次。这在布局被重复加载时尤为重要,比如使用在ListView或GridView中的时候。 在这节课我们将会学习如何使用Hierarchy Viewer工具及Layoutopt工具来检查、优化布局。 布局检查 Android的SDK包含了一个名为Hierarchy Viewer的工具。使用该工具可以帮助发现影响布局性能的瓶颈。 Hierarchy Viewer工作于所选择的进程上,它会显示一个布局树。每个View节点上的信号灯代表了该View在测量、排布、绘制上的性能优劣,这可以帮助你发现潜在的问题。 举个例子说明:下图是ListView的一个Item。该Item左边用于显示图片,而右边则显示两行文本。因为该Item会被进行多次加载,所以对其优化的话,那么UI性能会有显著的提升。 Hierarchy Viewer工具位于< sdk>/tools/目录下。打开后,Hierarchy Viewer会列出当前的可用设备以及设备上运行的组件。点击Load View Hierarchy来浏览所选组件的布局层级。下图是上图位于ListView中的运行效果演示: 在上图中,我们可以看到View的层级为3,并且在文本的排布上发现了一些问题。点击每个节点我们可以看到每个阶段所花费的时间(如下图所示)。那么我们就可以很清晰的知道哪个Item在测量、排布、渲染上花费的时间最长,所以我们就需要花点时间专门对其优化。 这里我们可以看到每个阶段所花费的时间: Measure: 0.977ms Layout: 0.167ms Draw: 2.717ms 调整布局 因为上面的示例说布局的性能慢是由于内嵌了一个LinearLayout,所以改进这部分性能只能通过扁平化来处理。要尽量使布局变浅变宽,杜绝变窄变深。RelativeLayout可以实现这样的布局。所以当使用RelativeLayout实现这样的布局时,那么可以看到布局的层级变为了2。我们所看到的布局图就是这个样子: 下面是优化后的时间开销: Measure: 0.598ms Layout: 0.110ms Draw: 2.146ms 我们可能会看到很微小的改进。 在改进时间上的大部分差别是由于LinearLayout的权重造成的,它会降低测量的速度。这里的示例仅仅是个优化手段的演示,在开发过程中应当认真考虑是否有必要使用权重。 使用Lint 开发者应该使用lint工具来检查布局层级是否有可优化的地方。Lint 与Layoutopt 相比有更加强大的功能。一些Lint的检查规则如下: 使用组合图形 - 一个包含了ImageView和TextView的LinearLayout作为组合图形处理起来更加高效。 合并根帧布局 - 如果一个FrameLayout是根布局,并且它没有提供背景色或内边距什么的,那么可以使用合并标签将其替换,这可以稍微的改进性能。 无用的叶子节点 - 如果一个布局没有子View,没有背景色,那么通常可以将其移除。 无用的中间节点 - 如果一个布局内部只含有一个子View,并且不是ScrollView或者根布局,也没有背景色,那么可以将它移除,并将其子View移动到它的父容器内。 非常深的布局嵌套 - 一个被嵌套很深的布局通常不利于性能。考虑使用RelativeLayout或者GridLayout这种扁平化布局来改进性能。默认的最大深度为10。 Lint的另一个好处就是它被集成进了Android Studio。Lint会在程序编译时自动运行。 你也可以管理检查Lint的配置,在Android Studio内通过File>Settings>Project Settings路径可以找到。 Lint可以自动的修复一些问题,并且会对余下的问题提供一些优化建议,以便使开发者手动修复。

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:后台服务之IntentService的使用

原文地址:http://android.xsoftlab.net/training/run-background-service/send-request.html 上节课我们学习了如何创建IntentService。这节课我们主要学习如何通过Intent使IntentService执行工作请求。Intent可以将任何数据交给IntentService处理。你可以在Activity或者Fragment的任意方法内发送Intent给IntentService。 创建并发送工作请求到IntentService 为了创建一个工作请求并将其发送给IntentService,首先我们需要创建一个显式 的Intent对象,然后向其添加请求数据,最后再通过startService()将它发送到IntentService。 下面的代码演示了这个过程: 为名RSSPullService的IntentService创建一个显式 的Intent。 /* * Creates a new Intent to start the RSSPullService * IntentService. Passes a URI in the * Intent's "data" field. */ mServiceIntent = new Intent(getActivity(), RSSPullService.class); mServiceIntent.setData(Uri.parse(dataUrl)); 调用startService()。 // Starts the IntentService getActivity().startService(mServiceIntent); 注意,你可以在Activity或者Fragment的任何地方发送工作请求。举个例子,如果你需要先获得用户的输入数据,那么就可以将工作请求的发送代码放在Button按钮的点击回调内。 一旦调用了startService(),那么IntentService将会在onHandleIntent()方法内执行工作请求,并且它会在任务完成后自动停止。 下一个步骤就是如何将工作的完成结果反馈给请求调用处。下一节课将会学习如何使用BroadcastReceiver完成这个过程。

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:后台服务之IntentService的创建

原文地址:http://android.xsoftlab.net/training/run-background-service/index.html 引言 除非特别指定,否则所有的操作都是在UI线程中执行的。不过这会引起问题,因为长时间的耗时操作会妨碍UI线程的运行。这会惹恼用户,并可能会引起系统错误。为了避免这样的情况出现,Android为此提供了一些类,可以使这些耗时操作放在单独的线程中执行。这里用到最多的就是IntentService了。 这节课主要学习如何实现IntentService,以及如何向它发送工作请求,以及如何响应它的执行结果。 创建后台服务 IntentService提供了一个非常简单的构造方法。IntentService允许执行耗时操作,而又不会引起UI线程的阻塞。同样的,IntentService还不受UI生命周期的影响。所以它可以在一个单独的环境中持续运行。 不过IntentService也是有限制的,列举如下: 它不可以与UI线程直接交互。为了将结果递送到UI,不得不采用广播的形式将结果发送出去。 工作请求是按顺序执行的。如果目前已经有一个操作在IntentService中执行,那么再往其中发送操作请求的话,这些操作请求都将会等待,直至第一个操作完成。 IntentService中的操作不可以被中断。 不管怎样,大多数情况下,IntentService是执行后台操作的首选方式。 这节课主要学习如何实现IntentService,以及如何创建请求回调方法onHandleIntent(),最后我们还会学习如何在清单文件中声明该IntentService。 创建IntentService 首先需要创建一个类并继承IntentService,然后重写它的onHandleIntent()方法: public class RSSPullService extends IntentService { @Override protected void onHandleIntent(Intent workIntent) { // Gets data from the incoming Intent String dataString = workIntent.getDataString(); ... // Do work here, based on the contents of dataString ... } } 这里要注意普通Service的那些回调方法,比如onStartCommand()方法,它会被IntentService自动调用,所以在IntentService内部最好不要重写这些方法。 在清单文件中声明IntentService IntentService同样需要在清单文件中定义。它的定义方式与普通Service是一样的。 <application android:icon="@drawable/icon" android:label="@string/app_name"> ... <!-- Because android:exported is set to "false", the service is only available to this app. --> <service android:name=".RSSPullService" android:exported="false"/> ... <application/> 其中android:name属性说明了IntentService的类名。 这里要注意:Service标签内并没有包含Intent Filter。因为其它组件是通过显示Intent发送工作请求的。所以这里不需要定义Intent Filter。这也意味着只有相关组件内的APP或者拥有相同用户ID的程序才可以访问该服务。 现在我们已经定义好了一个IntentService类,接下来就可以通过Intent对象向其发送工作请求了。关于如何构建相关对象以及如何发送请求到IntentService的相关内容将会在下节课学习。

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:手势处理之ViewGroup的事件管理

原文地址:https://developer.android.com/training/gestures/viewgroup.html 在ViewGroup中处理触摸事件要格外小心,因为在ViewGroup中有很多子View,而这些子View对于不同的触摸事件来说是不同的目标。要确保每个View都正确的接收了相应的触摸事件。 在ViewGroup中拦截触摸事件 onInterceptTouchEvent()方法会在触摸事件到达ViewGroup的表面时调用,这包括内部的子View。如果onInterceptTouchEvent()返回了true,那么MotionEvent对象就会被拦截,这意味着该次事件不会传给子View,而是会传给ViewGroup本身的onTouchEvent()方法。 onInterceptTouchEvent()给了ViewGroup本身一个机会:在子View获得任何事件之前一个拦截该事件的机会。如果onInterceptTouchEvent()返回了true,那么原先处理该次事件的子View就会收到一个ACTION_CANCEL的事件,并且原先事件的剩余事件都会被传到该ViewGroup的onTouchEvent()方法中做常规处理。onInterceptTouchEvent()还可以返回false,这样的话,该次事件则会通过View树继续向下传递,直到到达目标View为止,目标View会在自己的onTouchEvent()方法中处理该次事件。 在下面的示例代码中,类MyViewGroup继承了ViewGroup,并包含了多个View,这些View我们在这里称之为子View,而MyViewGroup称为父容器View。如果你在水平方向上滑动手指,那么子View皆不会收到触摸事件。MyViewGroup会通过滚动它的内部来实现触摸事件的处理。不管如何,如果你按下了子View中的按钮,或者在垂直方向上滑动,那么ViewGroup则不会去拦截这些事件,因为子View是该次事件的目标View。在这些情况下,onInterceptTouchEvent()应该返回false,且MyViewGroup的onTouchEvent()方法也不会被调用。 public class MyViewGroup extends ViewGroup { private int mTouchSlop; ... ViewConfiguration vc = ViewConfiguration.get(view.getContext()); mTouchSlop = vc.getScaledTouchSlop(); ... @Override public boolean onInterceptTouchEvent(MotionEvent ev) { /* * This method JUST determines whether we want to intercept the motion. * If we return true, onTouchEvent will be called and we do the actual * scrolling there. */ final int action = MotionEventCompat.getActionMasked(ev); // Always handle the case of the touch gesture being complete. if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { // Release the scroll. mIsScrolling = false; return false; // Do not intercept touch event, let the child handle it } switch (action) { case MotionEvent.ACTION_MOVE: { if (mIsScrolling) { // We're currently scrolling, so yes, intercept the // touch event! return true; } // If the user has dragged her finger horizontally more than // the touch slop, start the scroll // left as an exercise for the reader final int xDiff = calculateDistanceX(ev); // Touch slop should be calculated using ViewConfiguration // constants. if (xDiff > mTouchSlop) { // Start scrolling! mIsScrolling = true; return true; } break; } ... } // In general, we don't want to intercept touch events. They should be // handled by the child view. return false; } @Override public boolean onTouchEvent(MotionEvent ev) { // Here we actually handle the touch event (e.g. if the action is ACTION_MOVE, // scroll this container). // This method will only be called if the touch event was intercepted in // onInterceptTouchEvent ... } } 这里要注意,ViewGroup还提供了requestDisallowInterceptTouchEvent()方法。当子View不希望它的父容器及祖先容器拦截触摸事件时,ViewGroup会在 onInterceptTouchEvent()方法中对其进行调用,从而判断是否要拦截本次事件。 使用ViewConfiguration常量 在上面的代码中使用了ViewConfiguration来初始化一个名为mTouchSlop的变量。你可以使用ViewConfiguration来访问Android系统所使用的常用距离、速度及时间。 “mTouchSlop”引用了触摸事件在被拦截之前手指移动的以像素为单位的距离。Touch slop经常被用来在用户在执行触摸操作时防止产生意外滚动。 ViewConfiguration的另外两个常用方法是getScaledMinimumFlingVelocity()和getScaledMaximumFlingVelocity()。这两个方法分别返回了用于初始化滚动的最小、最大的速度值。以每秒几像素为单位: ViewConfiguration vc = ViewConfiguration.get(view.getContext()); private int mSlop = vc.getScaledTouchSlop(); private int mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); private int mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); ... case MotionEvent.ACTION_MOVE: { ... float deltaX = motionEvent.getRawX() - mDownX; if (Math.abs(deltaX) > mSlop) { // A swipe occurred, do something } ... case MotionEvent.ACTION_UP: { ... } if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity && velocityY < velocityX) { // The criteria have been satisfied, do something } } 扩展子View的触控区域 Android提供的TouchDelegate使扩展子View的触控区域成为了可能。这对于子View本身特别小,而它的触控区域需要很大时很有用。如果需要的话,你也可以使用这种方式来缩小子View的触控区域。 在下面的示例中,ImageButton作为我们的”delegate view”(这里的意思是需要父容器扩展触控区域的那个View)。下面是示例的布局文件: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/parent_layout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <ImageButton android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@null" android:src="@drawable/icon" /> </RelativeLayout> 下面的代码做了以下这些事情: 获得父容器View,并Post一个Runnale对象到UI线程。这可以确保在调用getHitRect()方法之前父容器已经对子View完成了排布。getHitRect()会返回父容器坐标内当前View的点击矩阵(触控区域)。 找到ImageButton,然后调用它的getHitRect()方法获得该View的触控边界。 扩大ImageButton的触控区域。 实例化一个TouchDelegate,将要扩展的触控区域矩阵与要扩展触控区域的ImageView作为参数传入。 将TouchDelegate设置给父容器View,只有这样做,我们所触碰到的扩展区域才会被路由到子View上。 在TouchDelegate代理的范围内,父容器View将会接收所有的触摸事件。如果触摸事件发生在子View本身的触控区域内,那么父容器View会将所有的触摸事件传给子View处理: public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Get the parent view View parentView = findViewById(R.id.parent_layout); parentView.post(new Runnable() { // Post in the parent's message queue to make sure the parent // lays out its children before you call getHitRect() @Override public void run() { // The bounds for the delegate view (an ImageButton // in this example) Rect delegateArea = new Rect(); ImageButton myButton = (ImageButton) findViewById(R.id.button); myButton.setEnabled(true); myButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(MainActivity.this, "Touch occurred within ImageButton touch region.", Toast.LENGTH_SHORT).show(); } }); // The hit rectangle for the ImageButton myButton.getHitRect(delegateArea); // Extend the touch area of the ImageButton beyond its bounds // on the right and bottom. delegateArea.right += 100; delegateArea.bottom += 100; // Instantiate a TouchDelegate. // "delegateArea" is the bounds in local coordinates of // the containing view to be mapped to the delegate view. // "myButton" is the child view that should receive motion // events. TouchDelegate touchDelegate = new TouchDelegate(delegateArea, myButton); // Sets the TouchDelegate on the parent view, such that touches // within the touch delegate bounds are routed to the child. if (View.class.isInstance(myButton.getParent())) { ((View) myButton.getParent()).setTouchDelegate(touchDelegate); } } }); } }

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:手势处理之多点触控处理

原文地址:http://android.xsoftlab.net/training/gestures/multi.html 多点触控是指多个手指同时触摸屏幕的情况。这节课主要学习如何检测多点触控手势。 记录多个触控点 当多根手指同时触碰到屏幕时,系统会产生以下触摸事件: ACTION_DOWN -第一个触碰到屏幕的点。它是手势的起始事件。这个触控点的指针数据在MotionEvent对象中的索引总是0。 ACTION_POINTER_DOWN -除第一个触控点之外的其它点。这个触控点的指针数据的索引由getActionIndex()方法返回。 ACTION_MOVE -屏幕上的手指位置发生变化时。 ACTION_POINTER_UP -除最开始按下的其它触控点离开屏幕时。 ACTION_UP -最后一个触控点离开屏幕时。 我们可以通过每一个触控点对应的索或ID来追踪MotionEvent对象中的每一个触控点: Index: MotionEvent对象将触控点的相关信息存储于一个数组中。每一个触控点的索引则是这个触控点在数组中的相对位置。MotionEvent对象的大多数方法都可以使用这些索引来与这些点产生交互。 ID: 每一个触控点也含有一个ID映射,这个映射关系在手势事件的整个生命周期内与相对应的触控点一直保持相对关系。 每个触控点的出现顺序是不固定的。因此,触控点的索引可以由事件转移到下一个索引,但是触控点的ID始终保持为一个常量。使用getPointerId()方法可以获得指定触控点的ID,因此可以在余下的手势事件中还可以继续保持与这个触控点的联系。使用findPointerIndex()方法可以根据指定的ID获得触控点的索引: private int mActivePointerId; public boolean onTouchEvent(MotionEvent event) { .... // Get the pointer ID mActivePointerId = event.getPointerId(0); // ... Many touch events later... // Use the pointer ID to find the index of the active pointer // and fetch its position int pointerIndex = event.findPointerIndex(mActivePointerId); // Get the pointer's current position float x = event.getX(pointerIndex); float y = event.getY(pointerIndex); } 获取事件的活动 使用getActionMasked()方法可以获取MotionEvent的活动。与getAction()方法不同,getActionMasked()适用于多个触控点。它会返回正在执行的活动。你可以使用getActionIndex()方法获得与之相关联的触控点的索引。下面的代码演示了这个过程: Note: 示例中使用了MotionEventCompat类。这个类位于支持库中。你应该使用该类以便提供良好的向后兼容性。注意,MotionEventCompat类并不可以替代MotionEvent类。这个类提供了一个实用的静态方法,可以将MotionEvent对象所关联的活动提取出来。 int action = MotionEventCompat.getActionMasked(event); // Get the index of the pointer associated with the action. int index = MotionEventCompat.getActionIndex(event); int xPos = -1; int yPos = -1; Log.d(DEBUG_TAG,"The action is " + actionToString(action)); if (event.getPointerCount() > 1) { Log.d(DEBUG_TAG,"Multitouch event"); // The coordinates of the current screen contact, relative to // the responding View or Activity. xPos = (int)MotionEventCompat.getX(event, index); yPos = (int)MotionEventCompat.getY(event, index); } else { // Single touch event Log.d(DEBUG_TAG,"Single touch event"); xPos = (int)MotionEventCompat.getX(event, index); yPos = (int)MotionEventCompat.getY(event, index); } ... // Given an action int, returns a string description public static String actionToString(int action) { switch (action) { case MotionEvent.ACTION_DOWN: return "Down"; case MotionEvent.ACTION_MOVE: return "Move"; case MotionEvent.ACTION_POINTER_DOWN: return "Pointer Down"; case MotionEvent.ACTION_UP: return "Up"; case MotionEvent.ACTION_POINTER_UP: return "Pointer Up"; case MotionEvent.ACTION_OUTSIDE: return "Outside"; case MotionEvent.ACTION_CANCEL: return "Cancel"; } return ""; } 有关多点触控的更多信息,可以参见课程Dragging and Scaling.

优秀的个人博客,低调大师

macOS 官方共三年更新支持,但你知道苹果给固件提供多久的支持吗?

众所周知,Apple 支持每个版本的 macOS 整整一年的更新,然后再提供两年的安全更新。但它对每个型号的固件支持多长时间?现在更新 Mac 固件的唯一方法是安装 macOS 更新,这会如何影响支持期?本文试图回答这些问题,并在此过程中揭开这长达十多年的谜团。 数据 Apple 不会发布任何有关固件版本或更新的信息,甚至很少在安全更新的发布说明中提及它们。幸运的是,自从七年前 High Sierra 发布以来,我一直在跟踪每种型号的固件版本,因此我有自己的记录,这些记录来自 macOS 更新中包含的版本。我将这些记录与 Ian Page 的Mactracker 数据库中给出的型号推出和停产日期进行了匹配,并在此总结了结果。 更新的工作原理 每次 macOS 更新都可能带来固件更新,尽管在支持的第一年,纯安全补丁带来的更新往往较少。通常同时发布的三个 macOS 更新中的固件更新都是相同的。因此,最近更新到 14.6 带来的更新与 13.6.8 和 12.7.5 中的更新相同,适用于各自支持的型号,但每次更新只会安装其支持的型号的更新。借助示例,这一点变得更加清晰,这些示例也揭示了这些更新的内在奥秘。 2020 年 7 月 15 日,主要更新带来了 macOS 10.15.6,以及针对 macOS 10.13 和 10.14 的安全更新 (SU)。其中包括以下 EFI 固件版本: 适用于 iMac12,1 版本 87.0.0.0.0(2019 年 6 月 14 日) 适用于 iMac13,1 版本 292.0.0.0.0,发布日期:2020 年 6 月 10 日 适用于 MacBookPro8,1 版本 87.0.0.0.0(2019 年 6 月 13 日) 适用于 MacBookPro9,1 版本 233.0.0.0.0,发布日期为 2020 年 6 月 10 日。 这两个适用于 iMac12,1 和 MacBookPro8,1 的固件版本均为 2019 年,当时已经有一年的历史了,因为 Apple 已于 2019 年 6 月停止为这两款型号发布新固件版本。但是,如果 iMac13,1 和 MacBookPro9,1 型号安装了 macOS 10.15.6 或任一安全更新,则会收到新版本的固件。 一年后,即 2021 年 7 月 21 日,Apple 发布了 macOS 11.5 更新,并发布了 Mojave SU 2021-005。由于 iMac12,1 和 MacBookPro8,1 不再能够运行受支持的 macOS 版本,因此它们都没有固件更新,只能运行 2019 年 6 月的版本。随后,两款较新的型号进行了以下更新: 适用于 iMac13,1 版本 422.0.0.0.0,2021 年 6 月 4 日 适用于 MacBookPro9,1 版本 422.0.0.0.0,发布日期:2021 年 6 月 4 日。 又过了一年,即 2022 年 7 月 20 日,这两款型号仍可运行受支持的 macOS,并在 Catalina SU 2022-005 中进行了以下固件更新: 适用于 iMac13,1 版本 429.0.0.0.0,发布日期:2022 年 3 月 18 日 适用于 MacBookPro9,1 版本 429.0.0.0.0,发布日期:2022 年 3 月 18 日。 但这些并不是该 SU 中的新功能,因为那时这两种型号的固件更新已经停止,而在 2023 年 5 月 18 日的 Big Sur 11.7.7 中,这两种型号都没有任何可用的固件,因为它们不再受仍在接收更新的 macOS 版本的支持。 这个例子揭示了一个鲜明的事实:对于相隔一年多发布的 ​​iMac 和 MacBook Pro 的连续型号,上次发布的固件更新却相隔了近三年的时间: 对于 iMac12,1 最新发布于 2019 年 6 月,对于 iMac13,1 最新发布于 2022 年 3 月 对于 MacBookPro8,1 来说,最新发布于 2019 年 6 月,对于 MacBookPro9,1 来说,最新发布于 2022 年 3 月。 多久? 因此,我收集了 2009 年 10 月至今推出的 40 款未配备 T2 芯片的英特尔 Mac 的数据,每款产品显然都已通过了最终固件更新。这不包括目前仍在接收固件更新的少数型号。 此图表按型号推出日期显示了每个型号的最后一次固件更新日期。2012 年之前推出的大量 Mac 于 2019 年 6 月收到了最后一次固件更新,之后近两年的时间里,所有后续型号都收到了进一步的固件更新,之后的下一批旧型号(这次是 2012-13 年推出的)才收到了最终更新。右上角可见一个异常值,即 2019 年 3 月推出的 iMac19,1,但似乎在 2024 年 2 月进行了最后一次更新,非常早。虽然此后没有收到任何固件更新,但将来可能会收到更多固件更新。 此图表显示了该型号推出之日起固件支持的总长度(以年为单位)。有三个不同的组: 2012 年之前的模型,在左侧形成一条陡峭的线,支持时间从不到 8 年到近 10 年不等; 更新的模型,形成不太密集的散点,支持时间从不到 7.5 年到近 10 年; 右下角是 iMac19,1 异常值,其支持时间极短,约为 5 年。 这是同一张图表,但叠加了标签,标明了每种型号的名称。型号范围(例如 iMac)和支持期限之间似乎没有任何关联。 因此,对于自 2009 年以来推出的大多数不带 T2 芯片的英特尔型号,固件更新支持已延长至推出以来至少 8 年。由于型号的推出和停产之间的时间差异很大,因此在以停产日期表示时,分散性较大。 差距 有几个可能的原因可以解释 2012 年之前推出的 Mac 与最近推出的 Mac 之间的差异。这些包括: 2011-12 年推出的 Mac 电脑从 Sandy Bridge 过渡到 Ivy Bridge; 在推出 Apple 硅片型号期间,Intel Mac 预计会保持一段稳定期; 苹果选择在 Covid 大流行期间不停止固件支持,尽管我不记得曾经明确表示过这一点; 苹果固件支持政策的任意改变。 我赞成不再使用 Sandy Bridge,因为众所周知,Sandy Bridge 存在一些问题,可能导致固件支持比预期更早地结束。 值得注意的是,这种差距并不意味着在此期间没有发布固件更新,而只是意味着在此期间仍在更新的型号会继续更新,并且不会终止任何更新。 T2 和 Apple 芯片 这些较新的型号从 2017 年开始推出,完全改变了固件更新。所有配备 T2 芯片的 Mac 都会收到看似相同的固件更新。尽管 Mac 仍受 macOS 更新支持,但后续更新仍被放弃(在某些情况下会发生这种情况),但 T2 固件更新似乎只有在型号不再受 macOS 更新支持时才会停止。 由于 Apple 完全拥有 Apple Silicon Mac 的硬件和操作系统,因此可以决定对每款产品的支持期限。 结论 对于大多数未配备 T2 芯片的 Intel Mac,Apple 在该型号推出后至少 8 年内都提供了固件更新。对于许多型号,在它们无法运行受支持的 macOS 版本之前,就已经进行了固件更新。 2012 年之前推出的一些 Mac 搭载了 Sandy Bridge 芯片组,其固件支持早早被取消。原因尚不清楚,但可能与芯片组有关。 T2 和 Apple Silicon Mac 将会有所不同。 原文链接:https://eclecticlight.co/2024/08/06/how-long-does-apple-support-mac-firmware

优秀的个人博客,低调大师

🔥🔥🔥 最好用的跨平台 asiinema 终端录屏分享工具,比官方工具还强大

项目地址: https://github.com/gvcgo/asciinema 支持的平台:**MacOS/Linux/Windows** 支持的操作: 录制 播放 编辑(加速、剪切、整理延迟) 上传 asciinema.org 官网,并回显链接 转换为 gif 演示: 以下 gif 均使用本项目录制、编辑、转换,最终生成 gif 动图 1 、原速 gif( https://asciinema.org/a/651138) 2 、2 倍速 gif( https://asciinema.org/a/651140)

优秀的个人博客,低调大师

iPhone 13第三方换屏更难:Face ID会失效 即便官方配件也不行

苹果对 iPhone 13 系列屏幕的重新设计,意味着第三方维修公司现在更难更换屏幕。因为在尝试更换损坏掉的屏幕,可能会导致失去对 Face ID 的支持。碎屏是最常见的手机故障之一,任何意外的跌落、磕碰、挤压等等都会造成。 当 iPhone 出现故障,用户可以选择就近的 Apple Store 天才吧、或者苹果认证的维修网点、亦或者是第三方的维修中心。而如果是 iPhone 屏幕损坏,那么不能通过第三方维修中心进行维护。 油管频道 Phone Repair Guru 表示,虽然将屏幕替换成相同型号的 iPhone 13 屏幕在理论上可行的。但是在更换之后系统警告说,设备并没有使用正规的屏幕,而且 Face ID 也无法使用。 虽然更换屏幕是存在问题的,但是包括麦克风、接近传感器和环境光传感器依然可以更换的。尽管使用的是真正的苹果显示屏,但该通知基本上意味着在维修过程中有一个步骤必须执行,以使显示屏能够与特定的iPhone一起使用,而这一步骤是苹果授权的维修服务可以做的,但第三方不能做。 【责任编辑:未丽燕 TEL:(010)68476606】

优秀的个人博客,低调大师

官方发话了!5G网络全面覆盖太遥远,还要五到八年才能实现

中国的4G网络在全球虽然算不上第一,但考虑到中国面积大人口多,如今的覆盖率、速度能够达到这种水平,已经算得上是全球比较先进的4G覆盖大国了,目前中国4G网络覆盖率超过了95%,至少对比起土地面积差不多的美国来说已经是完胜了。 不过中国的用户现在关心的并不是4G网络覆盖率,而是火爆的5G网络,毕竟现在的5G手机用户正在飞速增长中。经历了去年下半年的4G到5G缓慢过渡期,今年开始5G手机正在以前所未有的速度普及,除了厂商们的上新以外,用户的换新也显得非常积极。 信通院公布的一份数据显示,仅4月份国内的5G手机出货量就多达1638.2万台,占手机出货量的39.3%,今年1月至4月份期间,国内5G手机累计出货3044.1万台,占手机出货量的33.6%,同时5G手机上新数量也达到了65款,意味着5G普及大潮正式拉开。 既然5G手机的用户量提升了,5G套餐用户数又是怎样一番表现呢?此前工信部公布了截至3月底的数据,截至3月底5G套餐用户规模超过了5000万,分别是中国移动的3172.3万、中国电信的1661万,中国联通未公布,可能只有几百万。 5月17日,中国移动在生态合作伙伴大会上又公布了取得的阶段性成绩,建成5G基站12.4万个,覆盖56个城市,5G套餐用户已经突破5000万户,同时中国移动还强调,会力争在2020年底前实现5G用户突破1亿。 5G手机和5G套餐用户数双双上涨,对于用户而言,最关心的无疑就是5G网络的覆盖了,因为想要用户买单5G网络还得靠服务说话,目前很多地区根本就无法连接到5G网络,原因自然是和5G基站数量有关,现阶段全国大概只有20多万个5G基站。 作为对比,全国目前有4G基站大概500万个,所有才有如此覆盖率。不过最近全国政协委员、中国联通产品中心总经理张云勇在受访时表示,5G网络建设的规模要比4G大2-3倍,成本也高,如果要达到4G旗鼓相当的网络覆盖,需要1000万台,至少要投入2万亿人民币。 同时他还透露,去年三大运营商共建立了十几万5G基站,今年总共会再建设100万个5G基站,如果以这样的速度发展下去,5G想要基本满足大城市深度覆盖,地市重点场景覆盖,企业无缝接入等基本覆盖需求,大概还需要等个五到八年。 从这点可以看出,虽然现在市面上的5G手机看起来很美好,三大运营商拿出来的5G套餐用户数也很漂亮,但5G网络全面覆盖依然离我们非常遥远,可能随着最近5G新基建的兴起,建设的节奏会快一些,但5G基站依然面临着众多问题需要解决。 一个是上面提到的成本问题,至少要投入2万亿人民币,还有一个就是功耗问题。此前有报道称,5G基站功耗是4G基站的3-4倍,可见电费成本也是相当大的支出,因此5G建设对于运营商而言,提出了巨大的挑战!

优秀的个人博客,低调大师

高可用性(High Availability):Redis 哨兵是Redis官方的高可用性解决方案

Redis主从复制的问题 Redis主从复制可将主节点数据同步给从节点,从节点此时有两个作用: 一旦主节点宕机,从节点作为主节点的备份可以随时顶上来。 扩展主节点的读能力,分担主节点读压力。 主从复制同时存在以下几个问题: 一旦主节点宕机,从节点晋升成主节点,同时需要修改应用方的主节点地址,还需要命令所有从节点去复制新的主节点,整个过程需要人工干预。 主节点的写能力受到单机的限制。 主节点的存储能力受到单机的限制。 原生复制的弊端在早期的版本中也会比较突出,比如:Redis复制中断后,从节点会发起psync。此时如果同步不成功,则会进行全量同步,主库执行全量备份的同时,可能会造成毫秒或秒级的卡顿。 Redis 的 哨兵(Sentinel)深入探究 Redis Sentinel的架构 Redis的哨兵机制就是解决我们以上主从复制存在缺陷(选举问题),保证我们的Redis高可用,实现自动化故障发现与故障转移。 该系统执行以下三个任务: 监控:哨兵会不断检查你的主服务器和从服务器是否运作正常。 提醒:当被监控的某个Redis服务器出现问题时,哨兵可以通过API给程序员发送通知 自动故障转移:主服务器宕机,哨兵会开始一次自动故障转移操作,升级一个从服务器为主服务器,并让其他从服务器改为复制新的主服务器. 配置 Sentinel Redis 源码中包含了一个名为 sentinel.conf 的文件, 这个文件是一个带有详细注释的 Sentinel 配置文件示例。 运行一个 Sentinel 所需的最少配置如下所示: 1)sentinel monitor mymaster 192.168.10.202 6379 2 Sentine监听的maste地址,第一个参数是给master起的名字,第二个参数为master IP,第三个为master端口,第四个为当该master挂了的时候,若想将该master判为失效, 在Sentine集群中必须至少2个Sentine同意才行,只要该数量不达标,则就不会发生故障迁移。 ----------------------------------------------------------------------------------------------- 2)sentinel down-after-milliseconds mymaster 30000 表示master被当前sentinel实例认定为失效的间隔时间,在这段时间内一直没有给Sentine返回有效信息,则认定该master主观下线。 只有在足够数量的 Sentinel 都将一个服务器标记为主观下线之后, 服务器才会被标记为客观下线,将服务器标记为客观下线所需的 Sentinel 数量由对主服务器的配置决定。 ----------------------------------------------------------------------------------------------- 3)sentinel parallel-syncs mymaster 2 当在执行故障转移时,设置几个slave同时进行切换master,该值越大,则可能就有越多的slave在切换master时不可用,可以将该值设置为1,即一个一个来,这样在某个 slave进行切换master同步数据时,其余的slave还能正常工作,以此保证每次只有一个从服务器处于不能处理命令请求的状态。 ----------------------------------------------------------------------------------------------- 4)sentinel can-failover mymasteryes 在sentinel检测到O_DOWN后,是否对这台redis启动failover机制 ----------------------------------------------------------------------------------------------- 5)sentinel auth-pass mymaster 20180408 设置sentinel连接的master和slave的密码,这个需要和redis.conf文件中设置的密码一样 ----------------------------------------------------------------------------------------------- 6)sentinel failover-timeout mymaster 180000 failover过期时间,当failover开始后,在此时间内仍然没有触发任何failover操作,当前sentinel将会认为此次failoer失败。 执行故障迁移超时时间,即在指定时间内没有大多数的sentinel 反馈master下线,该故障迁移计划则失效 ----------------------------------------------------------------------------------------------- 7)sentinel config-epoch mymaster 0 选项指定了在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步。这个数字越小, 完成故障转移所需的时间就越长。 ----------------------------------------------------------------------------------------------- 8)sentinel notification-script mymaster/var/redis/notify.sh 当failover时,可以指定一个"通知"脚本用来告知当前集群的情况。 脚本被允许执行的最大时间为60秒,如果超时,脚本将会被终止(KILL) ----------------------------------------------------------------------------------------------- 9)sentinel leader-epoch mymaster 0 同时一时间最多0个slave可同时更新配置,建议数字不要太大,以免影响正常对外提供服务。 主观下线和客观下线 主观下线:指的是单个 Sentinel 实例对服务器做出的下线判断。 客观下线:指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN主观下线判断。 Redis Sentinel的工作原理​​​​​​​ 1.每个 Sentinel 以每秒一次的频率向它所知的主服务器、从服务器以及其他 Sentinel 实例发送一个 PING 命令。 2.如果一个实例距离最后一次有效回复 PING 命令的时间超过指定的值, 那么这个实例会被 Sentinel 标记为主观下线。 3.正在监视这个主服务器的所有 Sentinel 要以每秒一次的频率确认主服务器的确进入了主观下线状态。 4.有足够数量的 Sentinel 在指定的时间范围内同意这一判断, 那么这个主服务器被标记为客观下线。 5.每个 Sentinel 会以每 10 秒一次的频率向它已知的所有主服务器和从服务器发送 INFO 命令。 当一个主服务器被 Sentinel 标记为客观下线时, Sentinel 向下线主服务器的所有从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。 6.Sentinel和其他Sentinel协商主节点的状态,如果主节点处于SDOWN状态,则投票自动选出新的主节点。将剩余的从节点指向新的主节点进行数据复制。 7.当没有足够数量的Sentinel同意主服务器下线时,主服务器的客观下线状态就会被移除。当主服务器重新向Sentinel的PING命令返回有效回复时,主服务器的主观下线状态就会被移除。 ​​​​​​​自动发现 Sentinel 和从服务器 一个 Sentinel 可以与其他多个 Sentinel 进行连接, 各个 Sentinel 之间可以互相检查对方的可用性, 并进行信息交换。 你无须为运行的每个 Sentinel 分别设置其他 Sentinel 的地址, 因为 Sentinel 可以通过发布与订阅功能来自动发现正在监视相同主服务器的其他 Sentinel。 每个 Sentinel 会以每两秒一次的频率, 通过发布与订阅功能, 向被它监视的所有主服务器和从服务器的频道发送一条信息, 信息中包含了 Sentinel 的 IP 地址、端口号和运行 ID (runid)。 每个 Sentinel 都订阅了被它监视的所有主服务器和从服务器的频道, 查找之前未出现过的 sentinel 。 当一个 Sentinel 发现一个新的 Sentinel 时, 它会将新的 Sentinel 添加到一个列表中。 Sentinel 发送的信息中还包括完整的主服务器当前配置。 如果一个 Sentinel 包含的主服务器配置比另一个 Sentinel 发送的配置要旧, 那么这个 Sentinel 会立即升级到新配置上。 在将一个新 Sentinel 添加到监视主服务器的列表上面之前, Sentinel 会先检查列表中是否已经包含了和要添加的 Sentinel 拥有相同运行 ID 或者相同地址(包括 IP 地址和端口号)的 Sentinel , 如果是的话, Sentinel 会先移除列表中已有的那些拥有相同运行 ID 或者相同地址的 Sentinel , 然后再添加新 Sentinel 故障转移 一次故障转移操作由以下步骤组成: 发现主服务器已经进入客观下线状态。 对我们的当前纪元进行自增, 并尝试在这个纪元中当选。 如果当选失败, 那么在设定的故障迁移超时时间的两倍之后, 重新尝试当选。 如果当选成功, 那么执行以下步骤。 选出一个从服务器,并将它升级为主服务器。 向被选中的从服务器发送SLAVEOF NO ONE命令,让它转变为主服务器。 通过发布与订阅功能, 将更新后的配置传播给所有其他 Sentinel , 其他 Sentinel 对它们自己的配置进行更新。 向已下线主服务器的从服务器发送SLAVEOF命令, 让它们去复制新的主服务器。 当所有从服务器都已经开始复制新的主服务器时, 领头 Sentinel 终止这次故障迁移操作。 参考: https://redis.io/ https://www.cnblogs.com/bingshu/p/9776610.html

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册