19
4

为什么叫里氏替换原则?

里氏替换原则在SOLID这五个设计原则中是比较特殊的存在:如果违反了里氏替换原则,不只是降低软件设计的优雅性,很可能会导致Bug。

里氏替换原则译自Liskov substitution principle。Liskov是一位计算机科学家,也就是Barbara Liskov,麻省理工学院教授,也是美国第一个计算机科学女博士,师从图灵奖得主John McCarthy教授,人工智能概念的提出者。

Robert Martin在《敏捷软件开发:原则、模式与实践》一书中对原论文的解读:子类型(subtype)必须能够替换掉他们的基类型(base type)。这个是更简明的一种表述。

违背 LSP 原则的一个简单示例

一个非常明显地违背 LSP原则的示例就是使用 RTTI(Run Time Type Identification)来根据对象类型选择函数执行。

void DrawShape(const Shape& s)
{
    if (typeid(s) == typeid(Square))
        DrawSquare(static_cast<Square&>(s));
    else if (typeid(s) == typeid(Circle))
        DrawCircle(static_cast<Circle&>(s));
}

正方形和长方形,违背原则的微妙之处

很多情况下对 LSP 原则的违背方式都十分微妙。设想在一个应用程序中使用了 Rectangle 类,描述如下:

public class Rectangle
  {
    private double _width;
    private double _height;

    public void SetWidth(double w) { _width = w; }
    public void SetHeight(double w) { _height = w; }
    public double GetWidth() { return _width; }
    public double GetHeight() { return _height; }
  }

违反里氏替换原则的危害

当我们违反了这一原则会带来有一些危害:反直觉。期望所有子类行为是一致的,但如果不一致可能需要文档记录,或者在代码跑失败后涨此知识;不可读。如果子类行为不一致,可能需要不同的逻辑分支来适配不同的行为,徒增代码复杂度;不可用。可能出错的地方终将会出错。

如何避免违反里氏替换原则

谈到如何避免,当然要基于里氏替换原则的定义,与期望行为一致的替换。

从行为出发来设计。在做抽象或设计时,不只是要从模型概念出发,还要从行为出发,比如一个经典的例子,正方形和长方形,从现实的概念中正方形是一个长方形,但是在计算其面积的行为上是不一致的。

基于契约设计。这个契约即是基类方法签名、功能描述、参数类型、返回值等。在派生类的实现时,时刻保持派生类与基类的契约不被破坏。

开放封闭原则(Open Closed Principle)是许多面向对象设计启示思想的核心。符合该原则的应用程序在可维护性、可重用性和鲁棒性等方面会表现的更好。里氏替换原则(Liskov Substitution Principle)则是实现 OCP 原则的重要方式。只有当衍生类能够完全替代它们的基类时,使用基类的函数才能够被安全的重用,然后衍生类也可以被放心的修改了。

19
4

Author: Stephen Watts

SOLID is a popular set of design principles that are used in object-oriented software development. SOLID is an acronym that stands for five key design principles: single responsibility principle, open-closed principle, Liskov substitution principle, interface segregation principle, and dependency inversion principle. All five are commonly used by software engineers and provide some important benefits for developers.

The SOLID principles were developed by Robert C. Martin in a 2000 essay, “Design Principles and Design Patterns,” although the acronym was coined later by Michael Feathers. In his essay, Martin acknowledged that successful software will change and develop. As it changes, it becomes increasingly complex. Without good design principles, Martin warns that software becomes rigid, fragile, immobile, and viscous. The SOLID principles were developed to combat these problematic design patterns.

The broad goal of the SOLID principles is to reduce dependencies so that engineers change one area of software without impacting others. Additionally, they’re intended to make designs easier to understand, maintain, and extend. Ultimately, using these design principles makes it easier for software engineers to avoid issues and to build adaptive, effective, and agile software.

While the principles come with many benefits, following the principles generally leads to writing longer and more complex code. This means that it can extend the design process and make development a little more difficult. However, this extra time and effort is well worth it because it makes software so much easier to maintain, test, and extend.

Following these principles is not a cure-all and won’t avoid design issues. That said, the principles have become popular because when followed correctly, they lead to better code for readability, maintainability, design patterns, and testability. In the current environment, all developers should know and utilize these principles.

Single Responsibility Principle

Robert Martin summarizes this principle well by mandating that, “a class should have one, and only one, reason to change.” Following this principle means that each class only does one thing and every class or module only has responsibility for one part of the software’s functionality. More simply, each class should solve only one problem.

Single responsibility principle is a relatively basic principle that most developers are already utilizing to build code. It can be applied to classes, software components, and microservices.

Utilizing this principle makes code easier to test and maintain, it makes software easier to implement, and it helps to avoid unanticipated side-effects of future changes.

To ensure that you’re following this principle in development, consider using an automated check on build to limit the scope of classes. This check is not a foolproof way to make sure that you’re following single responsibility principle, but it can be a good way to make sure that classes are not violating this principle.

Open-Closed Principle

The idea of open-closed principle is that existing, well-tested classes will need to be modified when something needs to be added. Yet, changing classes can lead to problems or bugs. Instead of changing the class, you simply want to extend it. With that goal in mind, Martin summarizes this principle, “You should be able to extend a class’s behavior without modifying it.”

Following this principle is essential for writing code that is easy to maintain and revise. Your class complies with this principle if it is:

Open for extension, meaning that the class’s behavior can be extended; and
Closed for modification
, meaning that the source code is set and cannot be changed.

At first glance, these two criteria seem to be inherently contradictory, but when you become more comfortable with it, you’ll see that it’s not as complicated as it seems. The way to comply with these principles and to make sure that your class is easily extendable without having to modify the code is through the use of abstractions. Using inheritance or interfaces that allow polymorphic substitutions is a common way to comply with this principle. Regardless of the method used, it’s important to follow this principle in order to write code that is maintainable and revisable.

Liskov Substitution Principle

Of the five SOLID principles, the Liskov Substitution Principle is perhaps the most difficult one to understand. Broadly, this principle simply requires that every derived class should be substitutable for its parent class. The principle is named for Barbara Liskov, who introduced this concept of behavioral subtyping in 1987. Liskov herself explains the principle by saying:

What is wanted here is something like the following substitution property: if for each object O1 of type S there is an object O2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when O1 is substituted for O2 then S is a subtype of T.

While this can be a difficult principle to internalize, in a lot of ways it’s simply an extension of open-closed principle, as it’s a way of ensuring that derived classes extend the base class without changing behavior.

Following this principle helps to avoid unexpected consequences of changes and avoids having to open a closed class in order to make changes. It leads to easy extensions of software, and, while it might slow down the development process, following this principle during development can avoid lots of issues during updates and extensions.

Interface Segregation Principle

The general idea of interface segregation principle is that it’s better to have a lot of smaller interfaces than a few bigger ones. Martin explains this principle by advising, “Make fine grained interfaces that are client-specific. Clients should not be forced to implement interfaces they do not use.”

For software engineers, this means that you don’t want to just start with an existing interface and add new methods. Instead, start by building a new interface and then let your class implement multiple interfaces as needed. Smaller interfaces mean that developers should have a preference for composition over inheritance and for decoupling over coupling. According to this principle, engineers should work to have many client-specific interfaces, avoiding the temptation of having one big, general-purpose interface.

Dependency Inversion Principle

This principle offers a way to decouple software modules. Simply put, dependency inversion principle means that developers should “depend on abstractions, not on concretions.” Martin further explains this principle by asserting that, “high level modules should not depend upon low level modules. Both should depend on abstractions.” Further, “abstractions should not depend on details. Details should depend upon abstractions.”

One popular way to comply with this principle is through the use of a dependency inversion pattern, although this method is not the only way to do so. Whatever method you choose to utilize, finding a way to utilize this principle will make your code more flexible, agile, and reusable.

Conclusion

Implementing SOLID design principles during development will lead to systems that are more maintainable, scalable, testable, and reusable. In the current environment, these principles are used globally by engineers. As a result, to create good code and to use design principles that are competitive while meeting industry standards, it’s essential to utilize these principles.

While implementing these principles can feel overwhelming at first, regularly working with them and understanding the differences between code that complies with the principles and code that does not will help to make good design processes easier and more efficient.

07
4

C#实现两个顺序链表的合并

0
归档:2023年4月分类:C#和.NET

最近开始刷leetcode,刷了一周时间,确实会上瘾,不过能巩固很多基础知识,包括算法和语言。我现在是架构与编码并行。今天遇到的是两个列表的合并,刚开始我自己写了一个笨方法来合并,虽然通过了测试,但是代码质量不高,于是研究了其他人写的代码。

//传入的两个链表本身是从小到大排序好
public ListNode MergeTwoLists(ListNode list1, ListNode list2)
{
    ListNode head = new ListNode(); //定义新的链表头
    ListNode current = head;//定义链表最新的next地址
    //遍历两个链表,任何一个完毕后就终止循环
    while (list1 != null && list2 != null)
    {
        if (list1.val < list2.val)
        {
            //如果list1的元素值小,则把当前list1赋予current.next,注意这里相当于head.next
            current.next = list1;
            list1 = list1.next;
        }
        else
        {
            current.next = list2;
            list2 = list2.next;
        }
        //将current变量指向current.next,相当于head.next,当进入下一次循环给current.next赋值的时候,相当于是给head.next.next赋值了,一直这么循环到最后
        current = current.next;
    }
    //head链表的next指向未循环完成的部分
    current.next = list1 == null ? list2 : list1;
    //返回合并后的链表
    return head.next;

}

我刚开始发懵了,居然没理解这部分代码,因为忽略了“每次循环之后current的引用指向已经发生变化”。

07
4

赋值和深复制、浅复制并不是一样的,含义是不一样的。赋值。指的是 “ 等号= ”。它相当于是给引用对象起一个别名。浅度复制和深度复制。指的是类实现 ICloneable接口,重写该接口的唯一方法。注意:不管是深度复制还是浅度复制,都是通过ICloneable接口去实现的。

值类型变量存储的是变量的值,直接储存在栈内存中。引用类型变量存储的是变量所在的内存地址,引用类型变量的实际数据存储于托管堆,变量本身仅仅是一个指向堆中实际数据的地址,存储于栈内存中,通常是四个字节。

值类型Value存储在线程堆栈中。引用类型Reference存储在托管堆上。

全局数据区:存放全局变量,静态数据,常量。代码区:存放所有的程序代码。栈区:存放为运行而分配的局部变量,参数,返回数据,返回地址等。堆区:即自由存储区

为了理解值类型变量和引用类型变量的内存分配模型,我们应先区分两种不同的内存区域——线程堆栈Thread Stack和托管堆Managed Heap。每一个正在运行的程序都对应着一个进程Process,在一个进程内部,可以有一个或多个线程Thread,每个线程都拥有一块“自留地”,成为线程堆栈,大小为1M,用于保存自身的一些数据,如函数中定义的局部变量、函数调用时传送的参数值等。现在我们可以解释第一句话——值类型存储在线程堆栈中,也就是说所有值类型的变量都是在线程堆栈中分配的。另一块内存区域称为堆Heap,在.NET这种托管环境下,堆由CLR(Common Language Runtime)管理,所以又称托管堆Managed Heap。例如使用new关键字创建类的对象实例时,分配给对象的内存单元就位于托管堆中。

1、赋值。赋值和深度复制,浅度复制完全是不同的概念,并没有什么关系,很多文章说赋值对于值类型是深度复制,对于引用类型是浅度复制,这种说法是不正确的,它的本质是在线程栈上产生一样的副本。

2、浅度复制。值类型成员独立,但是引用类型成员共享。

3、深度复制。值类型成员和引用类型成员都是独立的,即完完全全的一个全新的副本,称之为深度复制。

(1)String字符串对象是引用对象,但是很特殊,它表现的如值对象一样,即对它进行赋值,分割,合并,并不是对原有的字符串进行操作,而是返回一个新的字符串对象。但这其实是运算符重载的结果,将string实现为语义遵循一般的、直观的字符串规则。 String对象被分配在堆上,而不是栈上。

(2)Array数组对象是引用对象,在进行赋值的时候,实际上返回的是源对象的另一份引用而已;因此如果要对数组对象进行真正的复制(深拷贝),那么需要新建一份数组对象,然后将源数组的值逐一拷贝到目的对象中。

03
4

最近开始整理自己实际项目中用到的和可能用到的软件架构体系,我打算每种架构都好好写文章,并且根据自己的实际情况定制出相应的架构,我还希望把他编写成一个codesmith模板,可以实现自动化代码。前面7种是具体落地的软件架构,最后1种是架构设计思想。

1、极简数据库访问架构,这种架构非常适合编写一些小软件,里面只有一个SQLHelper文件,并且模仿三层架构,拥有DAL类和BLL类,最后加上一个程序入口Program。

2、简单三层架构,这种架构非常适合开发小型项目,web层就是MVC或者API,并且拥有BLL层和DAL层。

3、N层架构,这是一种适合大型项目的开发架构,但是更多是基于数据库构建的系统,没有基于DDD或者其他好的软件设计模式。

4、基于AutoFac、依赖注入和模板模式的多层架构。

5、基于ABP框架的架构,完全按照DDD设计模式开发,可能有些臃肿,但是非常适合大型软件和系统。

6、基于整洁架构的框架,完全按照DDD并且结合各种软件技术。

7、垂直切片架构,这是一种和传统分成架构完全不一样的架构,适合已经熟悉DDD和各种架构模式的成熟团队使用,并且适合开发微服务架构下的子系统。

8、基于清晰架构的微服务软件架构设计思想,这不是具体的实现,而只是一种架构大局观,用于指导大型的软件架构设计。

24
3

许多年前,我们开始了一个新的长期项目,首先,我们基于洋葱架构构建了它的架构。在几个月内,这种风格开始显示出裂缝,我们从这种架构转向CQRS。随着转向CQRS,我们开始围绕垂直切片而不是层(无论是平面还是同心,它仍然是层)构建我们的架构。从那以后,在过去7到8年左右的时间里,围绕垂直切片架构构建应用程序和系统的所有方式一直是我们独有的方法,我无法想象回到分层架构方法的限制。

传统的分层/洋葱/清洁架构在其目标是单体的:

这种架构方式问题是实际上只适用于系统中的少数典型请求。此外,我倾向于看到这些架构严重拟合,严格遵守依赖关系管理规则。在实践中,我发现这些规则很少有用,并且你开始得到很多关于真正不应该被抽象的抽象(控制器必须与必须使用存储库的服务进行对话)。

相反,我想对我的系统采用量身定制的方法,我将每个请求视为如何处理其代码的独特用例。因为我的系统整齐地分解为“命令”请求和“查询”请求(HTTP-land中的GET与POST / PUT / DELETE),所以向垂直切片架构的移动使我使用了CQRS。

什么是“垂直切片架构”?在这种风格中,我的架构是围绕不同的具体请求功能而构建的,通过这种方法,我们的每个垂直切片都可以自行决定如何最好地满足请求:

(对于获取订单,直接使用ORM转换为DTO,对于订单细节使用原生SQL转换为DTO,对于发票,使用基于聚合根的事件溯源,取消订单使用存储过程,这是一种微服务风格)

在所谓正常的“n层”或六边形或任何架构中,通过垂直切片移除这些层障碍,并沿着变化轴聚合在一起:

在应用程序中添加或更改功能时,通常会在应用程序中涉及到许多不同的“层”。现在改为沿着切片垂直将这些功能聚合在一起。

最小化切片之间的耦合,并最大化切片内的聚合。

通过这种方法,大多数抽象都消失了,我们不需要任何类型的“共享”层抽象,如存储库,服务,控制器。有时我们仍需要这些工具,但我们将交叉切片逻辑共享保持在最低限度。

通过这种方法,我们的每个垂直切片都可以自行决定如何最好地满足请求。

“企业架构模式”一书中的旧域逻辑模式不再需要成为应用程序范围内的选择。相反,我们可以从简单的(事务脚本)开始,并简单地重构从我们在业务逻辑中看到的代码气味中出现的模式。新功能只添加代码,您不会更改共享代码并担心副作用。非常自由!

但是,这种方法有一些缺点,因为它确实假设您的团队了解代码气味和重构。如果您的团队不理解“服务”在将逻辑推送到领域时自己却做得太多相关业务逻辑事情,那么这种模式可能不适合您。(服务类似餐厅服务员,服务员不应该做决定,只是协调者,当然如果你想退菜,服务员会决定说:不能退,菜已经烧了。)

如果您的团队确实理解了重构,并且能够识别何时将复杂的逻辑推入域,进入DDD服务应该是什么,并且熟悉其他Fowler/Kerievsky重构技术,那么您会发现这种架构风格能够远远超过传统的分层/同心架构。

来源:https://www.jdon.com/53095.html

24
3

这段时间一直在重构公司的项目代码,所以非常关注软件架构的设计,回顾我的架构接触史:第一次接触C#和.NET的时候就知道了三层架构和多层架构,当时被DAL和BLL等分层震惊了,原来软件设计还有那么多门道;毕业之后工作一直在使用多层架构,接着是MVC架构的出现,这种快速开发web站点的架构横扫整个行业;后来进入大厂工作,接触到了DDD;最近几年微服务架构又风声水起。

整体下来我最开始用了三层架构、然后是多层架构,接着是MVC架构配合多层架构,最后到了微服务架构,现在深刻理解那句话:没有最好的架构,只有最合适的架构。

网上看到有个人总结传统的架构演变之路:

三层架构:这是最简单、同时也是最成熟的软件应用程序架构,它将应用程序组织到三个逻辑和物理计算层中,包括表示层或用户界面、用于处理数据的应用(业务逻辑)程序层和用于存储和管理应用程序关联数据的数据层。大型项目中很少用,但是我还是会在一些小工具和小应用里运用。

多层架构:这种架构是三层架构的升级版,就是增加更多层对系统进行隔离,提高可扩展性和复用性,当然也增加了系统的复杂度。这是我刚毕业那家年用得最多的架构。

基于DDD的分层架构:采用领域驱动设计的思想设计的多层架构,以领域模型为核心、使用依赖注入和控制反转等技术来实现软件,对系统进行解耦,获得最大限度地可维护性和可扩展性。这是我现在在项目中用得最多的架构。

领域模型准确反映了业务语言,而传统数据对象除了简单setter/getter方法外,没有任何业务方法,即失血模型,那么DDD领域模型就是充血模型(业务方法定义在实体对象中)。首次清晰描述了领域驱动的分层实现并统一了业务语言。单一职责、低耦合、高内聚、业务内核沉淀。

六边形架构:让用户、程序、自动化测试和批处理脚本可以平等地驱动应用,让应用的开发和测试可以独立于其最终运行的设备和数据库。

左侧: 代表 UI 的适配器被称为主适配器,它们发起了对应用的一些操作,端口(应用层API)和它的具体实现(controller实现)都在应用内部。右侧: 表示和后端工具链接的适配器,被称为从适配器,它们只会对主适配器的操作作出响应,端口在应用内部(业务接口),具体实现(impl)在应用之外。

洋葱架构:在端口和适配器架构的基础上贯彻了将领域放在应用中心,将传达机制(UI)和系统使用的基础设施(ORM、搜索引擎、第三方 API...)放在外围的思路。洋葱架构在业务逻辑中加入了一些在“领域驱动设计”中被识别出来的层次。

围绕独立的对象模型构建应用。内层定义接口,外层实现接口。依赖的方向指向圆心。所有的应用代码可以独立于基础设施编译和运行。职责分离更彻底,高内聚低耦合。更好的可测试性和可维护性。

整洁架构:这套架构是站在巨人的肩膀上,把MVC、EBI、端口适配器、洋葱架构、DDD融会贯通,形成了一套落地实践方案。

清晰架构:融合 DDD、洋葱架构、整洁架构、CQRS等一系列架构的信息,这种架构很复杂,你可以根据实际情况来选择。

微服务架构:微服务架构的诞生是因为docker的兴起,因为可以更好地管理各种部署,所以可以将一个大型系统拆分部署到docker之中,每个小服务都能实现集群部署。我个人认为并非一开始就要上微服务架构,架构是不断演化的过程,哪怕是一开始就定位很大系统,可以先划分出基本的服务就行(比如登录系统、支付中心、内容服务、接口开放平台和后台系统),更细的划分可以随着业务的发展不断演化——当然前提是要一直保证代码的整洁和简洁、遵循基本的面向对象代码原则。

垂直切片架构:我是最近才接触到了这种架构,我们的项目最近用到了MediatR和AutoMapper,这两个开源项目的作者Bogard JIMMY BOGARD在自己的项目中实现了这种架构,通过反思洋葱架构和整洁架构等各种分层和抽象得出来的一种架构,作者不建议做太多的分层,因为各种以来会导致大量的问题,作者认为在微服务架构里面特别实用,当然代码也会准从DDD的设计原则。作者认为在所谓传统正常的“N层”或六边形或任何架构中,通过垂直切片移除这些层障碍,并沿着变化轴聚合在一起:

15
2

这两周来频繁刷机,更新系统又还原系统,弄懂了很多事情,写一篇日志总结,以便以后查询使用,也希望能够看到的人能避免踩坑,也许第三部分更有价值,可以直接跳过去看。

一、关于刷机

刷机有风险,可能会变砖。不过因为很多手机都留了最后一手:FASTBOOT,所以总能把手机就回来。 刷机的有不少的方法:卡刷、线刷、OTA。卡刷:一般来说卡刷简单的能够理解为在内存卡中刷机,就是进入Recovery中刷入系统;线刷:线刷事实上能够简单的理解为用USB线刷机;OTA:仅仅要在系统中在线升级就能够成功刷机。

刷机之前需要解锁,一般开放点的厂商都会给相应的解锁工具和权限,只要愿意折腾都可以完成解锁。其实解锁和刷机就是为了获得root权限,这样可以对系统做更大的定制,当然也降低手机的安全性。

刷机要根据自己的手机型号找到相应的刷机包(也就是ROM),要注意是卡刷还是线刷,卡刷就是拷贝到手机卡里刷机,线刷就是用电脑的工具通过手机数据线刷机。

小米的MIUI系统有大量的刷机包,我一直都喜欢用欧版,地址是:https://xiaomi.eu 内网地址是:https://sourceforge.net/projects/xiaomi-eu-multilang-miui-roms/

二、获得root权限

我用的是小米手机,有两种方法,这里从别的地方搬过来,我只把重要的步骤留下,并且指出可能的坑点。

1、通过替换boot.img方式安装Magisk获取Root权限指南

来源原文:https://miuiver.com/install-magisk-for-xiaomi/

现在获取手机 Root 主要通过安装 Magisk 实现,这篇文章将以新手视角介绍如何安装使用 Magisk。

准备工作:安装 Magisk 过程中需要用到 fastboot 命令,需要刷写手机 boot 或 Recovery 分区,请确保手机已完成 BL 解锁,不然无法进行。正常情况下不会丢数据,但是建议都先外置备份手机数据再操作。

下载Magisk 地址 https://github.com/topjohnwu/Magisk/releases (选择下载最新版,然后安装到手机)。

提取相应文件:查看手机上运行的系统版本是多少,下载对应系统版本刷机包,从里面提取相应文件(请见下面说明)。如果之前 Ramdisk 查询结果为“是”,请提取 boot.img 文件如果之前 Ramdisk 查询结果为“否”,请提取 recovery.img 文件。

文件提取方法:如果系统版本有线刷包,可以直接解压提取;如果系统版本只有卡刷包,需要从解压的 payload.bin 文件里提取(老机型卡刷包可以直接提取),将提取到的文件复制到手机上。

生成修补文件:手机打开 Magisk 软件,点击 Magisk 卡片中的“安装”按钮。点击“选择并修补一个文件”,选择之前提取到的 boot.img 或 recovery.img 文件,点击“开始”,然后等待生成修补文件。据 Magisk 文档指出,小米有个别机型 Ramdisk 结果可能不能准确检测。如果修补 recovery.img 文件失败,可以尝试用 boot.img 修补,后面安装也遵循 Ramdisk 结果为“是”的做法。将生成的修补文件复制到电脑上(修补文件默认保存在手机内部存储 Download 目录)。

刷写修补文件:将手机关机,长按音量下键+电源键进入 FASTBOOT 模式,用数据线连接到电脑。电脑打开存放修补文件的文件夹,按住键盘 Shift 键,同时鼠标右键点击文件夹空白处,在右键菜单点击“在此处打开 Powershell 窗口”,然后运行下面刷写命令(命令中的文件名请先自行修改)。如果之前修补 boot.img 文件请用这个命令:.\fastboot flash boot magisk_patched-25200_pU6ZV.img;如果之前修补 recovery.img。文件请用这个命令:.\fastboot flash recovery magisk_patched-25200_pU6ZV.img。刷写完成后用下面命令重启手机:.\fastboot reboot

提示:如果刷完后遇到反复重启进不去系统问题,可以尝试先用下面命令禁用启动验证(AVB/DM-Verity),然后重复上一步骤重新刷写修补文件(这个方法仅限具有单独 vbmeta 分区的机型使用), .\fastboot --disable-verity --disable-verification flash vbmeta vbmeta.img
命令中用到的 vbmeta.img 文件从刷机包内提取,方法和之前提取其它文件一样。

如果出现成砖或者解决不了的问题,只需用之前方法刷回从刷机包提取的原始文件boot.img。

2、小米手机刷 TWRP 方法

来源原文:https://miuiver.com/how-to-flash-twrp/

如果要刷第三方ROM,或者不能用MIUI内置卡刷功能的受限版本,唯一办法是通过安装第三方 Recovery来刷机。其中TWRP是最常使用的第三方 Recovery,官方适配了许多机型,也有不少民间修改版本。如果是发布超过一段时间的机型,通常用官方版本就可以。如果是新机,则很可能官方还没有适配,只能用第三方版。

官方和第三方版本区别是:前者不用担心安全问题,能稳定获得更新,后者则需自行判断作者可信度。第三方版本通常会自带移除 MIUI 启动验证,官方版需要手动解决这个问题(刷入 Magisk 解决),不然可能卡米无法进入系统。

最后,对于采用 A/B 分区的手机,由于后续更新系统会切换分区,这可能丢失 TWRP 安装。这种机型不建议安装 TWRP,而改为只在需要时启动它。

TWRP刷入步骤:先将手机与电脑连接以便安装好驱动(如果安装失败,可下载 MiFlash 再手动安装)。另外确保手机已完成 BL 解锁;电脑下载 Fastboot 工具(解压备用)和对应机型 TWRP(.img 后缀文件,将其放入前面解压的文件夹里,官方地址是:https://twrp.me/Devices/Xiaomi/ );将手机关机,长按开机键 + 音量下键进入 fastboot 模式,与电脑连接; 电脑打开刚才解压的 platform-tools 文件夹,按住 Shift 键,同时右键点击文件夹空白处,在右键菜单点击“在此处打开 Powershell 窗口”,运行下面命令刷入 TWRP(自行替换文件名)。

.\fastboot flash recovery twrp-3.7.0.img

注:如果不希望安装 TWRP,而只是临时使用 TWRP,运行下面命令(自行替换文件名)并忽略第 5 步骤。

.\fastboot boot twrp-3.7.0.img

如果运行命令后无法启动 TWRP,一直停在开机界面,可能是 TWRP 版本适配有问题,可尝试使用第三方版。

为防止重启手机 MIUI 自动替换回官方 Recovery,按住手机音量上键,同时电脑运行下面命令重启手机,直至进入 TWRP 界面。

.\fastboot reboot

在进入 TWRP 后,会询问“是否保持系统分区为只读”。这里滑动按钮以允许修改 ,不然不能禁止 MIUI 替换回官方 Recovery。滑动按钮允许 TWRP 修改 System 分区。

由于 MIUI 在启动时会检查 System 分区完整性,上一步 TWRP 修改了 System 分区,这时重启手机会无法启动系统(“卡米”问题),需要通过刷入 Magisk 移除启动验证,步骤如下:电脑下载 Magisk 安装包,将其复制到手机上(这时电脑可以识别手机 MTP 设备);TWRP 界面上点击“安装”,找到下载的是 Magisk 安装包,点击文件名,滑动按钮刷入;等待 Magisk 刷入完成,点击“重启系统”,这时就不会有“卡米”问题,重启手机也不会丢失 TWRP 安装。

3、MIUI欧洲版或者国际版刷本土模块

通过上面的两个步骤获得root权限并且装上Magisk之后,就可以刷入本土包了,比如钱包里的公交卡和门禁卡等。

只要刷入相应的模块就可以,这里有人专门做了相应的模块包,链接直达:https://blog.minamigo.moe/archives/184 ,作者在github创建了一个项目,主要是在为Miui Eu用户恢复Miui大陆版的功能。Miui Eu用户可通过使用此项目,同时获取Miui Eu的功能和Miui大陆版的功能。其实我只需要钱包,其他都没必要,原因你懂的。

这个包是为miui13和miui14专门写的程序,不过我试过miui12、miui11都能安装,至少钱包可以,其他没有验证过。

三、我踩过的坑

1、TWRP官方版本最新程序是3.7.0(截至到2023年2月15日),目前只支持android12,不支持android13,所以如果你更新的系统是13,那么你就可以放弃获得root或者按照magisk了。不过对于一些小米机型,有高人自己改写了twrp,但是要注意:这不是官方的,一点要慎重,地址是:https://www.123pan.com/s/qHhDVv-nuQJv 作者是活跃在xiaomi.eu上面的会员,可以点击这里:https://xiaomi.eu/community/members/gogocar62.295691/ 查看他的信息。

2、线刷的时候路径不要太长,否则就刷机不成功,因为很多安装包下载的时候文件名都特别长,把文件夹名称修改为短一点的就行,比如rom。

3、现在很多手机都采用 Virtual A/B 分区,这种情况如果刷入twrp,可能你会不小心切换到不同的分区,比如slot A和slot B,很多人一看就英文就乱点。可能你不小心就进入另外一个槽位,那么手机会无线进入fastboot,这时候需要在fastboot模式下运行:.\fastboot set_active b,也就是把B槽位弄成活跃槽位(也可能要把A槽设置为活跃槽位:.\fastboot set_active a,具体看系统安装在哪里),然后重启手机到system。

4、刷机包ROM必须要对应自己的硬件,可以通过官方名,比如MI12,或者MI12 Lite之类的查找,不过一般都会有一个英文代号,刷机包不对肯定搞不定,包括获取boot.img,别抱侥幸心理,系统都是针对硬件定制开发,多一行代码都没法运行。

5、这是最后一条:必须备份数据!必须备份数据!!必须备份数据!!!哪怕你觉得自己很熟悉,甚至自己曾经走过同样的路径,总会有意想不到的事情发生。

公告栏

欢迎大家来到我的博客,我是dodoro,希望我的博客能给你带来帮助。