編輯:關於Android編程
public static IMManager getInstance(Context context) {
if (mInstance == null) {
synchronized (IMManager.class) {
if (mInstance == null)
mInstance = new IMManager(context);
}
}
return mInstance;
}
當調用getInstance時,如果傳入的context是Activity的context。只要這個單例沒有被釋放,這個Activity也不會被釋放。
解決方案:傳入Application的context.
2.非靜態內部類引起的內存洩露:
在java中,創建一個非靜態的內部類實例,就會引用它的外圍實例.如果這個非靜態內部類實例做了一些耗時的操作.就會造成外圍對象不會被回收,從而導致內存洩露.
解決方案:
1.將內部類變成靜態內部類
當將內部類變成靜態內部類後,便不再持有外部類對象的引用,導致程序不允許你在handler中操作activity的對象了,因此我麼需要在handler中增加一個對A/ctivity的弱引用(WeakReference);
注:為什麼使用靜態內部類就不會造成內存洩露了?
答:靜態內部類不會持有對外部對象的引用
2.如果有強引用Activity中的屬性,則將該屬性的引用方式改為弱引用.
使用WeakReference包裝該對象.
3.在業務允許的情況下,當activity執行onDestory時,結束這些耗時任務
static class MyHandler extends Handler {
WeakReference mActivityReference;
MyHandler(Activity activity) {
mActivityReference= new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
mImageView.setImageBitmap(mBitmap);
}
}
}
3.資源未關閉引起的內存洩露
當使用了BroadcastReceiver\Cursor\Bitmap等資源的時候,當不需要使用時,需要及時釋放掉,若沒有釋放,則會引起內存洩露.
4.**增加:在使用Bitmap時,出現內存溢出的解決方案:**1.調用系統的GC回收,System.gc();2.壓縮圖片質量大小.
雖然靜態類與非靜態類之間的區別並不大,但是對於Android開發者而言卻是必須理解的.至少我們要清楚,如果一個內部類實例的生命周期比Activity更長,那麼我們千萬不要使用非靜態的內部類.最好的做法是,使用靜態內部類,然後在該類裡面使用弱引用來指向所在的Activity
2.Java基礎之弱引用\強引用\軟引用\虛引用
強引用(StrongReference):是使用最普遍的引用.如果一個對象具有強引用,那GC回收器絕不會回收它.
Object o=new Object(); // 強引用
public void test(){
Object o=new Object();
// 省略其他操作
}
當內存不足時,java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題.如果不使用時,要通過如下方式來弱化引用.
在方法內部有一個強引用,這個引用保存在棧中,而真正得引用內容(Object )保存在堆中.當這個方法運行完成後,就會推出方法棧,則引用內容的引用不存在,這個Object會被回收.
但是如果這個o是全局的變量時,就需要在不用這個對象時復制為null,因為強引用不會被垃圾回收。
軟引用(SoftReference): 如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。
String str=new String("abc"); // 強引用
SoftReference softRef=new SoftReference(str); // 軟引用
軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
弱引用:
弱引用與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由於垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。
String str=new String("abc");
WeakReference abcWeakRef = new WeakReference(str);
str=null;
當你想引用一個對象,但是這個對象有自己的生命周期,你不想介入這個對象的生命周期,這時候你就是用弱引用。
虛引用(PhantomReference):
“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命周期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
虛引用主要用來跟蹤對象被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器准備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之 關聯的引用隊列中。
vcv7w8eho9LytMujrHN0YXRpY7bUz/O/ydLU1NrL/LXEyM66zrbUz/O0tL2o1q7HsLfDzsqjrM7e0OjS/dPDyM66zrbUz/OhozxiciAvPg0KMS48c3Ryb25nPnN0YXRpY9Deys63vbeoOjwvc3Ryb25nPjxiciAvPg0KtbFzdGF0aWO52Lz819bQ3srOt723qMqxo6zV4rj2t723qL7Ns8bOqr6yzKy3vbeooaO+ssyst723qMr009rA4Lb4srvK9NPayrXA/bbUz/Oho8THw7S+ssyst723qMjnus69+NDQtffTw6O/v8nS1M2ouf3A4MP7Lre9t6jD+8C01rG907X308OjrNKyv8nS1M2ouf3KtcD9ttTP8y63vbeow/vAtLX308OhozxiciAvPg0KyrnTw3N0YXRpY7nYvPzX1tDeys61xLe9t6jU2sDgvNPU2LXEyrG68r7Nu+G809TYtb3E2rTm1tCho8rHsbvWuM/yy/nK9LXEwOC2+LK7ysfKtcD9oaM8YnIgLz4NCr/J0tS/tLP2o6zV4rKisrvKx7iyuMejrMbkyrW+ssyst723qNa7v8nS1LG70v6y2KOstviyu7/J0tS4srjHoaO1sXQxzqpQZXJzb27A4NDNyrGjrNTytffTw1BlcnNvbsDgtcS+ssyst723qKOs0vLOqr6yzKy3vbeo1rvK9NPa1eK49sDgo6y2+LWxdDLA4NDNzqpUZXN0MDLKsaOstffTw9fTwOC1xL6yzKy3vbeoo6y1sdfTwODDu9PQyrGjrLX308O4uMDgtcS3vbeoo6zV4r7NvdDX9rzMs9C4uMDgtcS3vbeowcuhozxiciAvPg0KMi48c3Ryb25nPnN0YXRpY9DeuMTK9NDUOjwvc3Ryb25nPjxiciAvPg0KtbFzdGF0aWPQ3srOyvTQ1Mqxo6zT68Dg0rvR+aOstrzKx9TawOC809TYyrG+zbzT1Ni1vcHLxNq05qGjPGJyIC8+DQozLjxzdHJvbmc+c3RhdGlj0N7Kzr/pPC9zdHJvbmc+PGJyIC8+DQq803N0YXRpY9Deys61xL/p0rK74cvm18XA4LXEvNPU2Lb4vfjI68TatOajrMv50tS/ydLU1NrV4sDvs/XKvLuv0rvQqb6yzKyzydSxseTBv6GjPGJyIC8+DQo0LjxzdHJvbmc+yrnTw3N0YXRpY9ei0uLKws/uOjwvc3Ryb25nPjxiciAvPg0KtbHA4LG70OnE4rv6vNPU2Mqxo6ywtNXVyfnD98uz0PLPyLrzs/XKvLuvc3RhdGljs8nUsdfWts66zXN0YXRpY9PvvuS/6TxiciAvPg0Kc3RhdGljy/nQ3srOtcS3vbeous2x5MG/1rvK9NPawOCjrMv509C21M/zubLP7aGjPGJyIC8+DQrU2nN0YXRpY8v50N7KzrXEt723qLrN0+++5L/p1tCyu8TcyrnTw7fHc3RhdGljs8nUsdfWts6hozxiciAvPg0KvrLMrLPJ1LGyu8rHttTP87XEzNjQ1KOs1rvKx86qwcvV0tK7uPbI3cnt1q60pqOsy/nS1NDo0qq3xbW90ru49sDg1tC2+NLRo6yw0SZsZHF1bzvIq77WseTBvyZyZHF1bzu3xdTaxNqyv8Dg1tC+zcrHusHO3tLi0uW1xMrCx+mjrMv50tQgsbu9+9a5PGJyIC8+DQrU2kphdmGyu8Tc1rG907ao0uXIq77WseTBv6OsysfNqLn9c3RhdGljwLTKtc/WtcQ8YnIgLz4NCtTaSmF2YdbQw7vT0GNvbnN0o6yyu8Tc1rG907ao0uWzo8G/o6zNqLn9c3RhdGljIGZpbmFswLTKtc/WPGJyIC8+DQo8c3Ryb25nPmZpbmFsILnYvPzX1jo8L3N0cm9uZz48YnIgLz4NCjEuPHN0cm9uZz5maW5hbLnYvPzX1rXEyrnTwzo8L3N0cm9uZz7S/dPDsb7J7bXEsrux5LrN0v3Tw9a4z/K1xLbUz/Oyu7HkoaM8YnIgLz4NCjxzdHJvbmc+0N7KzsDgOjxlbT4qPC9lbT48L3N0cm9uZz7I57n70ru49sDgsbvJ+cP3zqpmaW5hbKOs0uLOttfFy/yyu8Tc1NnFycn6s/bQwrXE19PA4KOssrvE3Nf3zqq4uMDgsbu8zLPQKqGj0vK0y9K7uPbA4LK7xNzNrMqxyfnD986qYWJzdHJhY3QgZmluYWy78tXfaW50ZXJmYWNlIGZpbmFstcShozxiciAvPg0KPHN0cm9uZz7Q3srOt723qCA6PC9zdHJvbmc+vau3vbeoyfnD986qZmluYWyjrL/J0tSxo9aky/zDx9TayrnTw9bQsruxu7jEseSho7G7yfnD986qZmluYWy1xLe9t6jSss2s0fnWu8TcyrnTw6OssrvE3NbY1Niho7WrysfX08Dgv8nS1LzMs9C4uMDgtcRmaW5hbLe9t6ihozxiciAvPg0KPHN0cm9uZz7Q3srOseTBvyA6PC9zdHJvbmc+se3Kvsr00NTWtbXa0ru0zrP1yry7r7rzsrvE3LG70N64xKGjZmluYWzK9NDUv8nS1NaxvdOz9cq8u6+78tTaubnU7Lqvyv3W0LP1yry7ry7I57n7yvTQ1MrH1rG907P1yry7r6Os1PLG5Na1srvE3LG7xuTL/Lqvyv0osPzAqLm51Oy6r8r9KdDeuMShozxiciAvPg0KPHN0cm9uZz7Q3srOt723qLLOyv0gOjwvc3Ryb25nPrLOyv3WtbK7xNyxu9DeuMQ8YnIgLz4NCjxzdHJvbmc+0N7Kzre9t6jW0LXEvtayv7Hkwb8gOjwvc3Ryb25nPiC+1rK/seTBv7G7tdrSu7TOs/XKvLuvuvOyu8TcsbvQ3rjEPGJyIC8+DQrXojrIzrrO1NppbnRlcmZhY2XA78n5w/e1xLPJ1LGx5MG/o6zErMjPzqpwdWJsaWMgc3RhdGljIGZpbmFsoaM8YnIgLz4NCjxzdHJvbmc+yrnTw2ZpbmFstcTS4tLlOjwvc3Ryb25nPjxiciAvPg0KtdrSu6Oszqq3vbeoJmxkcXVvO8nPy/gmcmRxdW87o6y3wNa5yM66zrzMs9DA4LjEseTL/LXEsb7AtLqs0uW6zcq1z9aho8novMazzNDyyrGjrMj0z6PN+9K7uPa3vbeotcTQ0M6q1Nq8zLPQxtq85LGjs9ayu7Hko6y2+MfSsru/ybG7uLK4x7vyuMTQtKOsvs2/ydLUssnIodXi1tbX9reooaM8YnIgLz4NCrXatv6jrMzhuN+zzNDy1rTQ0LXE0KfCyqOsvavSu7j2t723qMnos8lmaW5hbLrzo6yx4NLrxve+zb/J0tSw0bbUxMe49re9t6i1xMv509C199PDtrzWw8jrJmxkcXVvO8e2yOsmcmRxdW87tffTw8Dvo6jE2se2u/rWxqOpoaM8YnIgLz4NCjxzdHJvbmc+ZmluYWxsebnYvPzX1jo8L3N0cm9uZz48YnIgLz4NCtTa0uyzo7SmwO3KsczhuakgZmluYWxseSC/6cC01rTQ0MjOus7H5bP9stnX96GjyOe5+8XXs/bSu7j20uyzo6OsxMfDtM/gxqXF5LXEIGNhdGNoINfTvuS+zbvh1rTQ0KOsyLu687/Y1sa+zbvhvfjI6yBmaW5hbGx5IL/poaMgy/nS1Mu1ZmluYWxseb/pysfSu7aou+Gxu9a00NC1xKGjPGJyIC8+DQpmaW5hbGx5INPvvuS/6crH1NogdHJ5ILvy1d8gY2F0Y2gg1tC1xCByZXR1cm4g0+++5Naux7DWtNDQtcSho7j8vNPSu7DjtcTLtbeoysejrGZpbmFsbHkg0+++5L/p06a4w8rH1Nq/2NbG16rSxtPvvuTWrsew1rTQ0KOsv9jWxteq0sbT777ks/3ByyByZXR1cm4gzeKjrLu509AgYnJlYWsgus0gY29udGludWWhoyByZXR1cm4gus0gdGhyb3cgsNGzzNDyv9jWxsio16q9u7j4y/zDx7XEtffTw9Xfo6hpbnZva2Vyo6mjrLb4IGJyZWFrILrNIGNvbnRpbnVlILXEv9jWxsioysfU2rWxx7C3vbeoxNrXqtLGoaM8YnIgLz4NCjxzdHJvbmc+ZmluYWxpemUgt723qMP7Ojwvc3Ryb25nPjxiciAvPg0KZmluYWxpemWjqKOpt723qMrH1NrArLv4ytW8r8b3yb6z/bbUz/PWrsewttTV4rj2ttTP87X308O1xKGjPC9wPg0KPHA+NC48c3Ryb25nPkphdmG24M/fs8zP37PMs9ggLSDP37PMs9jUrcDtOjwvc3Ryb25nPjwvcD4NCjxwcmUgY2xhc3M9"brush:java;">
public interface Executor {
void execute(Runnable command);
}
Executors方便的創建線程池.
(1).newCachedThreadPool :該線程池比較適合沒有固定大小並且比較快速就能完成的小任務,它將為每個任務創建一個線程。那這樣子它與直接創建線程對象(new Thread())有什麼區別呢?看到它的第三個參數60L和第四個參數TimeUnit.SECONDS了嗎?好處就在於60秒內能夠重用已創建的線程。下面是Executors中的newCachedThreadPool()的源代碼:
//基本的無界值(如 Integer.MAX_VALUE),則允許池適應任意數量的並發任務
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}
(2).newFixedThreadPool:使用的Thread對象的數量是有限的,如果提交的任務數量大於限制的最大線程數,那麼這些任務將排隊,然後當有一個線程的任務結束之後,將會根據調度策略繼續等待執行下一個任務。下面是Executors中的newFixedThreadPool()的源代碼:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue());
}
(3).newSingleThreadExecutor:就是線程數量為1的FixedThreadPool,如果提交了多個任務,那麼這些任務將會排隊,每個任務都會在下一個任務開始之前運行結束,所有的任務將會使用相同的線程。下面是Executors中的newSingleThreadExecutor()的源代碼:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
}
(4).newScheduledThreadPool:創建一個固定長度的線程池,而且以延遲或定時的方式來執行任務。
ThreadPoolExecutor構造方法:
public ThreadPoolExecutor(int corePoolSize, //核心線程
int maximumPoolSize, // 線程池維護的最大線程數量
long keepAliveTime, //線程池維護線程所允許的空閒時間
TimeUnit unit,
BlockingQueue workQueue //// 阻塞隊列 ) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
ExecutorService:為了解決執行服務的生命周期問題,Executor擴展了EecutorService接口,添加了一些用於生命周期管理的方法。
ThreadPoolExecutor線程池實現類:
private final BlockingQueue workQueue; // 阻塞隊列
private final ReentrantLock mainLock = new ReentrantLock(); // 互斥鎖
private final HashSet workers = new HashSet();// 線程集合.一個Worker對應一個線程
private final Condition termination = mainLock.newCondition();// 終止條件
private int largestPoolSize; // 線程池中線程數量曾經達到過的最大值。
private long completedTaskCount; // 已完成任務數量
private volatile ThreadFactory threadFactory; // ThreadFactory對象,用於創建線程。
private volatile RejectedExecutionHandler handler;// 拒絕策略的處理句柄
private volatile long keepAliveTime; // 線程池維護線程所允許的空閒時間
private volatile boolean allowCoreThreadTimeOut;
private volatile int corePoolSize; // 線程池維護線程的最小數量,哪怕是空閒的
private volatile int maximumPoolSize; // 線程池維護的最大線程數量
處理任務的優先級為:
核心線程corePoolSize > 任務隊列workQueue > 最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。 當池中的線程數大於corePoolSize的時候,多余的線程會等待keepAliveTime長的時間,如果無請求可處理就自行銷毀。workQueue :線程池所使用的緩沖隊列,該緩沖隊列的長度決定了能夠緩沖的最大數量,緩沖隊列有三種通用策略:
(1) 直接提交,工作隊列的默認選項是 SynchronousQueue,它將任務直接提交給線程而不保持它們.
(2) 無界隊列,使用無界隊列(例如,不具有預定義容量的 LinkedBlockingQueue)將導致在所有 corePoolSize 線程都忙時新任務在隊列中等待。這樣,創建的線程就不會超過 corePoolSize。(因此,maximumPoolSize 的值也就無效了。)當每個任務完全獨立於其他任務,即任務執行互不影響時,適合於使用無界隊列.
(3) 有界隊列,當使用有限的 maximumPoolSizes 時,有界隊列(如 ArrayBlockingQueue)有助於防止資源耗盡,但是可能較難調整和控制。隊列大小和最大池大小可能需要相互折中:使用大型隊列和小型池可以最大限度地降低 CPU 使用率、操作系統資源和上下文切換開銷,但是可能導致人工降低吞吐量。如果任務頻繁阻塞(例如,如果它們是 I/O 邊界),則系統可能為超過您許可的更多線程安排時間。使用小型隊列通常要求較大的池大小,CPU 使用率較高,但是可能遇到不可接受的調度開銷,這樣也會降低吞吐量.
ThreadFactory:使用 ThreadFactory 創建新線程。如果沒有另外說明,則在同一個 ThreadGroup 中一律使用 Executors.defaultThreadFactory() 創建線程,並且這些線程具有相同的 NORM_PRIORITY 優先級和非守護進程狀態。通過提供不同的 ThreadFactory,可以改變線程的名稱、線程組、優先級、守護進程狀態等等。如果從 newThread 返回 null 時 ThreadFactory 未能創建線程,則執行程序將繼續運行,但不能執行任何任務。
在DefaultThreadFactory類中實現了ThreadFactory接口並對其中定義的方法進行了實現,如下:
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
}
// 為線程池創建新的任務執行線程
public Thread newThread(Runnable r) {
// 線程對應的任務是Runnable對象r
Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(), 0);
// 設為非守護線程
if (t.isDaemon())
t.setDaemon(false);
// 將優先級設為Thread.NORM_PRIORITY
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
RejectedExecutionHandler:
當Executor已經關閉(即執行了executorService.shutdown()方法後),並且Executor將有限邊界用於最大線程和工作隊列容量,且已經飽和時,在方法execute()中提交的新任務將被拒絕.
在以上述情況下,execute 方法將調用其 RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。下面提供了四種預定義的處理程序策略:
1) 在默認的 ThreadPoolExecutor.AbortPolicy 處理程序遭到拒絕將拋出運行時 RejectedExecutionException;
2) 在 ThreadPoolExecutor.CallerRunsPolicy 線程調用運行該任務的 execute 本身。此策略提供簡單的反饋控制機制,能夠減緩新任務的提交速度
3) 在 ThreadPoolExecutor.DiscardPolicy 不能執行的任務將被刪除;
4) 在 ThreadPoolExecutor.DiscardOldestPolicy 如果執行程序尚未關閉,則位於工作隊列頭部的任務將被刪除,然後重試執行程序(如果再次失敗,則重復此過程)。
線程池默認會采用的是defaultHandler策略。
Callable接口代表一段可以調用並返回結果的代碼;Future接口表示異步任務,是還沒有完成的任務給出的未來結果。所以說Callable用於產生結果,Future用於獲取結果。
以下內容來自http://www.trinea.cn/android/java-android-thread-pool/,再此簡單記錄.
new Thread的弊端:
a.每次new Thread新建對象性能差.
b.線程缺乏統一管理,可能無限制新建線程,相互之間競爭,及可能占用過多系統資源導致死機或oom.
c.缺乏更多功能,如定時執行、定期執行、線程中斷.
Java提供的四種線程池的好處:
a. 重用存在的線程,減少對象創建、消亡的開銷,性能佳。
b. 可有效控制最大並發線程數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞。
c. 提供定時執行、定期執行、單線程、並發數控制等功能。
(1). newCachedThreadPool
創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。線程池為無限大,當執行第二個任務時第一個任務已經完成,會復用執行第一個任務的線程,而不用每次新建線程。
(2). newFixedThreadPool
創建一個定長線程池,可控制線程最大並發數,超出的線程會在隊列中等待。定長線程池的大小最好根據系統資源進行設置。如Runtime.getRuntime().availableProcessors()。
(3) newScheduledThreadPool
創建一個定長線程池,支持定時及周期性任務執行。延遲執行示例代碼如下
(4)、newSingleThreadExecutor
創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。現行大多數GUI程序都是單線程的。Android中單線程可用於數據庫操作,文件操作,應用批量安裝,應用批量刪除等不適合並發但可能IO阻塞性及影響UI線程響應的操作。
UIScrollView是內容滾動視圖,作為父視圖時,可以添加多個視圖控件,然後通過設置其特有的contentSize屬性,以便控制進行水平方向,或垂直方向的滾動。水平方
前言默認情況下,Android Studio設置新的項目並且部署到模擬器或者真機設備上,只需要點擊幾下。使用即時運行,你並不需要構建一個新的APK即可將改變後的方法和現有
今天開源一個時間滑輪組件,在網上有各種開源比如wheel和其它改寫,多多少少不夠實用和缺陷。 該項目地址位於:https://github.com/apple317/d
一款軟件就像一個孩子,不斷的在學習,在探索,當孩子犯下錯誤的時候,我們可以去包容,當孩子犯不改的時候,獲取他就不再讓人喜歡,甚至是去拋棄他。人之常情的問題,也是做軟件的我