Android如何实现exclude aar包中的某个jar包

要移除的jar包不在aar包中的classes.jar中

直接把aar包里的Jar打包的时候给去掉,就像下面这这样。注意,要使用exclude module这种方式,直接使用exclude group方式没有效果。exclude group的方法适用于exclude JAR包中的文件。

implementation(name: '×××××××aar', ext: 'aar') {
    exclude module: 'gson'
}

但是,上面的操作有时候不能生效,尤其是复杂依赖的情况下。我们可能需要在每个间接依赖上都手工进行排除操作,这样就非常麻烦了。

这时候最简单的办法就是移除不需要的jar包,然后重新打包了。

或者要移除的jar包在aar包中的classes.jar中

这个时候,使用exclude方法已经不能生效了。你可以使用下面的通用方法

解压aar文件到tmpDir目录下

$ unzip ×××.aar -d tmpDir

找到classes.jar包,用压缩工具打开,删除目标文件

将tmpDir重新打包成一个新的aar

$ jar cvf ×××NewLib.aar -C tmpDir/ .

另外,有人开发了 排除AAR(Jar)包中冗余或者冲突类的gradle脚本 。如果图方便的话,可以直接用这个脚本来排除。

参考链接


Android ViewPager存在界面卡住的BUG

最近开发的项目出现界面莫名其秒的卡住,直到发生`ANR`异常退出的问题。
问题排查许久,才发现是由于使用的`ViewPager`导致的。
在`ViewPager`中使用`setCurrentItem`长距离设置当前显示的位置的时候,比如`0->600`,`600->1000`,`1000->500` 这样的长距离跳转。会导致函数卡住在

void populate(int newCurrentItem)

函数中很长时间, 甚至一直不能跳出循环。具体卡住的的循环代码如下:

for(int pos = this.mCurItem + 1; pos < N; ++pos) { 
    if (extraWidthRight >= rightWidthNeeded && pos > endPos) { 
        if (ii == null) {
            break;
        }

        if (pos == ii.position && !ii.scrolling) {
            this.mItems.remove(itemIndex);
            this.mAdapter.destroyItem(this, pos, ii.object);
            ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;
        }
    } else if (ii != null && pos == ii.position) {
        extraWidthRight += ii.widthFactor;
        ++itemIndex;
        ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;
    } else {
        ii = this.addNewItem(pos, itemIndex);
        ++itemIndex;
        extraWidthRight += ii.widthFactor;
        ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;
    }
}

上述代码中循环体会一直循环到`N`结束为止。而这个`N`是通过

int N = this.mAdapter.getCount();

赋值的。恰好,我们的代码中返回的是`Integer.MAX_VALUE`。咨询了同事,当时设置这个数值的目的是为了解决循环滚动展示才设置的。代码是直接抄的 Android无限广告轮播 自定义BannerView / 如何优雅的实现一个可复用的 PagerAdapter

通过Google搜索关键词 `BannerAdapter extends PagerAdapter` 可以找到类似的代码。

具体的复现`BUG`的例子代码如下:

package com.mobibrw.viewpager;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import java.util.Random;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final ViewPager viewPager = findViewById(R.id.viewPager);
        viewPager.setAdapter(new PagerAdapter() {
            @Override
            public int getCount() {
                return Integer.MAX_VALUE;
            }

            @Override
            public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
                return view == object;
            }

            @Override
            public void destroyItem(ViewGroup container, int position, Object object) {
                container.removeView((View) object);
            }

            @Override
            public Object instantiateItem(ViewGroup container, final int position) {
                final View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.pager_view, null);
                final TextView tv = view.findViewById(R.id.text);
                tv.setText("" + position);
                container.addView(view);
                return view;
            }
        });

        final Button btnCmd = findViewById(R.id.btnCmd);
        btnCmd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Random rdm = new Random(System.currentTimeMillis());
                int rd = Math.abs(rdm.nextInt())%700 + 1;
                viewPager.setCurrentItem(rd);
            }
        });
    }
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"/>
    <Button
        android:id="@+id/btnCmd"
        android:text="@string/change_size"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        />

</android.support.constraint.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.53" />

</android.support.constraint.ConstraintLayout>

完整的项目代码可以点击此处下载 ViewPager / ViewPagerAppCompatV7。项目编译完成之后,多点击几次 `Change Size` 按钮就可以复现界面长时间卡住的情况。第二个项目是支持`appcompat-v7:18.0.0`的版本,目的是观察各个版本的代码是否存在不同,结论是,都一样

这个问题目前的解决方法就是保证`ViewPager`中的数据不要太多,比如底层分页显示,每页不要太多。另外就是不要大距离跳转,否则会有很多问题

如果能把`android.support`升级到`androidx`的话,可以试试最新的`ViewPager2`,最新的`ViewPager2`是用`RecyclerView `实现的,应该不会有以前的问题了。

这个问题,我已经向Google提交了BUG,估计最后的答复也是要升级到`androidx`。

参考链接


Android无限广告轮播 - ViewPager源码分析

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.

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.

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.

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.

// 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).

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.

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.

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.

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.

@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.

也可本站下载一份 代码拷贝

参考链接


Using a Thread Pool in Android

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`叠加在一起。

但是现实是让人崩溃的。

使用如下的代码:

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();
            }
        });
    }
}
<?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`,如下:

$ 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`,如下:

$ 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}

两者的区别就是

intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);

这句代码。

还是学艺不精啊。

完整例子代码,点击此处下载 MyApplication

参考链接


华为手机配置显示返回键

使用华为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项目死活编不过,我心想不就是一个项目么,编不过要么就是代码有问题,要么就是依赖库不完整这能有什么问题,于是自己在自己电脑试了下,结果自己也中招了:

继续阅读Gradle: 一个诡异的问题(ERROR: Failed to parse XML AndroidManifest.xml ParseError at [row,col]:[5,5] Message: expected start or end tag)

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 5.x新特性之elevation(阴影),tinting(着色)以及clipping(剪裁)

Android获取SD卡路径及SDCard内存的方法

Android获取SD卡路径及SDCard内存的方法。

代码如下:

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(); 
}

然后:在后面加上斜杠,在加上文件名

如下:

String fileName = getSDPath() +"/" + name;//以name存在目录中
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 的剩余容量和总容量。代码如下:

代码如下:

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获取SD卡路径及SDCard内存的方法