美團熱修複Robust-源碼篇

来源:https://www.cnblogs.com/yrstudy/archive/2018/05/01/8977315.html
-Advertisement-
Play Games

上一篇主要分析了Robust的使用方法,這一篇就來總結一下Robust的源碼分析。 我個人傾向於將Robust框架分為兩個部分,自動插入代碼和動態載入Patch。 一、Robust源碼分析 目前我的分析將Robust動態載入分為兩個部分,一部分是插樁後的代碼邏輯,一部分是拉取Patch的邏輯。 我們 ...


  上一篇主要分析了Robust的使用方法,這一篇就來總結一下Robust的源碼分析。

  我個人傾向於將Robust框架分為兩個部分,自動插入代碼和動態載入Patch。

一、Robust源碼分析

  目前我的分析將Robust動態載入分為兩個部分,一部分是插樁後的代碼邏輯,一部分是拉取Patch的邏輯。

  我們首先來看插樁後的代碼(這裡面套用的是官方的代碼,可能有些過時了)

  插樁前

public long getIndex() {
    return 100;
}

  插樁後

public static ChangeQuickRedirect changeQuickRedirect;
    public long getIndex() {
        if(changeQuickRedirect != null) {
            //PatchProxy中封裝了獲取當前className和methodName的邏輯,併在其內部最終調用了changeQuickRedirect的對應函數
            if(PatchProxy.isSupport(new Object[0], this, changeQuickRedirect, false)) {
                return ((Long)PatchProxy.accessDispatch(new Object[0], this, changeQuickRedirect, false)).longValue();
            }
        }
    return 100L;
}

  我們可以看到Robust為我們的類添加了一個靜態的ChangeQuickRedirect對象,我們可以看到當ChangeQuickRedirect為空時,證明此時沒有補丁,走原邏輯。當它不為空時,我們可以看到它調用了PatchProxy中的isSupport方法和accessDispatch方法。我們具體來看一下PatchProxy中的這兩個方法。

  

 1   public static boolean isSupport(Object[] paramsArray, Object current, ChangeQuickRedirect changeQuickRedirect, boolean isStatic, int methodNumber, Class[] paramsClassTypes, Class returnType) {
 2         //Robust補丁優先執行,其他功能靠後
 3         if (changeQuickRedirect == null) {
 4             //不執行補丁,輪詢其他監聽者
 5             if (registerExtensionList == null || registerExtensionList.isEmpty()) {
 6                 return false;
 7             }
 8             for (RobustExtension robustExtension : registerExtensionList) {
 9                 if (robustExtension.isSupport(new RobustArguments(paramsArray, current, isStatic, methodNumber, paramsClassTypes, returnType))) {
10                     robustExtensionThreadLocal.set(robustExtension);
11                     return true;
12                 }
13             }
14             return false;
15         }
16         String classMethod = getClassMethod(isStatic, methodNumber);
17         if (TextUtils.isEmpty(classMethod)) {
18             return false;
19         }
20         Object[] objects = getObjects(paramsArray, current, isStatic);
21         try {
22             return changeQuickRedirect.isSupport(classMethod, objects);
23         } catch (Throwable t) {
24             return false;
25         }
26     }

  我們可以看到第22行,它調用了changeQuickRedirect.isSupport方法,這個changeQuickRedirect便是我們註入的對象。

  我們接下來再看accessDispatch方法

 1  public static Object accessDispatch(Object[] paramsArray, Object current, ChangeQuickRedirect changeQuickRedirect, boolean isStatic, int methodNumber, Class[] paramsClassTypes, Class returnType) {
 2 
 3         if (changeQuickRedirect == null) {
 4             RobustExtension robustExtension = robustExtensionThreadLocal.get();
 5             robustExtensionThreadLocal.remove();
 6             if (robustExtension != null) {
 7                 notify(robustExtension.describeSelfFunction());
 8                 return robustExtension.accessDispatch(new RobustArguments(paramsArray, current, isStatic, methodNumber, paramsClassTypes, returnType));
 9             }
10             return null;
11         }
12         String classMethod = getClassMethod(isStatic, methodNumber);
13         if (TextUtils.isEmpty(classMethod)) {
14             return null;
15         }
16         notify(Constants.PATCH_EXECUTE);
17         Object[] objects = getObjects(paramsArray, current, isStatic);
18         return changeQuickRedirect.accessDispatch(classMethod, objects);

  可以看到第18行調用了changeQuickRedirect的accseeDispatch方法。

  註入後的代碼我們先看到這裡,我們接下來看一看,我們拉取Patch的代碼

 1   new PatchExecutor(getApplicationContext(), new PatchManpulateImp(), new RobustCallBack() {
 2                     @Override
 3                     public void onPatchListFetched(boolean result, boolean isNet, List<Patch> patches) {
 4                         Log.e("error-hot", "列印 onPatchListFetched:" + "isNet=" + isNet );
 5                     }
 6                     @Override
 7                     public void onPatchFetched(boolean result, boolean isNet, Patch patch) {
 8                         Log.e("error-hot", "列印 onPatchFetched:" + "result=" + result+"isNet="+isNet + "--->" + "patch=" + patch);
 9                     }
10                     @Override
11                     public void onPatchApplied(boolean result, Patch patch) {
12                         Log.e("error-hot", "列印 onPatchApplied:" + "result=" + result + "--->" + "patch=" + patch);
13                     }
14                     @Override
15                     public void logNotify(String log, String where) {
16                         Log.e("error-hot", "列印 logNotify:" + "log=" + log + "--->" + "where=" + where);
17                     }
18                     @Override
19                     public void exceptionNotify(Throwable throwable, String where) {
20                         Log.e("error-hot", "列印 exceptionNotify:" + "throwable=" + throwable.toString() + "--->" + "where=" + where);
21                     }
22                 }).start();

  進入PatchExecutor類中看一看,我們可以發現它繼承了一個線程,那麼直接去run方法看一下

 1    @Override
 2     public void run() {
 3         try {
 4             //拉取補丁列表
 5             List<Patch> patches = fetchPatchList();
 6             //應用補丁列表
 7             applyPatchList(patches);
 8         } catch (Throwable t) {
 9             Log.e("robust", "PatchExecutor run", t);
10             robustCallBack.exceptionNotify(t, "class:PatchExecutor,method:run,line:36");
11         }
12     }

  可以看到run方法中做了兩件事,拉取補丁列表和應用補丁列表。

  我們接著進入fetchPatchList方法

1    protected List<Patch> fetchPatchList() {
2         return patchManipulate.fetchPatchList(context);
3     }

  他返回了patchManipulate的fetchPatchList方法,這個對象便是我們在初始化的時候傳進來的。我們進入看一看

 1     @Override
 2     protected List<Patch> fetchPatchList(Context context) {
 3         Patch patch = new Patch();
 4         patch.setName("test patch");
 5         patch.setLocalPath(Environment.getExternalStorageDirectory().getPath()+
 6                 File.separator+"robust"+File.separator+"patch");
 7         patch.setPatchesInfoImplClassFullName("com.example.tyr.testrobust.PatchesInfoImpl");
 8         List<Patch> patches = new ArrayList<>();
 9         patches.add(patch);
10         return patches;
11     }

  我們將這個PatchesInfoImpl拉進到列表中,那麼這個PatchInfoImpl是在哪裡那?我們後面再說。

  接著看applyPatchList方法

  

protected void applyPatchList(List<Patch> patches) {
        if (null == patches || patches.isEmpty()) {
            return;
        }
        Log.d("robust", " patchManipulate list size is " + patches.size());
        for (Patch p : patches) {
            if (p.isAppliedSuccess()) {
                Log.d("robust", "p.isAppliedSuccess() skip " + p.getLocalPath());
                continue;
            }
            if (patchManipulate.ensurePatchExist(p)) {
                boolean currentPatchResult = false;
                try {
                    currentPatchResult = patch(context, p);
                } catch (Throwable t) {
                    robustCallBack.exceptionNotify(t, "class:PatchExecutor method:applyPatchList line:69");
                }
                if (currentPatchResult) {
                    //設置patch 狀態為成功
                    p.setAppliedSuccess(true);
                    //統計PATCH成功率 PATCH成功
                    robustCallBack.onPatchApplied(true, p);

                } else {
                    //統計PATCH成功率 PATCH失敗
                    robustCallBack.onPatchApplied(false, p);
                }

                Log.d("robust", "patch LocalPath:" + p.getLocalPath() + ",apply result " + currentPatchResult);

            }
        }
    }

  可以看到for迴圈patches中的每一個patch並調用patch方法。我們接著進入patch方法。

 1 protected boolean patch(Context context, Patch patch) {
 2         if (!patchManipulate.verifyPatch(context, patch)) {
 3             robustCallBack.logNotify("verifyPatch failure, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:107");
 4             return false;
 5         }
 6 
 7         DexClassLoader classLoader = new DexClassLoader(patch.getTempPath(), context.getCacheDir().getAbsolutePath(),
 8                 null, PatchExecutor.class.getClassLoader());
 9         patch.delete(patch.getTempPath());
10 
11         Class patchClass, oldClass;
12 
13         Class patchsInfoClass;
14         PatchesInfo patchesInfo = null;
15         try {
16             Log.d("robust", "PatchsInfoImpl name:" + patch.getPatchesInfoImplClassFullName());
17             patchsInfoClass = classLoader.loadClass(patch.getPatchesInfoImplClassFullName());
18             patchesInfo = (PatchesInfo) patchsInfoClass.newInstance();
19             Log.d("robust", "PatchsInfoImpl ok");
20         } catch (Throwable t) {
21             robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:108");
22             Log.e("robust", "PatchsInfoImpl failed,cause of" + t.toString());
23             t.printStackTrace();
24         }
25 
26         if (patchesInfo == null) {
27             robustCallBack.logNotify("patchesInfo is null, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:114");
28             return false;
29         }
30 
31         //classes need to patch
32         List<PatchedClassInfo> patchedClasses = patchesInfo.getPatchedClassesInfo();
33         if (null == patchedClasses || patchedClasses.isEmpty()) {
34             robustCallBack.logNotify("patchedClasses is null or empty, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:122");
35             return false;
36         }
37 
38         for (PatchedClassInfo patchedClassInfo : patchedClasses) {
39             String patchedClassName = patchedClassInfo.patchedClassName;
40             String patchClassName = patchedClassInfo.patchClassName;
41             if (TextUtils.isEmpty(patchedClassName) || TextUtils.isEmpty(patchClassName)) {
42                 robustCallBack.logNotify("patchedClasses or patchClassName is empty, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:131");
43                 continue;
44             }
45             Log.d("robust", "current path:" + patchedClassName);
46             try {
47                 oldClass = classLoader.loadClass(patchedClassName.trim());
48                 Field[] fields = oldClass.getDeclaredFields();
49                 Log.d("robust", "oldClass :" + oldClass + "     fields " + fields.length);
50                 Field changeQuickRedirectField = null;
51                 for (Field field : fields) {
52                     if (TextUtils.equals(field.getType().getCanonicalName(), ChangeQuickRedirect.class.getCanonicalName()) && TextUtils.equals(field.getDeclaringClass().getCanonicalName(), oldClass.getCanonicalName())) {
53                         changeQuickRedirectField = field;
54                         break;
55                     }
56                 }
57                 if (changeQuickRedirectField == null) {
58                     robustCallBack.logNotify("changeQuickRedirectField  is null, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:147");
59                     Log.d("robust", "current path:" + patchedClassName + " something wrong !! can  not find:ChangeQuickRedirect in" + patchClassName);
60                     continue;
61                 }
62                 Log.d("robust", "current path:" + patchedClassName + " find:ChangeQuickRedirect " + patchClassName);
63                 try {
64                     patchClass = classLoader.loadClass(patchClassName);
65                     Object patchObject = patchClass.newInstance();
66                     changeQuickRedirectField.setAccessible(true);
67                     changeQuickRedirectField.set(null, patchObject);
68                     Log.d("robust", "changeQuickRedirectField set sucess " + patchClassName);
69                 } catch (Throwable t) {
70                     Log.e("robust", "patch failed! ");
71                     t.printStackTrace();
72                     robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:163");
73                 }
74             } catch (Throwable t) {
75                 Log.e("robust", "patch failed! ");
76                 t.printStackTrace();
77                 robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:169");
78             }
79         }
80         Log.d("robust", "patch finished ");
81         return true;
82     }

  可以看到我們的類載入器在這裡載入了我們的patch,我們接下來可以看到classloader載入了我們的PatchInfoImpl類。在這個類中繼承了Robust的PatchInfo介面,這裡只有一個方法

1 public interface PatchesInfo {
2     List<PatchedClassInfo> getPatchedClassesInfo();
3 }  

  他拉取了我們需要修改的類的信息。

  這裡面的PatchedClassInfo中保存了兩個類的信息,一個是我們需要修改的類PatchedClass和修改他的類PatchClass。

  第39,40行Robust拿到了這兩個類的名字。

  第48行通過反射獲取了我們需要修改的類的所有field

  接下來是一個for迴圈獲取到我們註入代碼中的靜態ChangeQuickRedirect對象。

  獲取到對象後我們看第64行他載入了我們PatchClass的類

  接下來的65,66,67三行,我們可以看到他通過反射將我們PatchedClass即oldClass中的changeQuickRedirect欄位賦值為我們的PatchClass。至於這個PatchClass是什麼。我們接下來說。

  到目前為止,我們可以看到,插樁後的邏輯已經說完了,不得不說Robust的原理還是比較通俗易懂的。我們接下來回答前面的兩個剩餘問題,PatchInfoImpl和PatchClass在哪裡。我們順著我們的Patch.jar去尋找。反編譯後得到如下列表。

  

  找到了我們的PatchesInfoImpl,而我們的PatchClass就是RobustActivityPatchControl了

  我們先來看一看PatchesInfoImpl做了什麼

 1 import com.meituan.robust.PatchedClassInfo;
 2 import com.meituan.robust.PatchesInfo;
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 public class PatchesInfoImpl
 7   implements PatchesInfo
 8 {
 9   public List getPatchedClassesInfo()
10   {
11     ArrayList localArrayList = new ArrayList();
12     localArrayList.add(new PatchedClassInfo("com.example.tyr.testrobust.RobustActivity", "com.example.tyr.testrobust.RobustActivityPatchControl"));
13     com.meituan.robust.utils.EnhancedRobustUtils.isThrowable = false;
14     return localArrayList;
15   }
16 }

  可以看到他把我們的patchedClass和patchClass加入了list中,也就是上面返回的信息。

  我們接著看我們註入的這個patchClass中的方法

 1 public class RobustActivityPatchControl
 2   implements ChangeQuickRedirect
 3 {
 4   public static final String MATCH_ALL_PARAMETER = "(\\w*\\.)*\\w*";
 5   private static final Map<Object, Object> keyToValueRelation = new WeakHashMap();
 6 
 7   private static Object fixObj(Object paramObject)
 8   {
 9     Object localObject = paramObject;
10     if ((paramObject instanceof Byte))
11       if (((Byte)paramObject).byteValue() == 0)
12         break label32;
13     label32: for (boolean bool = true; ; bool = false)
14     {
15       localObject = new Boolean(bool);
16       return localObject;
17     }
18   }
19 
20   public Object accessDispatch(String paramString, Object[] paramArrayOfObject)
21   {
22     label134: 
23     while (true)
24       try
25       {
26         if (!paramString.split(":")[2].equals("false"))
27           continue;
28         if (keyToValueRelation.get(paramArrayOfObject[(paramArrayOfObject.length - 1)]) != null)
29           continue;
30         RobustActivityPatch localRobustActivityPatch = new RobustActivityPatch(paramArrayOfObject[(paramArrayOfObject.length - 1)]);
31         keyToValueRelation.put(paramArrayOfObject[(paramArrayOfObject.length - 1)], null);
32         break label134;
33         if (!"12".equals(paramString.split(":")[3]))
34           break;
35         localRobustActivityPatch.onCreate((Bundle)paramArrayOfObject[0]);
36         return null;
37         localRobustActivityPatch = (RobustActivityPatch)keyToValueRelation.get(paramArrayOfObject[(paramArrayOfObject.length - 1)]);
38         break label134;
39         localRobustActivityPatch = new RobustActivityPatch(null);
40         continue;
41       }
42       catch (Throwable paramString)
43       {
44         paramString.printStackTrace();
45         return null;
46       }
47     return null;
48   }
49 
50   public Object getRealParameter(Object paramObject)
51   {
52     Object localObject = paramObject;
53     if ((paramObject instanceof RobustActivity))
54       localObject = new RobustActivityPatch(paramObject);
55     return localObject;
56   }
57 
58   public boolean isSupport(String paramString, Object[] paramArrayOfObject)
59   {
60     paramString = paramString.split(":")[3];
61     return ":12:".contains(":" + paramString + ":");
62   }
63 }

  我們可以看到它實現了Robust的ChangeQuickRedirect介面,並實現了他們的兩個方法accessDispatch和isSupport的兩個方法,也就是PatchProxy中調用的這兩個方法。

  先說isSupport方法

  這裡的isSupport方法是混淆後的,我們可以看到在PatchProxy類中,他傳入了classMethod的名字和這個方法所需要的參數。校驗後進行判斷。

  在PatchProxy中他傳入了的classMethod格式為className:methodName:isStatic:methodNumber。這裡只校驗了方法的number。這裡是在accessDispatch中傳入,目測插樁後的代碼有所改動。

  繼續說accessPatch方法。

  第26行校驗了是否為靜態方法。將參數數組傳給了RobustActivityPatch這個類,並調用了它的onCreat方法,莫名的熟悉感,這個就是我們標註為Modify標簽的那個類。

  我們接下來看一看RobustActivityPatch這個類

  

 1 import android.os.Bundle;
 2 import android.support.v7.app.c;
 3 import android.view.View;
 4 import android.widget.TextView;
 5 import com.meituan.robust.utils.EnhancedRobustUtils;
 6 
 7 public class RobustActivityPatch
 8 {
 9   RobustActivity originClass;
10 
11   public RobustActivityPatch(Object paramObject)
12   {
13     this.originClass = ((RobustActivity)paramObject);
14   }
15 
16   public static void staticRobustonCreate(RobustActivityPatch paramRobustActivityPatch, RobustActivity paramRobustActivity, Bundle paramBundle)
17   {
18     RobustActivityPatchRobustAssist.staticRobustonCreate(paramRobustActivityPatch, paramRobustActivity, paramBundle);
19   }
20 
21   public Object[] getRealParameter(Object[] paramArrayOfObject)
22   {
23     if ((paramArrayOfObject == null) || (paramArrayOfObject.length < 1))
24       return paramArrayOfObject;
25     Object[] arrayOfObject = new Object[paramArrayOfObject.length];
26     int i = 0;
27     if (i < paramArrayOfObject.length)
28     {
29       if ((paramArrayOfObject[i] instanceof Object[]))
30         arrayOfObject[i] = getRealParameter((Object[])paramArrayOfObject[i]);
31       while (true)
32       {
33         i += 1;
34         break;
35         if (paramArrayOfObject[i] == this)
36         {
37           arrayOfObject[i] = this.originClass;
38           continue;
39         }
40         arrayOfObject[i] = paramArrayOfObject[i];
41       }
42     }
43     return arrayOfObject;
44   }
45 
46   protected void onCreate(Bundle paramBundle)
47   {
48     staticRobustonCreate(this, this.originClass, paramBundle);
49     EnhancedRobustUtils.invokeReflectMethod("setContentView", ((RobustActivityPatch)this).originClass, getRealParameter(new Object[] { new Integer(2131296284) }), new Class[] { Integer.TYPE }, c.class);
50     paramBundle = (View)EnhancedRobustUtils.invokeReflectMethod("findViewById", ((RobustActivityPatch)this).originClass, getRealParameter(new Object[] { new Integer(2131165307) }), new Class[] { Integer.TYPE }, c.class);
51     if (paramBundle == this);
52     for (paramBundle = ((RobustActivityPatch)paramBundle).originClass; ; paramBundle = (TextView)paramBundle)
53     {
54       String str = (String)EnhancedRobustUtils.invokeReflectMethod("RobustPublicgetString", new RobustActivityInLinePatch(getRealParameter(new Object[] { this })[0]), getRealParameter(new Object[0]), null, null);
55       Object localObject = paramBundle;
56       if (paramBundle == this)
57         localObject = ((RobustActivityPatch)paramBundle).originClass;
58       EnhancedRobustUtils.invokeReflectMethod("setText", localObject, getRealParameter(new Object[] { str }), new Class[] { CharSequence.class }, TextView.class);
59       return;
60     }
61   }
62 }

  看到onCreate方法,第48行,這裡我們可以看到他特殊處理了一下我們在RobustActivity的onCreate方法,感覺有點怪怪的,這裡似乎是又執行了一邊RobustActivity的OnCreate方法,而不是super.onCreate。

  ps:看了看美團官方關於super的解析,似乎是這樣的,他通過調用RobustActivity的OnCreate,將class文件中的invokevirtual指令替換為invokesuper指令,從而達到super的效果,這裡面還有個問題,如果這樣調用會出現這樣的問題

Caused by: java.lang.NoSuchMethodError: No super method thisIsSuper()V in class Lcom/meituan/sample/TestSuperClass; or its super classes (declaration of 'com.meituan.sample.TestSuperClass' appears in /data/app/com.meituan.robust.sample-3/base.apk)

  Robust的解決方案是使這個類也繼承RobustActivity的父類,我們可以看到RobustActivityPatchRobustAssist類果然繼承了一個類,但是由於混淆我們看到的是他繼承了一個c的類,猜測它應該就是RobustActivity的父類AppCompatActivity。

  驗證一下列印dex文件

  

  看到invoke-super指令,現在可以確定了

  再看一下RobustActivityPatchRobustAssist的父類,這絕對就是android.support.v7.app.AppcompatActivity了。

  

  然後我們可以看到他執行了setContentView,findViewById這裡傳入的兩串數字便是我們的佈局和空間在R類的數字。

  然後我們可以看到它執行到了我們修改代碼的地方。

  第54行它調用了RobustPublicgetString方法,又是莫名的熟悉感,,

  進入RobustActivityInLinePatch看一看。

 1 public class RobustActivityInLinePatch
 2 {
 3   RobustActivity originClass;
 4 
 5   public RobustActivityInLinePatch(Object paramObject)
 6   {
 7     this.originClass = ((RobustActivity)paramObject);
 8   }
 9 
10   private String getString()
11   {
12     return "hello robust";
13   }
14 
15   public String RobustPublicgetString()
16   {
17     return getString();
18   }
19 
20   public Object[] getRealParameter(Object[] paramArrayOfObject)
21   {
22     if ((paramArrayOfObject == null) || (paramArrayOfObject.length < 1))
23       return paramArrayOfObject;
24     Object[] arrayOfObject = new Object[paramArrayOfObject.length];
25     int i = 0;
26     if (i < paramArrayOfObject.length)
27     {
28       if ((paramArrayOfObject[i] instanceof Object[]))
29         arrayOfObject[i] = getRealParameter((Object[])paramArrayOfObject[i]);
30       while (true)
31       {
32         i += 1;
33         break;
34         if (paramArrayOfObject[i] == this)
35         {
36           arrayOfObject[i] = this.originClass;
37           continue;
38         }
39         arrayOfObject[i] = paramArrayOfObject[i];
40       }
41     }
42     return arrayOfObject;
43   }
44 }

  可以看到我們傳入的getString方法出現在了這裡。

二、總結

  到目前為止,Robust的邏輯算是走通了。

  目前為止,我認為Robust的核心應該算是它自動插樁的那一部分,目前暫時不涉及了,下一篇將會瞭解一下熱修複背後的動態載入。

參考資料:

  Android中熱修複框架Robust原理解析+並將框架代碼從"閉源"變成"開源"(上篇)

  Android熱更新方案Robust

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Alias——使用一個列名別名AS 關鍵字: GROUP BY語句: 比如在合計(sum函數)的時候常需要用group by語句來進行分類求和 2018-05-01 23:21:52 ...
  • 原文鏈接:http://www.cnblogs.com/xrq730/p/8944539.html,轉載請註明出處,謝謝 本文目錄 上一篇文章以認識Redis為主,寫了Redis系列的第一篇,現在開啟第二部分的學習,在本文中,我們將看到以下內容: Redis數據結構String、Hash、List、 ...
  • 連接查詢:同時設計兩個及以上的表的查詢 連接條件或連接謂詞:用來連接兩個表的條件一般格式: [<表名1>]<列名1> <比較運算符> [<表名2>]<列名2> [<表名1>]<列名1> between [<表名2>]<列名2> and [<表名2>]<列名3> 等值連接: 連接運算符為= 查詢每個學 ...
  • linux上安裝redis,本地遠程訪問問題總結,redis超時連接問題 ...
  • MongoDB運行的兩種方式 檢查是否有MongoDB:which mongod 創建資料庫存儲目錄:mkdir -p /data/db 檢查磁碟目錄是否有空間(一般要大於4G):df -lh 啟動:a直接啟動:mongod --dbpath=/data/db --port=27017 b守護進程的 ...
  • 對oracle資料庫,PL/SQL用法的快速學習 ...
  • 一、超級管理員創建及開啟登錄驗證 如果MongoDB要開啟登錄驗證,必須在開啟登錄驗證之前先創建好超級管理員,否則無法登錄資料庫! 例如,創建一個超級管理員admin,關聯給admin資料庫,角色設置為root(超級管理員) 首先,進入到目標庫admin,use admin 然後,輸入指令 db.c ...
  • 在Android開發過程中,耗時操作是不允許寫在主線程(UI線程)中的,以免由於等待時間過長而發生ANR。所以耗時操作需要創建子線程來完成,然而往往這些操作都需要與主線程進行通訊交互(例如更新主線程的UI),但android規定除了UI線程外,其他線程都不可以對UI控制項進行訪問或操控,所以我們需要通 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...