Thursday, February 11, 2021

Makefile - 變數使用

 變數宣告時要使用 = 或 := 給予初始值 (注意兩者在代換時稍有不同),如 obj = hello.o foo.o,取用時寫成 

(obj){obj}。如果我們想定義一些比較類似的文件,可以使用 Unix-like 的 * , ? 和 ~ 。

一個範例如下:


在一些 make 中常使用自動化變數來簡寫規則(讓 makefile 難讀):

  • $@:目前的目標項目名稱。
  • $<:代表目前的相依性項目。
  • $*:代表目前的相依性項目,但不含副檔名。
  • $?:代表需要重建(被修改)的相依性項目。

另外在 makefile 規則中所用的萬用配對字元是% ,因此一個使用各種變數代換技巧的 makefile 可能如下例所示:

CC:=gcc
exe:=main
obj:=main.o a.o b.o c.o

all:$(obj)
 $(CC) -o $(exe) $(obj)  
%.o:%.c
 $(CC) -c $^ -o $@

.PHONY:clean
clean:
 rm -rf $(obj) $(exe)



Makefile -. 顯式規則

 最重要的是 Makefile 規則,其基本結構如下:

  • 目標 (Target) : 一個目標檔,可以是 Object 檔,也可以是執行檔,還可以是一個標籤。
  • 依賴 (Dependency, Prerequisites) : 要產生目標檔 (target) 所依賴哪些檔。
  • 命令 (Command) : 建立專案時需要執行的 shell 命令。命令部分的每行的縮進必須要使用 Tab 鍵而不能使用多個空格。

#用「井」號表明注釋。
target(要生成的文件): dependencies(被依賴的文件)
#命令前面用的是「tab」而非空格。誤用空格是初學者容易犯的錯誤。
        命令1
        命令2
        命令3
          .
          .
          .
        命令n
#可以使用「\」表示續行。注意,「\」之後不能有空格。

像 clean 這種沒有被第一個目標文件直接或間接關聯,那麼它後面所定義的命令將不會被自動執行,不過我們可以顯式要求 make 執行。即 make clean。

.PHONY 會將目標設成假目標,使 make 目錄下沒有目標檔案或目標檔案為最新時,仍可執行 make <target>。.PHONY 寫法也可以讓程式設計師知道哪些工作目標不是針對檔案,增加可讀性。

Make 預設的假工作目標有 all, install, clean, distclean, TAGS, info 和 check。

一個常用的情況是 make clean,因為 clean 標籤下的 rm 命令並不產生 clean 文件:

.PHONY: clean
clean:
 rm *.o

Wednesday, February 10, 2021

Makefile 簡介

在軟體開發中,make 是一個工具程式,經由讀取一個叫做 makefile 的檔案,自動化建構軟體。

編譯小型程式可用簡單的命令編譯或 shell script 編譯,但當程式很大且包含大量標頭檔和函式庫時,就需要使用 makefile。


makefile 會將程式分成好幾個模組,根據裡面的

1. 目標 (target)

2. 規則 (rule) 

3. 檔案的修改時間進行判斷哪些需要重新編譯,

可以省去大量重複編譯的時間,這在大型程式中尤為有用。

Make 的工作流程

一般來說,當我們輸入 make 命令,會執行以下內容:

1. make 會在當前目錄下按順序找尋文件名為 GNUmakefile、makefile 或 Makefile 的文件。

2. 在 makefile 文件中的找到第一個目標文件(target),並把這個文件作為最終的目標文件。

3. 如果沒找到或目標文件所依賴的文件,或修改時間要比目標文件新,則 make 將執行後面所定義的命令來生成這個文件,如此遞迴下去找到文件彼此的依賴關係,直到最終編譯出第一個目標文件。

如果最後被依賴的文件找不到,那麼 make 就會直接退出並報錯,而對於所定義的命令的錯誤或是編譯不成功,make 不予理會。

Makefile 主要內容

Makefile 裡主要包含了五個東西:顯式規則、隱式規則、變量定義、文件指示和註釋。

  • 顯式規則顯式規則表示如何生成一個或多個目標文件。
  • 隱式規則:比較簡略地書寫 Makefile 規則,例如規則中有 .o 文件,make 會自動的把 .c 文件也加入依賴關係中。
  • 變數定義類似 C 語言中的 define,定義的變數都會置換到引用位置上。
  • 文件指示:
    • 類似 C 語言中的 include,一個 Makefile 中引用另一個 Makefile,如 include makefile.inc。
    • 類似 C 語言中的 預編譯 #if,根據某些情況指定 Makefile 中的有效部分。
  • 註釋與換行:Makefile 中只有行註釋,用 # 符號;換行則是使用 \ 符號 。

Tuesday, February 9, 2021

Android Command Pattern

 假設現在有三個按鈕, 分別處理三件不相同的事情,

你會怎麼寫?

if(id == event1){
    //處理事件一
} else if(id == event2){
   //處理事件二
} else if(id == event3){
   //處理事件三
}

看起來似乎很直覺對吧?
但是想想未來越來越多事件,
我們的if…else就越來越長,
這樣的設計看起來就是很糟糕XD
另外萬一中間修改一些邏輯判斷,
整段程式碼可能造成邊際效應, 進而產生負面影響。

這邊可以看到處理事件跟事件本身是緊緊綁住的,
所以造成只要發生變化, 則必須整塊程式碼進行大規模的變動,
產生bug的機率就大幅提升。

要改變這種情況, 則必須把事件跟事件處理鬆綁,
該怎麼做呢?
這時候我們就可以使用命令模式來解決這個問題。

1.定义

将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录日志,可以提供命令的撤销和恢复功能。

2.介绍

  • 命令模式属于行为型模式。
  • 我们遇到最常见的命令模式就是关机操作了,我们只需点击一下关机按钮就可以了,至于计算机是如何关机的,我们不需要关心其实现细节。

5. 应用场景

  • 需要对行为进行记录,撤销,重做,事务处理时。
  • 对于大多数请求——响应模式的功能,比较适合使用命令模式。

6. 优点

  • 调用者与接受者之间的解藕。
  • 易于扩展,扩展命令只需新增具体命令类即可,符合开放封闭原则。

7. 缺点

  • 过多的命令会造成过多的类。

8. Android中的源码分析

8.1 线程类

实际上Thread的使用就是一个简单的命令模式,先看下Thread的使用:

==========================================================

        new Thread(new Runnable() {
            @Override
            public void run() {
                //doSomeThing
            }
        }).start();
==========================================================

Threadstart()方法即命令的调用者,同时Thread的内部会调用Runnablerun(),这里Thread又充当了具体的命令角色,最后的Runnable则是接受者了,负责最后的功能处理。

8.2 Handler

另一个比较典型的常用到命令模式就是 Handler了,这里就不贴代码了,简单分析下各个类的角色:

  • 接受者:Handler,执行消息的处理操作。
  • 调用者:Looper,调用消息的的处理方法。
  • 命令角色:Message,消息类。
很好解釋

Monday, February 8, 2021

Makefile Index

Makefile 簡介

example

GNU make

CMakeLists

Reference:

https://jasonblog.github.io/note/gunmake/hao_yong_makefile.html

Android Interpreter Pattern

1.定义

给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。

2.介绍

  • 解释器模式属于行为型模式。
  • 解释器模式提供了一种解释语言的语法或表达式的方式。
  • 解释器模式实际开发中很少用到。

5. 应用场景

  • 简单的语法需要解释时,如解释一个sql语句。
  • 一些重复发生的问题,比如加减乘除四则运算,但是公式每次都不同,有时是a+b-cd,有时是ab+c-d等,公式千变万化,但是都是由加减乘除四个非终结符来连接的,这时我们就可以使用解释器模式。

6. 优点

  • 灵活的扩展性,想扩展语法规则时只需新增新的解释器就可以了。如上面的例子中,想增加乘除法,只想增加相应的解释类,并增加相应的表达式解释操作即可。

7. 缺点

  • 每一个文法都至少对应一个解释器,会产生大量的类,难于维护。
  • 解释器模式由于大量使用循环和递归,需要考虑效率的问题,而且调试也不方便。
  • 对于复杂的文法,构建其抽象语法树会显得异常繁琐。
  • 所以不推荐在重要的模块中使用解释器模式,维护困难。

8. Android中的源码分析

对于AndroidManifest.xml这个文件,我们是相当熟悉。实际上AndroidManifest.xml是由PackageManagerService使用了PackageParser这个类来解释的,这里面就用到了解释器模式。对于AndroidManifest.xml中的每一个标签,都有对应的类去保存相应的信息。

8.1 PackageParser的parseBaseApkCommon方法

基于Android 27的源码,不同版本的源码方法名可能不一样。

=======================================================================
    private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
                                       XmlResourceParser parser, int flags, String[] outError) 
            throws XmlPullParserException, IOException {
        //其他代码略
        if (tagName.equals(TAG_APPLICATION)) {
            //其他代码略
            if (!parseBaseApplication(pkg, res, parser, flags, outError)) {//解释application标签
                return null;
            }
        } else if (tagName.equals(TAG_OVERLAY)) {
            //其他代码略
        } else if (tagName.equals(TAG_KEY_SETS)) {
            if (!parseKeySets(pkg, res, parser, outError)) {
                return null;
            }
        } else if (tagName.equals(TAG_PERMISSION_GROUP)) {
            if (!parsePermissionGroup(pkg, flags, res, parser, outError)) {
                return null;
            }
        } else if (tagName.equals(TAG_PERMISSION)) {
            if (!parsePermission(pkg, res, parser, outError)) {
                return null;
            }
        } else if (tagName.equals(TAG_PERMISSION_TREE)) {
            if (!parsePermissionTree(pkg, res, parser, outError)) {
                return null;
            }
        } else if (tagName.equals(TAG_USES_PERMISSION)) {
            if (!parseUsesPermission(pkg, res, parser)) {
                return null;
            }
        } else if (tagName.equals(TAG_USES_PERMISSION_SDK_M)
                || tagName.equals(TAG_USES_PERMISSION_SDK_23)) {
            if (!parseUsesPermission(pkg, res, parser)) {
                return null;
            }
        } else if (tagName.equals(TAG_USES_CONFIGURATION)) {
            //其他代码略
        } else if (tagName.equals(TAG_USES_FEATURE)) {
            //其他代码略
        } else if (tagName.equals(TAG_FEATURE_GROUP)) {
            //其他代码略
        } else if (tagName.equals(TAG_USES_SDK)) {
            //其他代码略
        } else if (tagName.equals(TAG_SUPPORT_SCREENS)) {
            //其他代码略
        } else if (tagName.equals(TAG_PROTECTED_BROADCAST)) {
            //其他代码略
        } else if (tagName.equals(TAG_INSTRUMENTATION)) {
            //其他代码略
        } else if (tagName.equals(TAG_ORIGINAL_PACKAGE)) {
            //其他代码略
        } else if (tagName.equals(TAG_ADOPT_PERMISSIONS)) {
            //其他代码略
        } else if (tagName.equals(TAG_USES_GL_TEXTURE)) {
            //其他代码略
        } else if (tagName.equals(TAG_COMPATIBLE_SCREENS)) {
            //其他代码略
        } else if (tagName.equals(TAG_SUPPORTS_INPUT)) {//
            //其他代码略
        } else if (tagName.equals(TAG_EAT_COMMENT)) {
            //其他代码略
        } else if (tagName.equals(TAG_PACKAGE)) {
            //其他代码略
        } else if (tagName.equals(TAG_RESTRICT_UPDATE)) {
            //其他代码略
        } else if (RIGID_PARSER) {
            //其他代码略
        } else {
            //其他代码略
        }
    }
=======================================================================

从上面代码可以看到,就是对各个标签的内容进行解释。我们再来看看parseBaseApplication这个方法,这个是对Application进行解释。

8.2 parseBaseApplication方法

=======================================================================
    private boolean parseBaseApplication(Package owner, Resources res,
                                         XmlResourceParser parser, int flags, String[] outError)
            throws XmlPullParserException, IOException {

        //其他代码略
        String tagName = parser.getName();
        if (tagName.equals("activity")) {//解释activity
            Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,
                    owner.baseHardwareAccelerated);
            //其他代码略

        } else if (tagName.equals("receiver")) {//解释receiver
            Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
                    true, false);
            //其他代码略

        } else if (tagName.equals("service")) {//解释service
            Service s = parseService(owner, res, parser, flags, outError, cachedArgs);
            //其他代码略

        } else if (tagName.equals("provider")) {//解释provider
            Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
            //其他代码略

        } else if (tagName.equals("activity-alias")) {
            Activity a = parseActivityAlias(owner, res, parser, flags, outError, cachedArgs);
            //其他代码略

        } else if (parser.getName().equals("meta-data")) {
            //其他代码略
        } else if (tagName.equals("static-library")) {
            //其他代码略
        } else if (tagName.equals("library")) {
            //其他代码略

        } else if (tagName.equals("uses-static-library")) {
            if (!parseUsesStaticLibrary(owner, res, parser, outError)) {
                return false;
            }

        } else if (tagName.equals("uses-library")) {
            //其他代码略

        } else if (tagName.equals("uses-package")) {
            //其他代码略
        } else {
            //其他代码略
        }
        //其他代码略

        return true;
    }
=======================================================================
可以看到,上面有对activityreceiverservice等标签的解释,activity的具体解释在parseActivity这个方法里面,有兴趣的可以自行去看下,这里就不细说了,同时可以看到receiver也是在parseActivity这个方法中解释。

Reference:
https://www.jianshu.com/p/234a156b0210

Android Mediator Pattern

 

1.定义

用一个中介者对象来封装一系列的对象交互。中介者使得各对象不需要显式地相互引用,从而使其松散耦合,而且可以独立地改变它们之间的交互。

2.介绍

  • 中介者模式属于行为型模式。
  • 中介者模式也称为调解者模式或者调停者模式。
  • 当程序存在大量的类时,多个对象之间存在着依赖的关系,呈现出网状结构,那么程序的可读性和可维护性就变差了,并且修改一个类需要牵涉到其他类,不符合开闭原则。


5. 应用场景

  • 在程序中,如果类的依赖关系过于复杂,呈现网状的结构,可以使用中介者模式对其进行解耦。

6. 优点

  • 降低类的关系复杂度,将多对多转化成一对多,实现解耦。
  • 符合迪米特原则,依赖的类最少。

7. 缺点

  • 同事类越多,中介者的逻辑就越复杂,会变得越难维护。
  • 如果本来类的依赖关系不复杂,但是使用了中介者会使原来不复杂的逻辑变得复杂。因此需要根据实际情况去考虑,不要滥用中介者。

8. Android中的源码分析

Android中的锁屏功能就用到了中介者模式,KeyguardService(锁屏服务)通过KeyguardViewMediator(锁屏中介者)来协调各种Manager的状态以达到锁屏的功能。这里KeyguardService和各种Manager等等都充当了同事的角色。

8.1 KeyguardService的源码

===================================================================

    public class KeyguardService extends Service {//锁屏服务,同事角色

        private KeyguardViewMediator mKeyguardViewMediator;//锁屏中介者

        @Override
        public void onCreate() {
            ((SystemUIApplication) getApplication()).startServicesIfNeeded();
            //初始化中介者
            mKeyguardViewMediator = ((SystemUIApplication) getApplication()).getComponent(KeyguardViewMediator.class);
        }

        private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() {

            @Override // Binder interface
            public void addStateMonitorCallback(IKeyguardStateCallback callback) {
                checkPermission();

                mKeyguardViewMediator.addStateMonitorCallback(callback);//调用中介者的接口
            }

            @Override // Binder interface
            public void verifyUnlock(IKeyguardExitCallback callback) {
                checkPermission();

                mKeyguardViewMediator.verifyUnlock(callback);//调用中介者的接口
            }
            
            //其他代码略
            
        };
    }

===================================================================

8.2 KeyguardViewMediator的源码


===================================================================

    public class KeyguardViewMediator extends SystemUI {//锁屏中介者
        //各种Manager
        private AlarmManager mAlarmManager;
        private AudioManager mAudioManager;
        private StatusBarManager mStatusBarManager;
        private PowerManager mPM;
        private IWindowManager mWM;
        private TrustManager mTrustManager;
        private SearchManager mSearchManager;
        private PowerManager.WakeLock mShowKeyguardWakeLock;
        private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;

        //其他代码略

        private void playSound(int soundId) {//通过AudioManager去播放声音
            if (soundId == 0) return;
            final ContentResolver cr = mContext.getContentResolver();
            if (Settings.System.getInt(cr, Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) == 1) {

                mLockSounds.stop(mLockSoundStreamId);
                // Init mAudioManager
                if (mAudioManager == null) {
                    mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
                    if (mAudioManager == null) return;
                    mUiSoundsStreamType = mAudioManager.getUiSoundsStreamType();
                }
                // If the stream is muted, don't play the sound
                if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;

                mLockSoundStreamId = mLockSounds.play(soundId,
                        mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
            }
        }
        
    }
===================================================================

KeyguardViewMediator中通过playSound方法能够协调AudioManager去控制声音的播放等等,其他Manager同理。

n8n index

 【n8n免費本地端部署】Windows版|程式安裝x指令大補帖  【一鍵安裝 n8n】圖文教學,獲得無限額度自動化工具&限時免費升級企業版功能