Friday, October 3, 2014

Java Garbage Collection

Java Memory Management, với garbage collection được tích hợp sẵn, là một trong những ngôn ngữ tốt nhất hiện nay. Nó cho phép developer tạo ra các đối tượng mới mà không cần phải quan tâm tới allocation và deallocation, bởi vì garbage collector tự động thu hồi bộ nhớ để tái sử dụng. Cho phép phát triển nhanh các ứng dụng với ít dòng code hơn, trong khi loại bỏ memory leak và nhưng vấn đề về memory-related. Ít nhất là trên lý thuyết.

Trên thực tế, java garbare collection dường như hoạt động quá tốt, tạo và xóa rất nhiều đối tượng. Phần lớn các vấn đề về memory-management được giải quyết, nhưng phải trả giá bởi các vấn đề nghiêm trọng về performance. Để khiến garbage collection có thể sử dụng trong tất cả các tình huống sẽ dẫn tới một hệ thống phức tạp và khó có thể tối ưu hóa. 

How Garbage Collection Really Works

  • Track tất cả các đối tượng LIVE, và các đối tượng khác coi là GARBAGE

HEAP

  • Là vùng nhớ được sử dụng để cấp phát động
  • Trong hầu hết các cấu hình hệ điều hành cho phép JVM quản lý HEAP
  • Có 2 khía cạnh quan trọng:
    • Việc tạo đối tượng xảy ra nhanh hơn bởi vì không cần đồng bộ hóa với hệ điều hành cho từng đối tượng. Một lần cấp phát đơn giản là lấy một vài phần trong một mảng bộ nhớ sau đó dịch con trỏ lên phía trước.
    • Khi một đối tượng không còn được sử dụng nữa, garbage collector lấy tại bộ nhớ và sử dụng nó cho việc phát đối tượng sau này. Có nghĩa là không hề có việc xóa hay lấy lai bộ nhớ đối với hệ điều hành

  • Tất cả đối tượng được cấp phát trên heap đều được quản lý bởi JVM bao gồm class object, static variables, thập chí cả code của nó. Chừng nào mà đối tượng còn được tham chiêu tới thì JVM sẽ xem nó ALIVE. Nếu một đối tượng không còn được tham chiếu tới nghĩa là unreachable bởi application code, garbage collector xóa nó đi và lấy lại bộ nhớ. What is the first reference in the tree???!!!

Garbage-Collection Roots — The Source of All Object Trees

  • Mỗi một object tree phải có một hoặc nhiều root object. Chừng nào application còn reach root thì toàn bộ tree reachable. Nhưng khi nào thì root object được xem như reachable??? Các đối tượng đặc biệt gọi là garbage-collection root lúc nào cũng reachable vì thế bất kì đối tượng nào có một garbage-collection root tại root của nó.
  • Có 4 loại GC root trong java:
    1. Local variables: are kept alive by the stack of a thread. This is not a real object virtual reference and thus is not visible. For all intents and purposes, local variables are GC roots
    2. Active java threads: are always considered live objects and are therefore GC roots. This is especically important for thread local variables.
    3. Static variables: are referenced by their classes. This fact makes them de facto GC roots. Classes themselves can be garbage-collected, which would remove all referenced static variables. This is of special importance when we use application servers, OSGi containers or class loaders in general. We will discuss the related problems in the Problem Patterns section
    4. JNI Refercences are Java objects that the native code has created as part of a JNI call. Objects thus created are treated specially because the JVM does not know if it is being referenced by the native code or not. Such objects represent a very special from of GC root, which we will examine in more detail in the Problem Patterns section. 
Một ứng dụng Java đơn giản sẽ có các GC root:
  • Các local variable trong hàm main
  • Main thread
  • Các static variable của main class

Marking and Sweeping Away Garbage

  • Để chỉ ra các đối tượng không còn được sử dụng, JVM chạy liên tục thuật toán mark-and-sweep. Có hai bước thực hiện:
    1. The algorithm traverses all object references, starting with the GC roots, and marks every object found as alive
    2. All of the heap memory that is not occupied by marked objects is relaimed. It is simply marked as free, essentially swept free of unused object.
Garbage collection is intended to remove the cause for classic memory leaks: unreachable-but-not-deleted objects in memory. However, this works only for memory leaks in the original sense. It’s possible to have unused objects that are still reachable by an application because the developer simply forgot to dereference them. Such objects cannot be garbage-collected. Even worse, such a logical memory leak cannot be detected by any software . Even the best analysis software can only highlight suspicious objects

Sunday, June 29, 2014

What is a compiler?

Máy tính chỉ có thể hiểu được một thứ ngôn ngữ duy nhất và ngôn ngữ này bao gồm các tập hợp các số 1 và 0, được gọi là machine language (mã máy).

Một đoạn mã máy đơn giản:
0000010011110

Đoạn mã máy cho phép người dùng nhập vào 2 số, và xuất ra tổng của 2 số đó
0000010011110
0000111110100
0001010011110
0001111010100
0010010111111
0010100000000

Bạn có thể hình dung rằng, lập trình cho máy tính trực tiếp bằng mã máy chỉ sử dụng 1 và 0 rất khó khăn và dễ bị lỗi. Để khiến việc lập trình dễ dàng hơn, các ngôn ngữ bậc cao đã được phát triển.

Dưới đây là đoạn mã được viết bằng ngôn ngữ C++, đoạn code này có chung mục đích với đoạn mã máy phía trên:
1
2
3
4
5
6
7
int a, b, sum;
     
cin >> a;
cin >> b;
             
sum = a + b;
cout << sum << endl;
Thậm nếu bạn không thực sự hiểu đoạn mã trên thì bạn cũng thấy rằng sẽ dễ dàng hơn khi lập trình bằng C++ thay vì bằng mã máy.

Máy tính chỉ có thể hiểu được mã máy, và con người thì lại muốn dùng ngôn ngữ bậc cao để lập trình do đó ngôn ngữ bậc cao phải được dịch thành mã máy. Do đó một chương trình đặc biệt gọi là compiler, interpreter hoặc assembler đã được tạo ra.

C++ được thiết kế để trở thành ngôn ngữ đã biên dịch. Hiểu chung chung là nó được dịch sang mã máy và khiến cho việc tạo ra các chương trình hiểu quả hơn. Để làm điều này, một bộ các công cụ được cung cấp, được biết đến như development toolchain, mà thành phần chính là compiler và linker. 

Android các nguyên tắc thiết kế

Các nguyên tắc này được phát triển bởi Android User Experience Team giúp người dùng cảm thấy thú vị khi trải nghiệm.

Enchant Me

Delight me in surprising ways


Vẻ ngoài đẹp, animation đặt đúng chỗ, sound effect đúng thời điểm. Một loạt các effect tinh tế sẽ khiến người dùng cảm thấy có một lực mạnh mẽ ở bàn tay của họ.

Real objects are more fun than buttons and menus


Cho phép người dùng chạm và thao tác trực tiếp lên các đối tượng trong app của bạn. Khiến cho người dùng có nhiều cảm xúc hơn.

 Let me make it mine


Mọi người thích thêm những thứ của riêng mình vào app, bởi vì nó giúp họ cảm thấy như ở nhà và có toàn quyền quết định. Bạn nên có một setting mặc định thật hợp lý, nhưng bên cạnh đó cũng cần có các sự lựa chọn và tùy chỉnh trong chương trình.

Get to know me


Hãy tìm hiểu và ghi nhớ sở thích của người dùng. Hơn là bắt họ đưa ra lựa chọn giống nhau hết lần này tới lần khác.


Simplify My Life

Keep it brief


Đưa ra các cụm từ đơn giản. Mọi người có xu hướng bỏ qua các câu nếu thấy chúng dài.

Pictures are faster than words


Hãy nghĩ đến việc sử dụng hình ảnh để trình bày ý tưởng của bạn. Hình ảnh khiến mọi người chú ý và mang lại nhiều hiệu quả hơn là dùng từ ngữ

Decide for me but let me have final say


Đưa ra các lời khuyên tốt nhất cho người dùng trước khi hỏi họ. Nếu có quá nhiều sự chọn lựa sẽ khiến người dùng cảm thấy khó chịu. Trong trường hợp họ làm sai điều gì đó hãy cho phép họ làm lại

Only show what i need when i need it


Người dùng bị quá tải nếu họ thấy quá nhiều thứ tại cùng một thời điểm. Hãy chia nhỏ các tác vụ, thông tin khiến chúng "dễ tiêu hóa". Hãy giấu đi những tùy chọn không cần thiết tại một thời điểm, và chỉ cho người dùng cách tìm ra nó.

I should always know where i am


Hãy cho người dùng biết họ đang ở đâu trong chương trình của bạn. Làm cho các phần trong app của bạn trông riêng biệt. Nến có một dấu hiệu nào đó để chỉ ra rằng app đang chuyển trạng thái qua trạng thái khác hoặc chuyển từ phần này qua phần khác. Phải có sự phản hồi nào đó của các task đang được xử lý.

Never lose my stuff


Cái này dịch ra thì hơi khó hiểu. Đại loại là nếu bạn có nhiều ứng dụng, mà các ứng dụng này đều lưu một số thông tin giống nhau của người dùng, thì thay vì bắt họ tạo lại các thông tin này bạn hãy đồng bộ hóa thông tin giữa các ứng dụng với nhau. Cho phép người dùng có thế lấy ra thông tin này ở bất cứ app nào.

If it looks the same, it should act the same


Cái này hiểu như là nếu bạn có hai cái button giống nhau thì hai cái button này phải thực hiện cùng một hành động. Tranh trường hợp hai button giống nhau thực hiện hai hành đông khác nhau.

Only interupt me if it's important


Tưởng tượng app của bạn như một người trợ lý. Hãy xử lý những điều vụn vặt không quan trọng cho người dùng sau đó thông báo lại cho họ thay vì đưa nó trực tiếp cho họ xử lý.


Make Me Amazing

Give me tricks that work everywhere


Người dùng sẽ nhanh chóng nắm bắt được cách sử dụng ứng dụng của bạn nếu nó tận dụng cách thiết kế và cách sử dụng của các ứng dụng Android khác.

It's not my fault


Hãy nhẹ nhàng khi hướng dẫn người dùng thực hiện các thao tác. Nếu họ làm điều gì đó không đúng, hãy đưa ra một hướng dẫn khôi phục rõ ràng kèm theo các thông tin kỹ thuật.

Sprinkle encouragement


Chia nhỏ các tác vụ khiến chúng dễ thực hiện hơn. Đưa ra các phản hồi trên các thao tác.

Do the heavy lifting for me


Khiến cho những người mới sử dụng cảm thấy mình chuyên nghiệp bằng cách cho phép họ làm những việc mà họ không bao giờ nghĩ mình làm được. Ví dụ chỉnh sửa hình ảnh với nhiều effect tuyệt vời qua một vài phím tắt sẽ khiến cho một photographer nghiệp dư trông thật cool :).

Make important things fast


Không phải thao tác nào cũng giống nhau. Chỉ ra cái nào là cái quan trọng nhất trong ứng dụng của bạn, làm cho nó dễ dàng được tìm thấy và nhanh chóng khi sử dụng. Như nút chụp hình của một cái camera hay là nút tạm dừng của máy nghe nhạc

Friday, June 27, 2014

Process và Thread

Khi một application component khởi chạy và application không có bất kỳ component nào khác đang chạy, Android system khởi động một Linux process mới cho application với một thread thực thi. Mặc định tất cả các component trong cùng một ứng dụng chạy trên cùng một process và thread(main thread). 


Process

Mặc định tất cả các component của cùng một Activity chạy trong cùng một process. Nếu bạn muốn kiểm soát process nào dành cho component nào thì bạn có thể làm việc đó tại file manifest.

Mỗi loại component <activity>, <service>, <receiver>, <provider> hỗ trợ một thuộc tính android:process mà chỉ ra một process mà component chạy trên. Có thể có các component của các ứng dụng khác nhau chạy trên cùng một process.

<application> hỗ trợ thuộc tính android:process để thiết lập process mặc định cho tất cả components

Android có thể kill một process tại một thời điểm bất kỳ khi bộ nhớ không đủ hoặc được yêu cầu bởi các process khác đang trực tiếp phục vụ người dùng. Process được khởi động lại cho các component đó khi chúng làm việc trở lại

Khi quyết định process nào sẽ bị kill, Android sẽ so sánh mức độ quan trọng của process đó với người dùng. Ví dụ android sẽ kill process đang host một activity trong trạng thái paused thay vì process đang host activity trong trạng thái resumed.

Process Lifecycle
Android system cố gắng duy trì một process của ứng dụng càng lâu càng tốt, nhưng khi có một process mới quan trọng nó sẽ xóa bỏ process cũ để lấy lại bộ nhớ. Để chỉ ra process nào sẽ bị kill, hệ thống sẽ gán cho mỗi process một "importance hierarchy" dựa trên các component  chạy trên process và trạng thái của các component  đó. Các process có mức độ importance thấp sẽ bị loại bỏ trước...

Có 5 cấp độ importance hierarchy như sau:

1. Foreground process:
Là một process được yêu cầu cho các hành động hiện hành của user. Một process được coi là foreground nếu bất kỳ điều kiện nào sau đây là đúng:
  • Nếu nó host một Activity mà user đang tương tác với (trạng thái activity là resumed)
  • Nếu host một Service mà rằng buộc với activity mà user tương tác
  • Nếu host một Service đang chạy ở foreground - service đã gọi phương thức startForeground()
  • Nếu nó host một Service mà đang thực thi một trong các lifecycle callback của nó (onCreate(), onStart() hoặc onDestroy()).
  • Nếu nó host một BroadcastReceiver mà đang thực thi onReceiver()
Thông thường chỉ có một vài foreground process tồn tại ở một thời điểm, và chúng chỉ bị kill khi bộ nhớ không đủ để chạy toàn bộ foreground process cùng lúc.

2. Visible process:
Là một process không có bất kỳ foregound component nào, nhưng vẫn có thể tác động tới nhưng gì user nhìn thấy trên màn hình. Một process visible nếu bất kì điều kiện nào sau đây đúng:

  • Nếu nó host cho một Activity mà không ở trạng thái foreground, nhưng vẫn visible cho user (khi hàm onPause() được gọi). Ví dụ: khi một foreground Activity khỏi động một Dialog, Activity lúc này sẽ hiện ỏ phía sau dialog
  • Nó host cho một Service mà ràng buộc với một visible Activity (foreground Activity)
Visible process rất quan trọng và sẽ không bị kill trừ khi việc kill chúng là để giữ cho foreground process chạy.

3. Serivce process
Một process chạy một service khi hàm startService() được gọi và không rơi vào một trong hai category cao hơn. Mặc dù service process không trực tiếp gắn với bất cứ thứ gì mà user có thể nhìn thấy nhưng chúng làm những việc mà user quan tâm (như là play music in background, hoặc là download dữ liệu từ trên mạng), hệ thống sẽ duy trì chúng đến khi không còn đủ bộ nhớ cho foreground và visible process chạy

4. Background process
Một process dữ một Activity mà không còn visible cho user (hàm onStop() của Activity được gọi). Process không còn tác động tới user experience, và hệ thống có thể kill chúng bất kỳ lúc nào để duy trì bộ nhớ cho 3 loại process bên trên. Thông thường có rất nhiều background process chạy, chúng được lưu trong một danh sách LRU(least recently used) để đảm bảo rằng process và activity nào vừa mới được user sử dụng sẽ bị kill sau cùng.  Nếu một activity implement các method của lifecycle một cách đúng đắn thì việc kill process đó sẽ không ảnh hưởng tới user experience, bởi vì khi user quay trở lại activty đó nó sẽ khôi phục lại toàn bộ trạng thái.

5. Empty process
Một process không dữ bất kỳ một active application component nào, lý do duy nhất để duy trì process này là để caching, cải thiện thời gian startup. Hệ thống thường xuyên kill các process này theo thứ tự để duy trì sự cân bằng tài nguyên của toàn bộ hệ thống giữa các process cache và các kernal cache cơ bản.


Lưu ý: Android xếp hạng process ở cấp độ cao nhất mà nó có thể đạt được, dựa trên mức độ quan trọng của các component đang được active trong process. Ví dụ, nếu một proess host cho một service và một visible activiy thì process được xếp hạng là một visible process chứ không phải là service process.

Cấp độ của process có thể tăng lên bởi vì có các process khác phụ thuộc vào nó - process mà đang phục vụ cho các process khác thì cấp độ sẽ không bao giờ thấp hơn process mà nó đang phục vụ. Ví du: nếu một content provider trong process A đang phục vụ một client trong process B hoặc một service trong process A rằng buộc với một component trong process B thì cấp độ process A luôn bằng hoặc lớn hơn B.


Threads

Khi ứng dụng được khởi động, hệ thống sẽ tạo ra một thread cho ứng dụng gọi là "main". Thread rất quan trọng bởi vì nó chịu trách nhiệm điều phối các Event tới các user interface widget thích hợp, bao gồm cả các draw events. Nó cũng là thread mà chương trình của bạn sử dụng để tương tác với các component từ Android UI toolkit (các component từ android.widget và android.view package). Main thread còn được gọi là UI thread.

Hệ thông không tạo ra thread riêng cho từng component. Tất cả các thành phần chạy trên cùng process đều được khởi tạo trên UI thread, và hệ thống sẽ gọi cho từng component được đưa ra từ thread đó. Vì thế các method mà phản hồi cho system callback(như onKeyDown() hoặc một lifecycle callback) lúc nào cũng chạy trên UI thread của process.

Ví dụ, khi user touch vào một button trên màn hình, UI thread của bạn sẽ gửi touch event cho widget để chuyển trạng thái của nó sang pressed và post một invalidate request vào event queue. UI thread sẽ lấy các request từ trong queue và notify cho widget mà cần phải redraw.

Khi ứng dụng của bạn thực hiện một công việc chuyên sâu để đáp ứng sự tương tác của người dùng. Mô hình sử dụng một thread có thể mạng lại hiệu suất kém. Đặc biệt nếu bạn làm mọi thứ trên UI thread, thực hiện các tình toán cần nhiều thời gian như là truy cập network hoặc truy vấn dữ liệu sẽ block UI. Khi UI bị block, không event nào được gửi đi bao gồm cả drawing event. Và theo góc nhìn của người dùng thì chương trinh đang bị treo. Tệ hơn nữa nếu chương trình treo lâu hơn 5s cửa sổ "Application not responding" sẽ hiện ra, người dùng có thể sẽ đóng chương trình lại và uninstall nó nếu họ cảm thấy bực bội.

Android UI toolket is not thread-safe. Vì vậy bạn không thể thao tác với UI từ một worker thread, bạn phải làm mọi thao tác cho UI thread trên UI thread. Có 2 nguyên tắc cơ bản bạn cần phải lưu ý:

  1. Không được block UI thread
  2. Không được truy cập Android UI toolkit từ bên ngoài UI thread.

Worker threads

Nếu bạn phải thực hiện các thao tác không tức thời, bạn phải đảm bảo rằng các thao tác đó được thực hiện trên các thread tách biệt với UI thread ("backgound" hoặc "worker" thread)

Ví dụ bên dưới, một đoạn code đơn giản thự hiện việc lắng nghe sự kiện click của user download một tấm hình sau đó hiển thị lên trong một thread riêng:


public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
            mImageView.setImageBitmap(b);
        }
    }).start();
}

Trước hết, ta thấy đoạng code này có vẻ ổn, bởi vì nó tạo ra một thread mới để xử lý nework operation. Tuy nhiên nó đã vi phạm nguyên tắc thứ 2 nêu trên. Ví dụ này đã modify ImageView từ một worker thread thay vì làm nó trong UI thread.

Để giải quết vấn đề này, Android đã cung cấp một vài cách để truy cập UI thread từ các thread khác:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable, long)
Ví dụ, chúng ta có thể fix đoạn code bên trên như sau:

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}
 Đoạn code trên là thread-safe: network operation được thực hiện trong thread riêng trong khi ImageView được xử lý trong UI thread.

Tuy nhiên nếu bạn code theo cách này thi chương trình của bạn sẽ rất phức tạp và khó phát triển lên. Để giải quyết những thao tác phức tạp hơn với một worker thread, bạn nên sử dụng Handler trong worker thread để gửi message cho UI thread. Nhưng giải pháp tốt nhất là extend AsyncTask class, điều này đơn giản hóa việc hoạt động của worker thread.

Sử dụng AsyncTask

AsyncTask cho phép bạn thực hiện các việc bất đồng bộ trên user interface. Nó thực hiện các công việc tốn nhiều thời gian trên worker thread sau đó thông báo kết quả về cho UI thread.

Để sử dụng bạn vào subclass AsynTask và implement phương thức callback doInBackground(), cái mà chạy trên backgroud thread. Để cập nhật UI, bạn phải implement phương thức onPostExecute(), phương thức này nhận kết quả từ doInBackground và chạy trên UI thread. Bạn có thể chạy task bằng cách gọi excute() trên UI thread.

Ví dụ:
public void onClick(View v) {
    new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
    /** The system calls this to perform work in a worker thread and
      * delivers it the parameters given to AsyncTask.execute() */
    protected Bitmap doInBackground(String... urls) {
        return loadImageFromNetwork(urls[0]);
    }
    
    /** The system c
om doInBackground() */
    protected void onPostExecute(Bitmap result) {
        mImageView.setImageBitmap(result);
    }
}

Chú ý: Một vấn đề khác có thể xuất hiện khi sử dụng một worker thread đó là Activity bị khởi động lại do runtime configuration thay đổi (như khi user xoay màn hình) có thế kill worker thread của bạn.


Sunday, April 27, 2014

Simple file manager for Android

Đây là chương trình đơn giản giúp bạn quản lý các file có trong sdcard của thiết bị Android

Các chức năng chính của chương trình gồm có:
  • Hiển thị các file có trong sdcard theo dạng danh sách.
  • Cho phép người dùng thực hiện các thao tác như cut, copy, delete trên một hoặc nhiều file
Bạn có thể tải source code chương trình tại đây.

Một số điểm đáng chú ý trong chương trình:

  • Các bạn cần phải khai báo permission trong file Manifest để có thể tương tác được với sdcard

  • Thuật toán đệ quy:
public void copyFolder(File src, File dest) throws IOException {
  if (src.isDirectory()) {
   if (!dest.exists()) {
    dest.mkdir();
   }
   String[] files = src.list();
   for (String file : files) {
    File srcFile = new File(src, file);
    File destFile = new File(dest, file);
    copyFolder(srcFile, destFile);
   }
  } else {
   InputStream in = new FileInputStream(src);
   OutputStream out = new FileOutputStream(dest);
   byte[] buffer = new byte[1024];
   int length;
   while ((length = in.read(buffer)) > 0) {
    out.write(buffer, 0, length);
   }
   in.close();
   out.close();
  }
 }
Để copy 1 folder ta phải copy toàn bộ file của nó, đối với các subfolder ta cũng tiến hành làm tương tự, vì vậy để giải quyết bài toán này ta sử dụng thuật toán đệ quy như trên.

Chúng ta cũng sử dụng thuật toán đệ quy để delete 1 folder.

  • Toàn bộ các function của chương trình đều chạy trên UIThread, điều này khiến chương trình không được mượt mà, nhất là khi các bạn copy, cut, delete các file có kích thước lớn, vì vậy các bạn hãy sũy nghĩ cách sử dụng Multithreading để cải tiến chương trình.


Tuesday, April 15, 2014

ActionBar và ListView

Bài viết này sẽ hướng dẫn các bạn sử dụng ActionBar kết hợp với ListView trong Android. Các bạn có thể xem bài viết về ListView tại đây.

Chương trình hoàn thiện sẽ có giao diện như hình bên dưới, phần ListView sẽ hiển thị danh sách các thư mục có trong sdcard, cho phép chọn nhiều item trong ListView, lọc các file bằng search box trên ActionBar.

ActionBar xuất hiện trong Android từ phiên bản 3.0(API level 11), nhưng bạn có thể sử dụng ActionBar thông qua Support Library cho các phiên bản từ 2.1(API level 7) trở lên. Trong bài viết này mình sẽ sử dụng Support Library, các bạn có thể tìm hiểu các thức setup Support Library tại đây.

Đầu tiên các bạn tiến hành tạo project mới như hình bên dưới 
Các thư mục chính trong project:
  • scr gồm có 2 class:
    • CustomListViewAdapter.java: sử dụng cho ListView
    • MainActivity.java: chứa các thành phần chính của chương trình.
  • drawable:
    • Hai file hình ảnh là icon của file và folder
    • background_selected.xml, background_deselected.xml, list_item_background_selector.xml được dùng cho các trạng thái khác nhau của ListView item.
  • layout, menu:
    • activity_main.xml: định nghĩa giao diện chính của chương trình
    • list_item.xml: giao diện của một item trong ListView
    • action_mode.xml: định nghĩa menu trong action mode.
    • main.xml: menu chính của chương trình.
  • AndroidManifest.xml:



    

    
    

    
        
            
                

                
            
        
    

  • activity_main.xml:



    
    




  • list_item.xml:



    

    

        

        

        
    




  • list_item_background_selector.xml:



    
    
    




  • background_selected.xml:





  • background_deselected.xml:






  • action_mode.xml:



    
    
    




  • main.xml:



  • CustomListViewAdapter.java:

public class CustomListViewAdapter extends BaseAdapter {
 
 private LayoutInflater inflater;
 private File[] listFile;
 private Context context;
 
 public CustomListViewAdapter(Context context, File[] files){
  this.context = context;
  inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  listFile = files;
 }
 

 @Override
 public int getCount() {
  return listFile.length;
 }

 @Override
 public Object getItem(int position) {
  return listFile[position];
 }

 @Override
 public long getItemId(int position) {
  return position;
 }

 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
  if(convertView == null)
   convertView = inflater.inflate(R.layout.list_item, null);
  ImageView iv = (ImageView) convertView.findViewById(R.id.iv_icon);
  TextView tvFileName = (TextView) convertView.findViewById(R.id.tv_file_name);
  TextView tvFileSize = (TextView) convertView.findViewById(R.id.tv_file_size);
  TextView tvDateModify = (TextView) convertView.findViewById(R.id.tv_date_modify);
  File file = listFile[position];
  long size = 0;
  if(file.isDirectory()){
   size = folderSize(file);
   iv.setImageDrawable(context.getResources().getDrawable(R.drawable.folder_icon));
  }
  else{
   iv.setImageDrawable(context.getResources().getDrawable(R.drawable.file_icon));
   size = file.length();
  }
  tvFileName.setText(file.getName());
  tvFileSize.setText(readableFileSize(size));
  tvDateModify.setText(convertTime(file.lastModified()));
  return convertView;
 }
 
 public static String readableFileSize(long size) {
     if(size <= 0) return "0 B";
     final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" };
     int digitGroups = (int) (Math.log10(size)/Math.log10(1024));
     return new DecimalFormat("#,##0.#").format(size/Math.pow(1024, digitGroups)) + " " + units[digitGroups];
 }
 
 public String convertTime(long time){
     Date date = new Date(time);
     Format format = DateFormat.getDateInstance();
     return format.format(date).toString();
 }
 
 public static long folderSize(File directory) {
     long length = 0;
     for (File file : directory.listFiles()) {
         if (file.isFile())
             length += file.length();
         else
             length += folderSize(file);
     }
     return length;
 }

}

  • MainActivity.java:

Các bạn có thể tải source code project tại đây

Thursday, April 10, 2014

Custom ListView

Chào các bạn bài viết này mình sẽ hướng dẫn các bạn tạo ra một custom ListView trong Android.

Sau khi hoàn thành chương trình sẽ trông như hình bên dưới:

Đây là một chương trình đơn giản, sử dụng ListView để hiển thị toàn bộ file và folder trong sdcard của thiết bị Android.

Đầu tiên các bạn tạo một android project như hình bên dưới:
Đây là cấu trúc thư mục của project sau khi tạo xong:
  • CustomListViewAdapter.java: lớp này là Adapter, đóng vai trò trung gian giữa giữ liệu(data) và ListView.
  • MainActivity.java: lớp này chịu trách nhiệm hiển thị giao diện chính của chương trình.
  • activity_main.xml: là file định nghĩa layout chính của chương trình.
  • list_item.xml: là file định nghĩa layout của một  item trong ListView.

Để có thể thao tác được với sdcard chúng ta phải thêm permission vào trong file AndroidManifest.xml
activity_main.xml:




    
    


list_item.xml:




    

    

    

        

        

        
    


CustomListViewAdapter.java:


public class CustomListViewAdapter extends BaseAdapter {
 
 private LayoutInflater inflater;
 private File[] listFile;
 private Context context;
 
 public CustomListViewAdapter(Context context, File file){
  this.context = context;
  inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  listFile = file.listFiles();
 }
 

 @Override
 public int getCount() {
  return listFile.length;
 }

 @Override
 public Object getItem(int position) {
  return listFile[position];
 }

 @Override
 public long getItemId(int position) {
  return position;
 }

 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
  if(convertView == null)
   convertView = inflater.inflate(R.layout.list_item, null);
  ImageView iv = (ImageView) convertView.findViewById(R.id.iv_icon);
  TextView tvFileName = (TextView) convertView.findViewById(R.id.tv_file_name);
  TextView tvFileSize = (TextView) convertView.findViewById(R.id.tv_file_size);
  TextView tvDateModify = (TextView) convertView.findViewById(R.id.tv_date_modify);
  File file = listFile[position];
  long size = 0;
  if(file.isDirectory()){
   size = folderSize(file);
   iv.setImageDrawable(context.getResources().getDrawable(R.drawable.folder_icon));
  }
  else{
   iv.setImageDrawable(context.getResources().getDrawable(R.drawable.file_icon));
   size = file.length();
  }
  tvFileName.setText(file.getName());
  tvFileSize.setText(readableFileSize(size));
  tvDateModify.setText(convertTime(file.lastModified()));
  return convertView;
 }
 
 public static String readableFileSize(long size) {
     if(size <= 0) return "0 B";
     final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" };
     int digitGroups = (int) (Math.log10(size)/Math.log10(1024));
     return new DecimalFormat("#,##0.#").format(size/Math.pow(1024, digitGroups)) + " " + units[digitGroups];
 }
 
 public String convertTime(long time){
     Date date = new Date(time);
     Format format = new SimpleDateFormat("yyyy MM dd HH:mm:ss");
     return format.format(date).toString();
 }
 
 public static long folderSize(File directory) {
     long length = 0;
     for (File file : directory.listFiles()) {
         if (file.isFile())
             length += file.length();
         else
             length += folderSize(file);
     }
     return length;
 }

}

MainActivity.java:


public class MainActivity extends ActionBarActivity {

 private ListView listView;
 private CustomListViewAdapter adapter;
 private File file;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  if (isExternalStorageWritable()) {
   listView = (ListView) findViewById(R.id.lv_file_view);
   file = Environment.getExternalStorageDirectory();
   adapter = new CustomListViewAdapter(this, file);
   listView.setAdapter(adapter);
  }
  else{
   Toast.makeText(this, "Can not interact with External Storage!", Toast.LENGTH_LONG).show();
  }
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  // Handle action bar item clicks here. The action bar will
  // automatically handle clicks on the Home/Up button, so long
  // as you specify a parent activity in AndroidManifest.xml.
  int id = item.getItemId();
  if (id == R.id.action_settings) {
   return true;
  }
  return super.onOptionsItemSelected(item);
 }

 /* Checks if external storage is available for read and write */
 public boolean isExternalStorageWritable() {
  String state = Environment.getExternalStorageState();
  if (Environment.MEDIA_MOUNTED.equals(state)) {
   return true;
  }
  return false;
 }
}
link dowload source project: source