分类: Android
Android是一种基于Linux的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由Google公司和开放手机联盟领导及开发。
Using a Thread Pool in Android
Android上使用线程池

In my last post Using HanderThread in Android, I showed how to offload short blocking tasks to a worker thread. While HandlerThread is good for tasks running in a sequential manner, there are cases where background tasks do not have dependencies on each other. To get these tasks done as quickly as possible, you might want to exploit the powerful multi-core processor on the mobile device and run the tasks concurrently on more than one worker thread.
A thread pool is a good fit for this scenario. Thread pool is a single FIFO task queue with a group of worker threads. The producers (E.g. the UI thread) sends tasks to the task queue. Whenever any worker threads in the thread pool become available, they remove the tasks from the front of the queue and start running them.
Comparing with starting a random number of individual worker threads, using a thread pool prevent the overhead of killing and recreating threads every time a worker thread is needed. It also gives you fine control over the number of threads and their lifecycle. E.g. ThreadPoolExecutor allows you to specify how many core threads, how many max threads the pool should create and the keep alive time for the idle threads.
Android supports Java’s Executor framework which offers the following classes for using a thread pool.
- Executor: an interface which has a execute method. It is designed to decouple task submission from running.
- Callable: An Interface similar to runnable but allow a result to be returned.
- Future: Like a promise in JavaScript. It represents the result for an asynchronous task.
- ExecutorService: an interface which extends Executor interface. It is used to manage threads in the threads pool.
- ThreadPoolExecutor: a class that implements ExecutorService which gives fine control on the thread pool (Eg, core pool size, max pool size, keep alive time, etc.)
- ScheduledThreadPoolExecutor: a class that extends ThreadPoolExecutor. It can schedule tasks after a given delay or periodically.
- Executors: a class that offers factory and utility methods for the aforementioned classes.
- ExecutorCompletionService: a class that arranges submitted task to be placed on a queue for accessing results.
Basic Thread Pool
The simplest way of creating a thread pool is to use one of the factory methods from Executors class.
|
1 2 3 4 5 6 7 |
static final int DEFAULT_THREAD_POOL_SIZE = 4; ExecutorService executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); ExecutorService executorService = Executors.newCachedThreadPool(); ExecutorService executorService = Executors.newSingleThreadExecutor(); |
newFixedThreadPool creates a thread pool with a a fixed number of thread in the pool specified by the user. The user can call setCorePoolSized(int) later to resize the thread pool.
newCachedThreadPool creates a new thread when there is a task in the queue. When there is no tasks in the queue for 60 seconds, the idle threads will be terminated.
newSingleThreadExecutor creates a thread pool with only one thread.
To add a task to the thread pool, call one of the following methods.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
executorService.execute(new Runnable(){ @Override public void run(){ callBlockingFunction(); } }); Future future = executorService.submit(new Callable(){ @Override public Object call() throws Exception { callBlockingFunction(); return null; } }); |
The second method returns a future object. It can be used to retrieve the result from the callable by calling future.get() or cancel the task by calling future.cancel(boolean mayInterruptIfRunning).
Advanced Thread Pool
If you want to have finer control over the thread pool, ThreadPoolExecutor class can be used. In the following example, I first find the available processors of the phone. The thread pool is configured to have core size as the NUMBER_OF_CORES, the maximum core size as the NUMBER_OF_CORES x 2, idle threads’ keep-alive time as 1 second, task queue as a LinkedBlockingQueue object and a custom thread factory.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors(); int KEEP_ALIVE_TIME = 1; TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS; BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>(); ExecutorService executorService = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES*2, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, taskQueue, new BackgroundThreadFactory()); private static class BackgroundThreadFactory implements ThreadFactory { private static int sTag = 1; @Override public Thread newThread(Runnable runnable) { Thread thread = new Thread(runnable); thread.setName("CustomThread" + sTag); thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND); // A exception handler is created to log the exception from threads thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable ex) { Log.e(Util.LOG_TAG, thread.getName() + " encountered an error: " + ex.getMessage()); } }); return thread; } } |
Cancel Tasks
To stop the tasks in the task queue from execution, we just need to clear the task queue. To allow the running threads to be stopped, store all future objects in a list and call cancel on every object which is not done.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// Add a callable to the queue, which will be executed by the next available thread in the pool public void addCallable(Callable callable){ Future future = mExecutorService.submit(callable); mRunningTaskList.add(future); } /* Remove all tasks in the queue and stop all running threads * Notify UI thread about the cancellation */ public void cancelAllTasks() { synchronized (this) { mTaskQueue.clear(); for (Future task : mRunningTaskList) { if (!task.isDone()) { task.cancel(true); } } mRunningTaskList.clear(); } sendMessageToUiThread(Util.createMessage(Util.MESSAGE_ID, "All tasks in the thread pool are cancelled")); } |
Handle Activity Lifecycle
One thing the thread pool framework does not handle is the Android activity lifecycle. If you want your thread pool to survive the activity lifecycle and reconnect to your activity after it is re-created (E.g. after an orientation change), it needs to be created and maintained outside the activity.
In my example, I made a static singleton class called CustomThreadPoolManager. It has a private constructor. It creates an instance of itself and return that single instance in the static getInstance method. It also holds a weak reference to the Activity. The reference is later used to communicate with the UI thread (see the next section).
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
public class CustomThreadPoolManager { private static CustomThreadPoolManager sInstance = null; private static int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors(); private static final int KEEP_ALIVE_TIME = 1; private static final TimeUnit KEEP_ALIVE_TIME_UNIT; private final ExecutorService mExecutorService; private final BlockingQueue<Runnable> mTaskQueue; private List<Future> mRunningTaskList; private WeakReference<UiThreadCallback> uiThreadCallbackWeakReference; // The class is used as a singleton static { KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS; sInstance = new CustomThreadPoolManager(); } // Made constructor private to avoid the class being initiated from outside private CustomThreadPoolManager() { // initialize a queue for the thread pool. New tasks will be added to this queue mTaskQueue = new LinkedBlockingQueue<Runnable>(); mRunningTaskList = new ArrayList<>(); mExecutorService = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES*2, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, mTaskQueue, new BackgroundThreadFactory()); } public static CustomThreadPoolManager getsInstance() { return sInstance; } ... // Keep a weak reference to the UI thread, so we can send messages to the UI thread public void setUiThreadCallback(UiThreadCallback uiThreadCallback) { this.uiThreadCallbackWeakReference = new WeakReference<UiThreadCallback>(uiThreadCallback); } ... } |
In the Activity, get the thread pool singleton instance by calling the getInstance static method. Set the activity to the CustomThreadPoolManager. As CustomThreadPoolManager keeps the reference to the Activity as a weak reference, you don’t need to worry about leaking the Activity.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class MainActivity extends AppCompatActivity implements UiThreadCallback { private CustomThreadPoolManager mCustomThreadPoolManager; ... @Override protected void onStart() { super.onStart(); // get the thread pool manager instance mCustomThreadPoolManager = CustomThreadPoolManager.getsInstance(); // CustomThreadPoolManager stores activity as a weak reference. No need to unregister. mCustomThreadPoolManager.setUiThreadCallback(this); } // onClick handler for Send 4 Tasks button public void send4tasksToThreadPool(View view) { for(int i = 0; i < 4; i++) { CustomCallable callable = new CustomCallable(); callable.setCustomThreadPoolManager(mCustomThreadPoolManager); mCustomThreadPoolManager.addCallable(callable); } } ... } |
Communicate with UI Thread
When each task finishes, you may need to send some data back to the UI thread. A safe way of doing this is to send a message to the handler of the UI thread. First, extend Handler class and define what the UI thread should do when a message is received.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
UiHandler mUiHandler; ... @Override protected void onStart() { super.onStart(); // Initialize the handler for UI thread to handle message from worker threads mUiHandler = new UiHandler(Looper.getMainLooper(), mDisplayTextView); ... } ... // Send message from worker thread to the UI thread @Override public void publishToUiThread(Message message) { // add the message from worker thread to UI thread's message queue if(mUiHandler != null){ mUiHandler.sendMessage(message); } } ... // UI handler class, declared as static so it doesn't have implicit // reference to activity context. This helps to avoid memory leak. private static class UiHandler extends Handler { private WeakReference<TextView> mWeakRefDisplay; public UiHandler(Looper looper, TextView display) { super(looper); this.mWeakRefDisplay = new WeakReference<TextView>(display); } // This method will run on UI thread @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ // Our communication protocol for passing a string to the UI thread case Util.MESSAGE_ID: Bundle bundle = msg.getData(); String messageText = bundle.getString(Util.MESSAGE_BODY, Util.EMPTY_MESSAGE); if(null != mWeakRefDisplay) { TextView refDisplay = mWeakRefDisplay.get(); if(null != refDisplay) { mWeakRefDisplay.get().append(Util.getReadableTime() + " " + messageText + "\n"); } } break; default: break; } } } |
In the CustomThreadPoolManager, use the Activity’s weak reference to send the message to the UI thread.
|
1 2 3 4 5 6 7 8 |
public void sendMessageToUiThread(Message message){ if(null != uiThreadCallbackWeakReference) { final UiThreadCallback uiThreadCallback = uiThreadCallbackWeakReference.get(); if(null != uiThreadCallback) { uiThreadCallback.publishToUiThread(message); } } } |
In the CustomCallable, as it has reference to the CustomThreadPoolManager, it can send the message by calling CustomThreadPoolManager’s sendMessageToUiThread method.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
@Override public Object call() throws Exception { try { // check if thread is interrupted before lengthy operation if (Thread.interrupted()) throw new InterruptedException(); // In real world project, you might do some blocking IO operation // In this example, I just let the thread sleep for 3 second Thread.sleep(3000); // After work is finished, send a message to CustomThreadPoolManager Message message = Util.createMessage(Util.MESSAGE_ID, "Thread " + String.valueOf(Thread.currentThread().getId()) + " " + String.valueOf(Thread.currentThread().getName()) + " completed"); if(null != mCustomThreadPoolManagerWeakReference) { final CustomThreadPoolManager customThreadPoolManager = mCustomThreadPoolManagerWeakReference.get(); if(null != customThreadPoolManager) { customThreadPoolManager.sendMessageToUiThread(message); } } } catch (InterruptedException e) { e.printStackTrace(); } return null; } |
Source Code
The full source code for the example used in this post is available on Github.
也可本站下载一份 代码拷贝。
参考链接
Activity singleInstance/singleTop启动模式的理解
Android有四种启动模式,分别是standard,singleTop,singleTask,singleInstance。下面分别简单的介绍下这四种启动模式的作用。
standard
Android 默认的一种启动模式。不需要为activity设置launchMode。这种启动模式简单的来说就是当你startActivity的时候,他就创建一个。
singleTop
这种模式模式从字面意思就能看得出来,就是当前的activity处于栈顶的时候,当你startActivity当前的activity的时候,它不会创建新的activity,而是会复用之前的activity。举个例子,startActivity了一个ActivityA,ActivityA又startActivity了ActivityB,当在ActivityB再次startActivity一个ActivityB的时候,它不会创建一个新的ActivityB,而是复用之前的ActivityB。
这里需要注意的是,只有当前的activity处于栈顶的时候才管用。举个例子:startActivity了一个ActivityA,ActivityA又startActivity了ActivityB,ActivityB又startActivity了ActivityA,那么ActivityA还是会重新创建,而不是复用之前的ActivityA。
singleTask
单一任务。意思就是说当前的activity只有一个实例,无论在任何地方startActivity出来这个activity,它都只存在一个实例。并且,它会将在他之上的所有activity都销毁。通常这个activity都是用来作为MainActivity。因为主页只需要存在一个,然后回到主页的时候可以将所有的activity都销毁起到退出应用的作用。举个例子,startActivity了一个ActivityA,ActivityA的启动模式为singleTask,那么在ActivityA里startActivity了一个ActivityB,在ActivityB里startActivity了一个ActivityC。此时在当前的任务栈中的顺序是,ActivityA->ActivityB->ActivityC。然后在ActivityC里重新startActivity了一个ActivityA,此时ActivityA会将存在于它之上的所有activity都销毁。所以此时任务栈中就只剩下ActivityA了。
singleInstance
这个模式才是重点,也是比较容易入坑的一种启动模式。字面上理解为单一实例。它具备所有singleTask的特点,唯一不同的是,它是存在于另一个任务栈中。上面的三种模式都存在于同一个任务栈中,而这种模式则是存在于另一个任务栈中。
注意事项
以往的理解中,只要在AndroidManifest.xml中声明singleTop或者singleInstance,那么在使用startActivity或者startActivityForResult的时候,自动就会保证singleInstance的情况下只有一个对象,singleTop的情况下不会两个相同的Activity叠加在一起。
但是现实是让人崩溃的。
使用如下的代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
package com.mobibrw.myapplication; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity { private final static int SINGLE_INSTANCE = 1; private final static int SINGLE_TOP = 2; private void startSingleInstanceActivity() { final Intent intent = new Intent(this, SingleInstanceActivity.class); //intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivityForResult(intent, SINGLE_INSTANCE); } private void startSingleTopActivity() { final Intent intent = new Intent(this, SingleTopActivity.class); //intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivityForResult(intent, SINGLE_TOP); } private void startSingleInstanceWithFlagsActivity() { final Intent intent = new Intent(this, SingleInstanceActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivityForResult(intent, SINGLE_INSTANCE); } private void startSingleTopWithFlagsActivity() { final Intent intent = new Intent(this, SingleTopActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivityForResult(intent, SINGLE_TOP); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btnSingleTop = findViewById(R.id.SingleTop); btnSingleTop.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { startSingleTopActivity(); startSingleTopActivity(); } }); Button btnSingleInstance = findViewById(R.id.SingleInstance); btnSingleInstance.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { startSingleInstanceActivity(); startSingleInstanceActivity(); } }); Button btnSingleTopWithFlags = findViewById(R.id.SingleTopWithFlags); btnSingleTopWithFlags.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { startSingleTopWithFlagsActivity(); startSingleTopWithFlagsActivity(); } }); Button btnSingleInstanceWithFlags = findViewById(R.id.SingleInstanceWithFlags); btnSingleInstanceWithFlags.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { startSingleInstanceWithFlagsActivity(); startSingleInstanceWithFlagsActivity(); } }); } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mobibrw.myapplication"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".SingleInstanceActivity" android:launchMode="singleInstance"/> <activity android:name=".SingleTopActivity" android:launchMode="singleTop"/> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> |
在两次触发startSingleTopActivity/startSingleInstanceActivity的时候,出现了两个叠加的Activity,如下:
|
1 2 3 4 5 6 7 8 |
$ adb shell dumpsys activity | grep SingleTopActivity Hist #2: ActivityRecord{6f4fc4 u0 com.mobibrw.myapplication/.SingleTopActivity t457} Intent { cmp=com.mobibrw.myapplication/.SingleTopActivity } Hist #1: ActivityRecord{4a4ec71 u0 com.mobibrw.myapplication/.SingleTopActivity t457} Intent { cmp=com.mobibrw.myapplication/.SingleTopActivity } Run #1: ActivityRecord{6f4fc4 u0 com.mobibrw.myapplication/.SingleTopActivity t457} mResumedActivity: ActivityRecord{6f4fc4 u0 com.mobibrw.myapplication/.SingleTopActivity t457} mFocusedActivity: ActivityRecord{6f4fc4 u0 com.mobibrw.myapplication/.SingleTopActivity t457} |
可以看到出现两个单独的实例 Hist #2/Hist #1,而不是预期的忽略第二次调用。
在两次触发startSingleTopWithFlagsActivity/startSingleInstanceWithFlagsActivity的时候,只会出现了一个Activity,如下:
|
1 2 3 4 5 6 |
$ adb shell dumpsys activity | grep SingleTopActivity Hist #1: ActivityRecord{933a335 u0 com.mobibrw.myapplication/.SingleTopActivity t457} Intent { flg=0x420000 cmp=com.mobibrw.myapplication/.SingleTopActivity } Run #1: ActivityRecord{933a335 u0 com.mobibrw.myapplication/.SingleTopActivity t457} mResumedActivity: ActivityRecord{933a335 u0 com.mobibrw.myapplication/.SingleTopActivity t457} mFocusedActivity: ActivityRecord{933a335 u0 com.mobibrw.myapplication/.SingleTopActivity t457} |
两者的区别就是
|
1 |
intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); |
这句代码。
还是学艺不精啊。
完整例子代码,点击此处下载 MyApplication
参考链接
Java Calendar 获取上下午
|
1 2 3 4 5 6 7 8 9 |
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); Date date = sdf.parse(yourdate); Calendar cal=Calendar.getInstance(); if(null!=date) { cal.setTime(date); if (Calendar.AM == cal.get(Calendar.AM_PM)) { return "上午"; } } |
参考链接
华为手机配置显示返回键
使用华为Honor V8习惯了Android屏幕最下方的三个操作按键(返回/Home/列表),三个按键所在的位置被称之为"导航栏"。
最近换了华为Honor 30,想要点返回键时,却发现手机屏幕上没有返回键。手势操作非常不方便,经常误操作。而且有些界面适配的很不好,界面上没有设置回退功能。当缺少系统层面的返回按键的时候,只能强制退出应用。
其实这个返回键是在导航键里,需要设置才会显示。下面几个步骤就教你如何设置返回键:
Gradle: 一个诡异的问题(ERROR: Failed to parse XML AndroidManifest.xml ParseError at [row,col]:[5,5] Message: expected start or end tag)
今天同事说他下了一个老版本的Android Studio项目死活编不过,我心想不就是一个项目么,编不过要么就是代码有问题,要么就是依赖库不完整这能有什么问题,于是自己在自己电脑试了下,结果自己也中招了:
Android 5.x新特性之elevation(阴影),tinting(着色)以及clipping(剪裁)
研究了Google I/O 2014 发布 Material Design设计,人性化的风格,丰富的色彩,使人机交互更完美。中文学习地址http://wiki.jikexueyuan.com/project/material-design/(这个好像是极客学院翻译的),当然如果你的引文OK的话,也可以去看官方英文文档http://www.google.com/design/spec/material-design/。
1. 阴影以及高度--elevation
Android获取SD卡路径及SDCard内存的方法
Android获取SD卡路径及SDCard内存的方法。
代码如下:
|
1 2 3 4 5 6 7 8 9 10 |
public String getSDPath(){ File sdDir = null; boolean sdCardExist = Environment.getExternalStorageState() .equals(android.os.Environment.MEDIA_MOUNTED);//判断sd卡是否存在 if(sdCardExist) { sdDir = Environment.getExternalStorageDirectory();//获取跟目录 } return sdDir.toString(); } |
然后:在后面加上斜杠,在加上文件名
如下:
|
1 |
String fileName = getSDPath() +"/" + name;//以name存在目录中 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
1、讲述 Environment 类 Environment 是一个提供访问环境变量的类。 Environment 包含常量: MEDIA_BAD_REMOVAL 解释:返回getExternalStorageState() ,表明SDCard 被卸载前己被移除 MEDIA_CHECKING 解释:返回getExternalStorageState() ,表明对象正在磁盘检查。 MEDIA_MOUNTED 解释:返回getExternalStorageState() ,表明对象是否存在并具有读/写权限 MEDIA_MOUNTED_READ_ONLY 解释:返回getExternalStorageState() ,表明对象权限为只读 MEDIA_NOFS 解释:返回getExternalStorageState() ,表明对象为空白或正在使用不受支持的文件系统。 MEDIA_REMOVED 解释:返回getExternalStorageState() ,如果不存在 SDCard 返回 MEDIA_SHARED 解释:返回getExternalStorageState() ,如果 SDCard 未安装 ,并通过 USB 大容量存储共享 返回 MEDIA_UNMOUNTABLE 解释:返回getExternalStorageState() ,返回 SDCard 不可被安装 如果 SDCard 是存在但不可以被安装 MEDIA_UNMOUNTED 解释:返回getExternalStorageState() ,返回 SDCard 已卸掉如果 SDCard 是存在但是没有被安装 Environment 常用方法: 方法:getDataDirectory() 解释:返回 File ,获取 Android 数据目录。 方法:getDownloadCacheDirectory() 解释:返回 File ,获取 Android 下载/缓存内容目录。 方法:getExternalStorageDirectory() 解释:返回 File ,获取外部存储目录即 SDCard 方法:getExternalStoragePublicDirectory(String type) 解释:返回 File ,取一个高端的公用的外部存储器目录来摆放某些类型的文件 方法:getExternalStorageState() 解释:返回 File ,获取外部存储设备的当前状态 方法:getRootDirectory() 解释:返回 File ,获取 Android 的根目录 2、讲述 StatFs 类 StatFs 一个模拟linux的df命令的一个类,获得SD卡和手机内存的使用情况 StatFs 常用方法: getAvailableBlocks() 解释:返回 Int ,获取当前可用的存储空间 getBlockCount() 解释:返回 Int ,获取该区域可用的文件系统数 getBlockSize() 解释:返回 Int ,大小,以字节为单位,一个文件系统 getFreeBlocks() 解释:返回 Int ,该块区域剩余的空间 restat(String path) 解释:执行一个由该对象所引用的文件系统 3、完整例子读取 SDCard 内存 存储卡在 Android 手机上是可以随时插拔的,每次的动作都对引起操作系统进行 ACTION_BROADCAST,本例子将使用上面学到的方法,计算出 SDCard 的剩余容量和总容量。代码如下: |
代码如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
package com.terry; import java.io.File; import java.text.DecimalFormat; import android.R.integer; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.os.StatFs; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; public class getStorageActivity extends Activity { private Button myButton; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); findView(); viewHolder.myButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub getSize(); } }); } void findView(){ viewHolder.myButton=(Button)findViewById(R.id.Button01); viewHolder.myBar=(ProgressBar)findViewById(R.id.myProgressBar); viewHolder.myTextView=(TextView)findViewById(R.id.myTextView); } void getSize(){ viewHolder.myTextView.setText(""); viewHolder.myBar.setProgress(0); //判断是否有插入存储卡 if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ File path =Environment.getExternalStorageDirectory(); //取得sdcard文件路径 StatFs statfs=new StatFs(path.getPath()); //获取block的SIZE long blocSize=statfs.getBlockSize(); //获取BLOCK数量 long totalBlocks=statfs.getBlockCount(); //己使用的Block的数量 long availaBlock=statfs.getAvailableBlocks(); String[] total=filesize(totalBlocks*blocSize); String[] availale=filesize(availaBlock*blocSize); //设置进度条的最大值 int maxValue = Integer.parseInt(availale[0]) * viewHolder.myBar.getMax() / Integer.parseInt(total[0]); viewHolder.myBar.setProgress(maxValue); String Text="总共:" + total[0]+total[1]+"/n" + "可用:"+availale[0] + availale[1]; viewHolder.myTextView.setText(Text); } else if(Environment.getExternalStorageState().equals(Environment.MEDIA_REMOVED)){ Toast.makeText(getStorageActivity.this, "没有sdCard",1000).show(); } } //返回数组,下标1代表大小,下标2代表单位 KB/MB String[] filesize(long size){ String str=""; if(size>=1024){ str="KB"; size/=1024; if(size>=1024){ str="MB"; size/=1024; } } DecimalFormat formatter=new DecimalFormat(); formatter.setGroupingSize(3); String result[] =new String[2]; result[0]=formatter.format(size); result[1]=str; return result; } } |
参考链接
Android 获取View在屏幕中的位置
Android里面提供了一些方法可以获取View在屏幕中的位置。
getLocationOnScreen,计算该视图在全局坐标系中的x,y值,获取在当前屏幕内的绝对坐标(该值从屏幕顶端算起,包括了通知栏高度)。
getLocationInWindow,计算该视图在它所在的widnow的坐标x,y值。
getLeft, getTop, getBottom, getRight, 这一组是获取相对在它父亲布局里的坐标。
注意:如果在Activity的OnCreate()事件输出那些参数,是全为0,要等UI控件都加载完了才能获取到这些数据。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
button2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { int[] location = new int[2]; v.getLocationOnScreen(location); x = location[0]; y = location[1]; Log.d("test", "Screenx--->" + x + " " + "Screeny--->" + y); v.getLocationInWindow(location); x = location[0]; y = location[1]; Log.d("test", "Window--->" + x + " " + "Window--->" + y); Log.d("test", "left:" + v.getLeft()); Log.d("test", "right:" + v.getRight()); Log.d("test", "Top:" + v.getTop()); Log.d("test", "Bottom:" + v.getBottom()); Log.d("test", "Width:"+v.getWidth()); Log.d("test", "Height:"+v.getHeight()); } |
getDimension()、getDimensionPixelSize()和getDimensionPixelOffset()的区别
今天写代码的时候,需要从资源文件中读取一个长度值,用来设置控件的宽高,误以为getDimension()返回的就是资源里定义的dp值,后来发现不是我理解的那样。下面介绍一下getDimension()、getDimensionPixelSize()和getDimensionPixelOffset()的区别;
先看我在资源中的定义:
<dimen name="guide_first_cover_image_width">171dp</dimen>
然后打印出它们的值看看
QLog.i(TAG,String.format("getDimension: %f",mContext.getResources().getDimension(R.dimen.guide_first_cover_image_width)) );
QLog.i(TAG,String.format("getDimensionPixelSize: %d",mContext.getResources().getDimensionPixelSize(R.dimen.guide_first_cover_image_width)));
QLog.i(TAG,String.format("getDimensionPixelOffset:%d",mContext.getResources().getDimensionPixelOffset(R.dimen.guide_first_cover_image_width)));
结果如下:
getDimension: 256.500000
getDimensionPixelSize: 257
getDimensionPixelOffset:256
我的模拟器是480*800的,屏幕密度是1.5,从打印结果就可以推知,getDimension()、getDimensionPixelSize()和getDimenPixelOffset()的结果值都是将资源文件中定义的dip值乘以屏幕密度,即171*1.5=256.5,只是getDimension()返回的是float,其余两个返回的是int, 其中getDimensionPixelSize()返回的是实际数值的四舍五入,而getDimensionPixelOffset返回的是实际数值去掉后面的小数点; 再跟踪代码查看这三个函数的具体实现,可以了解得更具体。总然而之,这三个函数返回的都是dip值乘以屏幕密度,如果你在资源文件中定义的长度单位不是dip,而是px的话,程序会直接抛出异常。
参考链接
getDimension()、getDimensionPixelSize()和getDimensionPixelOffset()的区别