相信大家对线程肯定不陌生,我们在编程的过程中经常要用到线程,Android提供了多线程支持,在Android程序中,VM采用抢占式调度模式对线程进行调度(基于线程优先级来决定CPU的使用权),android一个重要的机制就是线程+消息。

一,线程进程

既然说到线程,得先说说进程。一般来说一个android程序对于一个进程(组件元素activity、service、receiver、provider。都有一个process属性可以指定组件运行在哪个进程中,你可以在清单文件中manifest设置组件的进程)所有的组件都在特定进程的主线程中实例化。
线程:线程是属于进程的。同一个进程下的可以有多个线程。这些线程是共同享该进程占有的资源和地址空间的。线程是进程的一部分,一个没有线程的进程可以看做是单线程的。对于android应用来说,当一个应用启动的时候,系统会默认为它创建一个线程,称为“主线程”(UI线程)。这个线程很重要因为它负责处理调度事件到相关的 user interface widgets,包括绘制事件。你的应用也是在这个线程里面与来自Android UI toolkit (包括来自 android.widget 和 android.view 包的组件)的组件进行交互。因此,这个主线程有时候也被称为 UI 线程。
进程线程总结:
1. 进程让操作系统的并发性成为可能,而线程让进程的内部并发成为可能。
2. 进程是操作系统进行资源分配的基本单位。
3. 线程是操作系统进行调度的基本单。
4. 线程的划分尺度小于进程,线程隶属于某个进程。
5. 进程是程序的一种动态形式,是CPU、内存等资源占用的基本单位,而线程是不能独立的占有这些资源的。
6. 进程之间相互独立,通信比较困难,而线程之间共享一块内存区域,通信比较方便。
7. 进程在执行过程中,包含比较固定的入口、执行顺序和出口,而线程的这些过程会被应用程序所控制。

二,线程的实现(两种方式)

两种方法,继承Thread类和实现Runnable接口

  • 继承Thread类
    extends Thread 重写run方法做我们想做的事情,start启动线程。 简单代码如下:
    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        new CustomerThread("CustomerThread").start();    }    public class CustomerThread extends Thread {        public CustomerThread(String threadName) {            super(threadName);        }        @Override        public void run() {            /** do our things */        }    }
  • 实现Runnable接口:run方法里面做我们要做的事情。简单代码如下
    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        SyncRunnable syncRunnable = new SyncRunnable();        Thread thread = new Thread(syncRunnable, "syncRunnable");        thread.start();    }    public class SyncRunnable implements Runnable {        @Override        public void run() {            synchronized (this) {                for (int i = 0; i < 5; i++) {                    System.out.println(Thread.currentThread().getName() + " synchronizedloop " + i);                }            }        }    }

注:这两种方法的实现中我都有带上线程的名字。顺便说一点,我们用线程的时候最好给线程加上一个名字,这样我们在看log信息的时候能方便的知道是哪个线程打(默认会thread-0, thread-1什么的)。

三,线程退出

我们在使用线程的时候经常会碰到这种情况,我们的线程是要一直while执行的(如果不是要一直while执行的thread执行完了就会自动销毁)。这个时候当程序退出的时候我们想要这个while线程也去释放资源。主要有三种方式。

  • 使用退出标志,使线程正常退出
    这个最简单了设置标志位。简单代码如下:
    private boolean exit = false;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        SyncThread syncRunnable = new SyncThread("syncThread");        syncRunnable.start();    }    @Override    protected void onDestroy() {        exit = true;        super.onDestroy();    }    public class SyncThread extends Thread {        public SyncThread(String threadName) {            super(threadName);        }        @Override        public void run() {            while(!exit) {                /** do our things */            }        }    }

在onDestroy方法里面退出。exit = true;

  • 使用interrupt()方法中断线程 (当调用线程的interrupt()方法时,系统会抛出一个InterruptedException异常)
    1)先捕获InterruptedException异常之后通过break来跳出循环,简单代码如下。
    SyncThread syncRunnable = null;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        syncRunnable = new SyncThread("syncThread");        syncRunnable.start();    }    @Override    protected void onDestroy() {        if (null != syncRunnable) {            syncRunnable.interrupt();        }        super.onDestroy();    }    public class SyncThread extends Thread {        public SyncThread(String threadName) {            super(threadName);        }        @Override        public void run() {            while(true) {                /** do our things */                try {                    Thread.sleep(5*1000);                } catch (InterruptedException e) {                    e.printStackTrace();                    /** exit the while */                    break;                }            }        }    }

2)使用isInterrupted()判断线程的中断标志来退出循环。简单代码如下

    SyncThread syncRunnable = null;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        syncRunnable = new SyncThread("syncThread");        syncRunnable.start();    }    @Override    protected void onDestroy() {        if (null != syncRunnable) {            syncRunnable.interrupt();        }        super.onDestroy();    }    public class SyncThread extends Thread {        public SyncThread(String threadName) {            super(threadName);        }        @Override        public void run() {            while(!isInterrupted()) {                /** do our things */            }        }    }

3)两者结合使用(推荐) 在线程未进入阻塞的代码段时通过isInterrupted()判断中断来退出循环,在进入阻塞状态后通过通过捕获中断异常来退出循环,简单代码如下

    SyncThread syncRunnable = null;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        syncRunnable = new SyncThread("syncThread");        syncRunnable.start();    }    @Override    protected void onDestroy() {        if (null != syncRunnable) {            syncRunnable.interrupt();        }        super.onDestroy();    }    public class SyncThread extends Thread {        public SyncThread(String threadName) {            super(threadName);        }        @Override        public void run() {            while(!isInterrupted()) {                /** do our things */                try{                    Thread.sleep(5*1000);                }catch(InterruptedException e){                    e.printStackTrace();                    break;                }            }        }    }
  • 使用stop方法强行终止线程(不推荐使用,可能发生不可预料的结果)
    thread.stop() 不安全主要是:thread.stop()调用之后,创建子线程的线程就会抛出ThreadDeatherror的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。

四,多线程通信

  • 管道(pipe)属于linux范畴:一条“管道”为两个线程建立一个单向的通道。生产者负责写数据,消费者负责读取数据。简单代码如下
    private PipedReader mRead;    private PipedWriter mWrite;    private Thread mThread;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initData();        try {            mWrite.write("pipe");        } catch (IOException e) {            e.printStackTrace();        }    }    @Override    protected void onDestroy() {        super.onDestroy();        mThread.interrupt();        try {            mRead.close();            mWrite.close();        } catch (IOException e) {        }    }    private void initData() {        mRead = new PipedReader();        mWrite = new PipedWriter();        try {            mWrite.connect(mRead);        } catch (IOException e) {            e.printStackTrace();        }        mThread = new Thread(new ThreadPipe(mRead), "ThreadPipe");        mThread.start();    }    private static class ThreadPipe implements Runnable {        private final PipedReader reader;        public ThreadPipe(PipedReader reader){            this.reader = reader;        }        @Override        public void run() {            while(!Thread.currentThread().isInterrupted()){                try {                    int i;                    while((i = reader.read()) != -1){                        char c = (char) i;                        Log.d("vae_tag", "char = " + c);                    }                } catch (IOException e) {                    e.printStackTrace();                }            }        }    }

先把PipedReader,PipedWriter关联起来。如mWrite.connect(mRead);然后这里是在UI线程里面写,ThreadPipe 线程里面读log出来。

  • 共享内存:一个变量可以同时被多个线程所访问。这里要特别注意同步和原子操作的问题,这也共享内存容易出错的原因所在,容易出现死锁等。
  • socket:这个应该简单。
  • 消息队列(Hander和Message):这个也是android常用的线程间通信方式,网上也有好多关于这个的博文。主要要理清Thread, Handler,Message,Looper,MessageQueue 之间的关系。
    • Looper:(相当于隧道) 一个线程可以产生一个Looper 对象,Looper负责的就是创建管理一个MessageQueue。
    • MessageQueue(消息队列):里面放的就是Message对象,每一个线程最多只可以拥有一个MessageQueue,通常使用一个Looper对象对该线程的MessageQueue进行管理,每一个MessageQueue都不能脱离Looper而存在。主线程创建时会默认创建MessageQueue,其他的线程不会默认创建。
    • Message(消息):被放入在MessageQueue中。
    • Handler(消息的处理者):Handler负责将需要传递的信息封装成Message,通过调用Handler对象的obtainMessage()来实现,将消息传递给Looper,这是通过Handler对象的sendMessage()来实现的。继而由Looper将Message放入MessageQueue中。当Looper对象看到MessageQueue中含有Message,就将其广播出去。该Handler对象收到该消息后,调用相应的Handler对象的handleMessage()方法对其进行处理。简单来说就是当要发送Message消息的时候Handler告诉对应Thread的Looper有消息到来,Looper把Message放入到MessageQueue当中去。当MessageQueue中有Message的时候该Thread的Looper告诉对应的Thread的Handler来处理消息。
      几个简单例子代码。
      1)UI线程给自己发送消息。
    private Handler handler;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        handler = new HandlerTest(getMainLooper());        Message msg = handler.obtainMessage(1, 1, 1, "UI thread send message");        handler.sendMessage(msg);    }    class HandlerTest extends Handler {        public HandlerTest(Looper looper) {            super(looper);        }        public void handleMessage(Message msg) {            super.handleMessage(msg);            Log.d("vae_tag", (String)msg.obj);        }    }

2)UI线程给其他线程发送消息。

    private Button  mButton;    private Handler handler;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mButton = (Button) findViewById(R.id.send_message_id);        new ChildThread("ChildThread").start();        mButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                handler.obtainMessage(1, "UI thread send to child thread").sendToTarget();            }        });    }    class ChildThread extends Thread {        public ChildThread(String threadName) {            super(threadName);        }        public void run() {            Looper.prepare();/** init Looper */            handler = new ThreadHandler(Looper.myLooper());            Looper.loop(); /** start Looper */        }        class ThreadHandler extends Handler {            public ThreadHandler(Looper looper) {                super(looper);            }            public void handleMessage(Message msg) {                Log.d("vae_tag", "child thread receive the message :" + msg.obj);            }        }    }

3)其他线程给UI线程发送消息。

    private Handler handler;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        new ChildThread("ChildThread").start();    }    class HandlerTest extends Handler {        public HandlerTest(Looper looper) {            super(looper);        }        public void handleMessage(Message msg) {            super.handleMessage(msg);            Log.d("vae_tag", (String) msg.obj);        }    }    class ChildThread extends Thread {        public ChildThread(String threadName) {            super(threadName);        }        public void run() {            handler = new HandlerTest(Looper.getMainLooper());            Message msg = handler.obtainMessage(1, 1, 1, "child thread send the message");            handler.sendMessage(msg);        }    }

4)其他线程给其他线程发

    private Handler handler;    private Button mButton;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mButton = (Button) findViewById(R.id.send_message_id);        mButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                new ChildThread2("ChildThread2").start();            }        });        new ChildThread1("ChildThread1").start();    }    class ChildThread1 extends Thread {        public ChildThread1(String threadName) {            super(threadName);        }        public void run() {            Looper.prepare();/** init Looper */            handler = new ThreadHandler(Looper.myLooper());            Looper.loop(); /** start Looper */        }        class ThreadHandler extends Handler {            public ThreadHandler(Looper looper) {                super(looper);            }            public void handleMessage(Message msg) {                Log.d("vae_tag", "receive message: " + msg.obj);            }        }    }    class ChildThread2 extends Thread {        public ChildThread2(String threadName) {            super(threadName);        }        public void run() {            Message msg = handler.obtainMessage(1, 1, 1, "child thread send the message");            handler.sendMessage(msg);        }    }

代码都是非常简单的代码,主要抓住Handler往哪个线程发就绑定到对应线程的Looper。
注:关于Message对象,虽然我们可以自己创建一个新的Message,但是更加推荐的是调用handler的obtainMessage方法来获取一个Message对象。这个方法的作用是从系统的消息池中取出一个Message,这样就可以避免Message创建和销毁带来的资源浪费了

五,子线程更新UI的几种方式

  • handler:上面讲过。
  • Activity.runOnUIThread(Runnable):简单代码实现。
    private Button   mButton;    private TextView mTextView;    private Activity mActivity;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mActivity = this;        mTextView = (TextView) findViewById(R.id.text_view_id);        mButton = (Button) findViewById(R.id.send_message_id);        mButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                new Thread(){                    public void run() {                        mActivity.runOnUiThread(new Runnable() {                            @Override                            public void run() {                                mTextView.setText("click ite");                            }                        });                    };                }.start();            }        });    }
  • View.Post(Runnable)或者View.PostDelayed(Runnabe,long)或者handler.post(new Runnable())三个是一个意思实际上是一样的View.Post(Runnable)源码里面会先获取到当前线程的handler,然后再调用post方法(ViewRootImpl.getRunQueue().post(action);):简单代码如下:
    private Button   mButton;    private TextView mTextView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mTextView = (TextView) findViewById(R.id.text_view_id);        mButton = (Button) findViewById(R.id.send_message_id);        mButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                new ChileThread(mButton).start();            }        });    }    public class ChileThread extends Thread {        private Button mButton;        public ChileThread(Button textView) {            mButton = textView;        }        @Override        public void run() {            mButton.post(new Runnable() {                @Override                public void run() {                    mTextView.setText("click ite");                }            });        }    }

在这里不管是mButton.post() 还是mTextView.post()都是可以的只要是继承View的对象就行。
- AsyncTask:异步任务,主要是四个重要的方法。

  • onPreExecute:运行在UI线程,主要目的是为后台线程的运行做准备。当他运行完成后,他会调用doInBackground方法。
  • doInBackground:运行在后台线程,他用来负责运行任务。他拥有参数Params,并且返回Result。在后台线程的运行当中,为了能够更新作业完成的进度,需要在doInbackground方法中调用PublishProgress方法。该方法拥有参数Progress。通过该方法可以更新Progress的数据。然后当调用完PublishProgress方法,他会调用onProgressUpdate方法用于更新进度。
  • onProgressUpdate:运行在UI线程,主要目的是用来更新UI线程中显示进度的UI控件。他拥有Progress参数。在doInBackground中调用PublishProgress之后,就会自动调onProgressUpdate方法。
  • onPostExecute:运行在UI线程,当doInBackground方法运行完后,他会调用onPostExecute方法,并传入Result。在onPostExecute方法中,就可以将Result更新到UI控件上。

六,HandlerThread

我们都知道Android中Handler的使用,一般都在UI主线程中执行,因此在Handler接收消息后,处理消息时,不能做一些很耗时的操作,否则将出现ANR错误。Android中专门提供了HandlerThread类,来解决该类问题。HandlerThread类是一个线程专门处理Hanlder的消息HandlerThread用于方便的创建一个含有Looper的线程类。Looper用来创建Handler类,实际上就一个Thread,只不过它比普通的Thread多了一个Looper。一般HandlerThread和Handler类配合使用, Handler将消息发往HandlerThread的消息队列, Handler处理消息。这里启动的是一个新线程 虽然不能直接操作UI 但可以通过Message发送消息来进行操作,其实就是把Handler里面要做的比较耗时的操作放到HandlerThread里面去做,如果有必要的话HandlerThread处理完之后可以再发回给UI线程。
使用HandlerThread的好处:
1. 开发中如果多次使用类似new Thread(){…}.start()这种方式开启一个子线程,会创建多个匿名线程,使得程序运行起来越来越慢,而HandlerThread自带Looper使他可以通过消息来多次重复使用当前线程,节省开支。
2. android系统提供的Handler类内部的Looper默认绑定的是UI线程的消息队列,对于非UI线程又想使用消息机制,那么HandlerThread内部的Looper是最合适的,它不会干扰或阻塞UI线程。
简单的实例代码如下:

public class MainActivity extends ActionBarActivity {    private Handler         mHandler;    private MyHandlerThread mHandlerThread;    private Button          mButton;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mHandlerThread = new MyHandlerThread("MyHandlerThread");        mHandlerThread.start();        mHandler = new Handler(mHandlerThread.getLooper(), mHandlerThread);        mButton = (Button) findViewById(R.id.send_message_id);        mButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                mHandler.sendEmptyMessage(1);            }        });    }    @Override    protected void onDestroy() {        super.onDestroy();        mHandlerThread.quit();    }    public class MyHandlerThread extends HandlerThread implements Handler.Callback {        public MyHandlerThread(String name) {            super(name);        }        @Override        public boolean handleMessage(Message msg) {            Log.d("vae_tag", "get message");            return true;        }    }}

七,线程池

对应数据库连接我们有数据库连接池,对于线程我们也有线程池。线程池是预先创建线程的一种技术。线程池在任务还没到来之前先创建一定数量的线程,放入空闲队列中。这些线程都是处于睡眠状态,即均为启动,不消耗CPU,而只是占用较小的内存空间。当请求到来之后,缓冲池给这次请求分配一个空闲线程,把请求传入此线程中运行,进行处理。当预先创建的线程都处于运行状态,即预制线程不够,线程池可以自由创建一定数量的新线程,用于处理更多的请求。当系统比较闲的时候,也可以通过移除一部分一直处于停用状态的线程。从而达到减少了创建和销毁线程的次数,最大程度的复用对象。

包括三种单一线程池,固定线程池,缓存的线程池使用其他也非常的简单。
下面是固定线程池的简单实例,很简单的例子。

    private Button          mButton;    private ExecutorService mExecutorService;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        /** create three threads in the pool */        mExecutorService = Executors.newFixedThreadPool(3);        mExecutorService.submit(new ChileThread("ChileThread"));        mButton = (Button) findViewById(R.id.send_message_id);        mButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                mExecutorService.submit(new ChileThread("ChileThread"));            }        });    }    public class ChileThread extends Thread {        public ChileThread(String threadName) {            super(threadName);        }        @Override        public void run() {            Log.d("vae_tag", "aaaaaaaaaaaaaaaaa");        }    }

缓存的线程池 线程池的大小会根据执行的任务数动态分配

mExecutorService = Executors.newCachedThreadPool();

单一线程池

mExecutorService = Executors.newSingleThreadExecutor();

八,Callable,Future

Callable 源码

public interface Callable<V> {    /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */    V call() throws Exception;}

Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其他线程执行的任务。Callable是一个泛型接口。call()函数返回的类型就是传递进来的V类型,Callable要配合ExecutorService来使用,采用ExecutorService的submit方法提交。
Callable 和 Runnable方法的区别:
1)Callable定义的方法是call,而Runnable定义的方法是run。
2)Callable的call方法可以有返回值,而Runnable的run方法不能有返回值。
3)Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常。

Future表示异步计算的结果,它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。Future的cancel方法可以取消任务的执行,它有一布尔参数,参数为 true 表示立即中断任务的执行,参数为 false 表示允许正在运行的任务运行完成。Future的 get 方法等待计算完成,获取计算结果。简单的实例代码如下。
1. 单个任务:

public class MainActivity extends ActionBarActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        signalTaskTest();        Log.d("vae_tag", "after signalTaskTest function");    }    private void signalTaskTest() {        Task.callInBackground(new Callable<Void>() {            @Override            public Void call() throws Exception {                /** start test */                ExecutorService threadPool = Executors.newSingleThreadExecutor();                Future<String> future = threadPool.submit(new Callable<String>() {                    public String call() throws Exception {                        Thread.sleep(2000);                        return "hello";                    }                });                Log.d("vae_tag", "start waiting result");                try {                    Log.d("vae_tag", "get the result " + future.get());                } catch (InterruptedException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                } catch (ExecutionException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                return null;            }        });    }}

注:我是在一个线程里面测试 future.get()会等待获取结果(这里有用到Task.callInBackground 在gradle用引入compile ‘com.parse.bolts:bolts-android:1.2.0’)
2. 多个任务:

public class MainActivity extends ActionBarActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        multipleTasksTest();    }    private void multipleTasksTest() {        Task.callInBackground(new Callable<Void>() {            @Override            public Void call() throws Exception {                /** start test */                ExecutorService threadPool2 = Executors.newFixedThreadPool(10);                CompletionService<Integer> compeletionService = new ExecutorCompletionService<Integer>(                    threadPool2);                for (int i = 0; i <= 10; i++) {                    final int seq = i;                    compeletionService.submit(new Callable<Integer>() {                        @Override                        public Integer call() throws Exception {                            Thread.sleep(new Random().nextInt(5000));                            return seq;                        }                    });                }                for (int i = 0; i < 10; i++) {                    try {                        Log.d("vae_tag", "result = " + compeletionService.take().get());                    } catch (InterruptedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    } catch (ExecutionException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                }                return null;            }        });    }}

配合CompletionService使用。

九,推荐(EventBus 一个可以用于通信的开源库)

github项目地址 https://github.com/greenrobot/EventBus

这个也是我们在应用中经常用到的用于通信的一个类库。特别是在fragment 和fragment,fragment和activity等等之间通信非常的方便,用起来也非常的简单具体的可以去google下。

更多相关文章

  1. Android(安卓)异步更新UI----handler+thread
  2. Android(安卓)数据库的简单使用
  3. Android(安卓)中如何调节 TextView 的字间距
  4. android volatile的使用
  5. Android(安卓)彻底关闭WebView,防止WebView造成OOM
  6. 旅行青蛙(旅かえる)逆向笔记
  7. Android: 如何打开assets or raw文件夹下的数据库文件
  8. Android逆向之旅—Hook神器Frida使用详解
  9. Android(安卓)应用安装过程分析

随机推荐

  1. 新用户注册的过程(会话流程)
  2. 分支与循环混编
  3. Android(安卓)iOS测试区别
  4. Android(安卓)Adapter的那些事
  5. AIDL --- Android中的远程接口(1)
  6. Android应用Preference相关及源码浅析(Sh
  7. Android(安卓)Alert Dialog解决点击按钮
  8. 从Android到React Native开发(二、通信与
  9. Android高手进阶教程(十七)之---Android
  10. 关于android开机速度性能方面