Sunday, January 24, 2021

TV Audio

S/PDIF 輸出 SPEAKER (只有一個黃色插孔)


PCM   直接轉成幾聲道,然後輸出聲音


RAW   直接輸出聲音,再轉成幾聲道 (Amplier 會出現 杜比)


Sound -> Advanced  -> Audio output format -> Multichannel (RAW) (將聲音拿到 Amplier 解,輸出 5.1 聲道)  (Amplier 會出現 杜比)


Sound -> Advanced  -> Audio output format -> Stereo (PCM) (聲音在 main board 解,再輸出至 AMP,只有 2.1 聲道)


Android Decorator Pattern

 

定义

动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。

介绍

  • 装饰者模式属于结构型模式。
  • 装饰者模式在生活中应用实际上也非常广泛,一如一间房,放上厨具,它就是厨房;放上床,就是卧室。
  • 通常我们扩展类的功能是通过继承的方式来实现,但是装饰者模式是通过组合的方式来实现,这是继承的替代方案之一。
角色说明:
  • Component(抽象组件):接口或者抽象类,被装饰的最原始的对象。具体组件与抽象装饰角色的父类。
  • ConcreteComponent(具体组件):实现抽象组件的接口。
  • Decorator(抽象装饰角色):一般是抽象类,抽象组件的子类,同时持有一个被装饰者的引用,用来调用被装饰者的方法;同时可以给被装饰者增加新的职责。
  • ConcreteDecorator(具体装饰类):抽象装饰角色的具体实现。

4.实现

就以装修房间为例子

4.1 创建抽象组件

这里是一个抽象房子类,定义一个装修的方法:

====================================================================
    public abstract class Room {
        public abstract void fitment();//装修方法
    }
====================================================================

4.2 创建具体组件
现在有一间新房子,已经装上了电:

====================================================================
    public class NewRoom extends Room {//继承Room
        @Override
        public void fitment() {
            System.out.println("这是一间新房:装上电");
        }
    }

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

4.3 创建抽象装饰角色

要为房子装修,定义抽象的房间装饰类:

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

     public abstract class RoomDecorator extends Room {//继承Room,拥有父类相同的方法
        private Room mRoom;//持有被装饰者的引用,这里是需要装修的房间
        public RoomDecorator(Room room) {
            this.mRoom = room;
        }
        @Override
        public void fitment() {
            mRoom.fitment();//调用被装饰者的方法
        }
    }

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

4.4 创建具体装饰类

我们要将房间装修成卧室和厨房,其具体实现是不同的:

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

    public class Bedroom extends RoomDecorator {//卧室类,继承自RoomDecorator
        public Bedroom(Room room) {
            super(room);
        }
        @Override
        public void fitment() {
            super.fitment();
            addBedding();
        }
        private void addBedding() {
            System.out.println("装修成卧室:添加卧具");
        }
    }
    public class Kitchen extends RoomDecorator {//厨房类,继承自RoomDecorator
        public Kitchen(Room room) {
            super(room);
        }
        @Override
        public void fitment() {
            super.fitment();
            addKitchenware();
        }
        private void addKitchenware() {
            System.out.println("装修成厨房:添加厨具");
        }
    }

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

4.5 客户端测试:
====================================================================
     public void test() {
        Room newRoom = new NewRoom();//有一间新房间
        RoomDecorator bedroom = new Bedroom(newRoom);
        bedroom.fitment();//装修成卧室
        RoomDecorator kitchen = new Kitchen(newRoom);
        kitchen.fitment();//装修成厨房
    }
====================================================================

输出结果:
====================================================================
这是一间新房:装上电
装修成卧室:添加卧具
这是一间新房:装上电
装修成厨房:添加厨具

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

5. 应用场景

  • 需要扩展一个类的功能,或给一个类增加附加功能时
  • 需要动态的给一个对象增加功能,这些功能可以再动态的撤销
  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。

6. 优点

  • 采用组合的方式,可以动态的扩展功能,同时也可以在运行时选择不同的装饰器,来实现不同的功能。
  • 有效避免了使用继承的方式扩展对象功能而带来的灵活性差,子类无限制扩张的问题。
  • 被装饰者与装饰者解偶,被装饰者可以不知道装饰者的存在,同时新增功能时原有代码也无需改变,符合开放封闭原则。

7. 缺点

  • 装饰层过多的话,维护起来比较困难。
  • 如果要修改抽象组件这个基类的话,后面的一些子类可能也需跟着修改,较容易出错。

8. Android中的源码分析

我们都知道ActivityServiceApplication等都是一个Context,这里面实际上就是通过装饰者模式来实现的。下面以startActivity()这个方法来简单分析一下。

8.1 Context类

Context实际上是个抽象类,里面定义了大量的抽象方法,其中就包含了startActivity()方法:

====================================================================
    public abstract class Context {//抽象类
        public abstract void startActivity(@RequiresPermission Intent intent);//抽象方法
        //其他代码略
    }  
====================================================================

8.2 ContextImpl类

Context类的具体实现实际上是在ContextImpl类,里面具体实现了startActivity()方法:

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

    class ContextImpl extends Context {
        @Override
        public void startActivity(Intent intent) {
            warnIfCallingFromSystemProcess();
            startActivity(intent, null);
        }
        @Override
        public void startActivity(Intent intent, Bundle options) {//具体实现原理这里就不分析了
            warnIfCallingFromSystemProcess();
            // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
            // generally not allowed, except if the caller specifies the task id the activity should
            // be launched in.
            if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                    && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
                throw new AndroidRuntimeException(
                        "Calling startActivity() from outside of an Activity "
                                + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                                + " Is this really what you want?");
            }
            mMainThread.getInstrumentation().execStartActivity(
                    getOuterContext(), mMainThread.getApplicationThread(), null,
                    (Activity) null, intent, -1, options);
        }
        //其他代码略
    }

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

8.3 ContextWrapper类

通常我们在ActivityService里面调用startActivity()方法,实际上是调用他们的父类ContextWrapper里面的startActivity()方法,我们先来看下ActivityService的继承关系:




可以看到ActivityService都是继承自ContextWrapper,再来看看ContextWrapper的代码:

====================================================================
    public class ContextWrapper extends Context {//Context包装类
        Context mBase;//持有Context引用

        public ContextWrapper(Context base) {//这里的base实际上就是ContextImpl
            mBase = base;
        }

        @Override
        public void startActivity(Intent intent) {
            mBase.startActivity(intent);//调用ContextImpl的startActivity()方法
        }

        //其他代码略
    }
====================================================================

8.4 总结

Context类在这里就充当了抽象组件的角色,ContextImpl类则是具体的组件,而ContextWrapper就是具体的装饰角色,通过扩展ContextWrapper增加不同的功能,就形成了ActivityService等子类。最后,放一张总的UML类图帮助理解:





Reference:https://www.jianshu.com/p/df1a96c5c046

Wednesday, January 20, 2021

Bad Smells in Code

Bloaters (程式碼已經成長得過於龐大,大於能夠妥善掌握發揮的幅度)

1. Long Function (過長函式)

2.  Large Class (過大類別)

3.  Long Parameter List (過長參數列)

4.  Data Clumps (資料泥團) 

2 個 classes 內有相同欄位、相同參數

 5. Primitive Obsession (基本型別偏執)

沒有將基本型別轉化成小物件,小物件如,Date class 

Tool Abusers (濫用物件導向設計,也就是說沒有完全發揮物件導向設計的特性)

6. Repeated Switch (重複的切換邏輯)

同樣的 switch 述句散佈於不同地點,添加一個新的 case 子句,必須找到所有 switch 述句並修改它們

7. Temporary Field (令人迷感的暫時欄位)

 某個 instance 變數僅為某個特定情勢而設

8. Alternative Classes with Different Interfaces (異曲同工的類別)

 兩個函式做同一件事,卻有著不同的署名式

9. Refused Bequest (被拒絕的遣贈)

 只繼承父類別某個抽象類別

Change Preventers (小部分的修訂會連動到大範圍的修訂) 

10. Divergent Change (發散式修改)

相同 class 一旦新增 A 功能要改 3 個函式,一旦新增 B 功能要改 4 個函式

11. Shotgun Surgery (霰彈式修改)

 如果每遇到新增功能,需要修改散佈四處的 classes 程式碼

 12. Parallel Inheritance Hierarchies (平行繼承體系)

 某個 class 增加 subclass,另一個 class 也相同必須增加 subclass

Dispensables (程式碼中多餘的部分,應該要被移出 source code) 

13. Duplicate Code (重複的程式碼)

14.  Lazy Class (冗員類別)

15. Speculative Generality (夸夸其談未來性)

 預留了太多未來可能會用到的擴充點

16.  Data Class (純稚的資料類別)

 沒有將資料欄位封裝起來

 17. Dead Code (無作用的程式碼)

 The Couplers (Couplers 說明程式碼之間存在強烈相依性、高度的耦合情形)

18. Feature Envy (依戀情結)

 某個 class 常呼叫其它 class 的函式

19. Message Chains (過度耦合的訊息鏈)

 當用戶向一個物件索求 (request) 另一個物件,然後再向後者索求另一個物件…

20. Middle Man (中間轉手人)

 某個 class 介面有一半的函式都委託給其他 class,這樣就是過度運用

 21. Inappropriate Intimacy (狎暱關係)

 兩個 classes 過於親密,花費太多時間去探究彼此的資料

其它 7 個 code smells

22. Comments (過多的註釋)

23. Global Data (全域資料) 

24. Mutable Data (可變資料)

25. Mysterious Name (神秘的名稱)

26. Incomplete Library Class (不完美程式庫類別)

27. Loop (迴圈)

 first-class function

28. Insider Trading (內幕交易)

 模組間過度交換資料會導致更多耦合

Refactoring index

Introduction

Bad Smells in Code

Code Deodorant

Reference:

http://mikamantyla.eu/BadCodeSmellsTaxonomy.html

Monday, January 18, 2021

Windows Index

Windows 網芳 net use command 指令

Win10 無法存取網路位址分享的資料夾 0x80004005 錯誤

Ping TCP port

Windows下用cmd命令netstat查看系統端口(PORT)使用情況和刪除對應程式

Win+G 內鍵錄影

Android Flyweight Pattern

 

試想一個情節:公司裡每個人都會有員工資訊,也都會有名片。假設名片是個類別時,對於每個員工,都需要 new 出名片的實體,而實際上名片裡也只是內容不同而已。當員工越多時,就需要 new 出越多名片物件,造成記憶體浪費,

共享物件,用來儘可能減少記憶體使用量以及分享資訊給儘可能多的相似物件。 Use sharing to support large numbers of fine-grained objects efficiently.



角色说明:
  • Flyweight(抽象享元角色):接口或抽象类,可以同时定义出对象的外部状态和内部状态的接口或实现。
  • ConcreteFlyweight(具体享元角色):实现抽象享元角色中定义的业务。
  • UnsharedConcreteFlyweight(不可共享的享元角色):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。该对象一般不会出现在享元工厂中。
  • FlyweightFactory(享元工厂):管理对象池和创建享元对象。


最近共享单车非常火,就以共享单车为例。如果我们之前没用过共享单车的话,需要先交一份押金才能用;交过押金之后,第二次使用的话就可以直接用了,不用再次交押金。这就跟对象池非常像,如果池中没有这个对象的话,需要先创建一个对象;如果存在的话,则可以直接使用这个对象了。

创建抽象享元角色
定义一个单车接口,里面有个计费方法:

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

    public interface IBike {
        void billing(int time);
    }
=========================================================

创建具体享元角色
创建共享单车类,其中单价是它的内部状态,不随环境而改变;总价是它的外部状态,随着环境改变而改变。

=========================================================
    public class ShareBike implements IBike {//共享单车类
        private int price = 1;//单价
        private int total;//总价

        @Override
        public void billing(int time) {
            total = price * time;
            System.out.println("骑车花费了" + total + "元");
        }
    }
=========================================================

创建享元工厂
负责管理对象池和创建享元对象:

=========================================================
 public class BikeFactory {
    private static Map<String, IBike> pool = new HashMap<>();//使用HashMap来保存IBike对象

    public IBike getBike(String name) {
        IBike bike = null;
        if (pool.containsKey(name)) {//如果存在对象的话,直接使用
            System.out.println("押金已交,直接用车:" + name);
            bike = pool.get(name);
        } else {//对象不存在的话,先创建对象
            bike = new ShareBike();
            pool.put(name, bike);
            System.out.println(name + "交100押金,可以用车了。");
        }
        return bike;
    }
}
=========================================================

客户端测试
=========================================================
     public void test() {
        BikeFactory factory=new BikeFactory();
        IBike ofo = factory.getBike("ofo");
        ofo.billing(2);
        IBike mobike = getBike("Mobike");
        mobike.billing(1);
        IBike ofo1 = getBike("ofo");
        ofo1.billing(3);
    }
=========================================================

输出结果:
=========================================================

ofo交100押金,可以用车了。
骑车花费了2元
Mobike交100押金,可以用车了。
骑车花费了1元
押金已交,直接用车:ofo
骑车花费了3元

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

应用场景

  • 系统存在大量相似或相同的对象。
  • 外部状态相同类似情况下。
  • 需要缓冲池时。

优点

  • 大大减少了系统创建的对象,降低了程序内存的使用。

7. 缺点

  • 将对象分为内部状态外部状态两部分,导致系统变复杂,逻辑也更复杂。
  • 将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。

Android中的源码分析

在 Java中,會維護一個String Pool,對於一些可以共享的字串物件,會先在String Pool中查找是否存在相同的String內容(字元相同),如果有就直接傳回,而不是直接創造一個新的 String 物件,因此上面的結果會顯示 true。


===================================================================
        String s0 = "abc";
        String s1 = "abc";

        System.out.println("s0 == s1 " + s0 == s1);
===================================================================
s0 == s1 true
===================================================================

可以看到s0和s1指向了同一个引用。

Android Observer Pattern

 

应用场景

  • 当一个对象的改变需要通知其它对象改变时,而且它不知道具体有多少个对象有待改变时。
  • 当一个对象必须通知其它对象,而它又不能假定其它对象是谁
  • 跨系统的消息交换场景,如消息队列、事件总线的处理机制。

优点

  • 解除观察者与主题之间的耦合。让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。
  • 易于扩展,对同一主题新增观察者时无需修改原有代码。

缺点

  • 依赖关系并未完全解除,抽象主题仍然依赖抽象观察者。
  • 使用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。
Android 中的源码分析 1

===================================================================
        Button button = (Button) findViewById(R.id.button);
        //注册观察者
        button.setOnClickListener(new View.OnClickListener() {
            //观察者实现
            @Override
            public void onClick(View arg0) {
                Log.d("test", "Click button ");
            }
        });
===================================================================

上面代码中,button就是具体的主题,也就是被观察者;

new 出来的 View.OnClickListenerd 对象就是具体的观察者;

OnClickListener 实际上就是个接口,也就是抽象观察者;通过 setOnClickListener 把观察者注册到被观察者中。

一旦 button 捕获的点击事件,即状态发生变化的时候,就会通过回调注册的 OnClickListener 观察者的 onClick 方法会来通知观察者,Button 状态发生变化。。

==================================================================
    public interface OnClickListener {//抽象观察者

        void onClick(View v);//只有onClick这个方法
    }

    //注册观察者
    public void setOnClickListener(@Nullable View.OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);//设置为可点击
        }
        getListenerInfo().mOnClickListener = l;//把传入的 OnClickListener 对象赋值给了 getListenerInfo().mOnClickListener,即mListenerInfo的mOnClickListener持有OnClickListener对象的引用
    }

    ListenerInfo getListenerInfo() {//返回ListenerInfo对象,这里是一个单例模式
        if (mListenerInfo != null) {
            return mListenerInfo;
        }
        mListenerInfo = new ListenerInfo();
        return mListenerInfo;
    }

    public boolean performClick() {//执行点击事件
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);//执行onClick方法,li.mOnClickListener即OnClickListener对象
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }
=======================================================================

Android 中的源码分析 2: Adapter的notifyDataSetChanged()方法

当我们使用 ListView 时,需要更新数据时我们就会调用 Adapter 的 notifyDataSetChanged() 方法,那么我们来看看 notifyDataSetChanged() 的实现原理,这个方法是定义在 BaseAdaper 中,具体代码如下:

=======================================================================
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
     //数据集被观察者
    private final DataSetObservable mDataSetObservable = new DataSetObservable();

    //注册观察者
    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }
    //注销观察者
    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }
    //数据集改变时,通知所有观察者
    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }
}
    //其他代码略

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

由上面的代码可以看出BaseAdapter实际上就是使用了观察者模式,BaseAdapter就是具体的被观察者。接下来看看 mDataSetObservable.notifyChanged()的实现

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

//数据集被观察者
public class DataSetObservable extends Observable<DataSetObserver> {
   
    public void notifyChanged() {
        synchronized(mObservers) {
            //遍历所有观察者,并调用他们的onChanged()方法
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }

    //其他代码略
}

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

由上面的代码可以看出BaseAdapter实际上就是使用了观察者模式,BaseAdapter就是具体的被观察者。接下来看看 mDataSetObservable.notifyChanged()的实现:

=======================================================================
//数据集被观察者
public class DataSetObservable extends Observable<DataSetObserver> {
   
    public void notifyChanged() {
        synchronized(mObservers) {
            //遍历所有观察者,并调用他们的onChanged()方法
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }

    //其他代码略
}
=======================================================================

现在我们看到了有观察者的影子,那么这些观察者是从哪里来的呢?实际上这些观察者是在ListView通过setAdaper()设置Adaper时产生的:

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

public class ListView extends AbsListView {
    //其他代码略
    
    public void setAdapter(ListAdapter adapter) {
        //如果已存在Adapter,先注销该Adapter的观察者
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }
        
        //其他代码略
        
        super.setAdapter(adapter);

        if (mAdapter != null) {
            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
            mOldItemCount = mItemCount;
            mItemCount = mAdapter.getCount();//获取Adapter中的数据的数量
            checkFocus();

            mDataSetObserver = new AdapterDataSetObserver();//创建一个数据集观察者
            mAdapter.registerDataSetObserver(mDataSetObserver);//注册观察者

           //其他代码略
        } 
    }
}

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

从上面的代码可以看到,观察者有了,那么这个观察者主要是干什么的呢?

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

class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
        @Override
        public void onChanged() {
            super.onChanged();//调用父类的onChanged()方法
            if (mFastScroller != null) {
                mFastScroller.onSectionsChanged();
            }
        }

       //其他代码略
    }

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

AdapterDataSetObserver类中的onChanged()方法没看出啥,继续看他父类的onChanged()方法:

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

class AdapterDataSetObserver extends DataSetObserver {
        private Parcelable mInstanceState = null;
        //观察者的核心实现
        @Override
        public void onChanged() {
            mDataChanged = true;
            mOldItemCount = mItemCount;
            mItemCount = getAdapter().getCount();//获取Adapter中的数据的数量
            if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                    && mOldItemCount == 0 && mItemCount > 0) {
                AdapterView.this.onRestoreInstanceState(mInstanceState);
                mInstanceState = null;
            } else {
                rememberSyncState();
            }
            checkFocus();
            //重新布局
            requestLayout();
        }

       //其他代码略
    }

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

最终就是在AdapterDataSetObserver这个类里面的onChanged()方法中实现了布局的更新。

Reference:
https://www.jianshu.com/p/8f32da74cd8b

n8n index

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