aidl书籍管理(有取消注册)

  1. aidl
// Book.aidl
package com.example.a2.aidl;

parcelable Book;

// IBookManager.aidl
package com.example.a2.aidl;

import com.example.a2.aidl.Book;
import com.example.a2.aidl.IOnNewBookArrivedListener;
interface IBookManager {
    List<Book> getBookList();

    void addBook(in Book book);

    void registerListener(IOnNewBookArrivedListener listener);
    void unRegisterListener(IOnNewBookArrivedListener listener);
}


// IOnNewBookArrivedListener.aidl
package com.example.a2.aidl;

import com.example.a2.aidl.Book;

interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book book);
}
  1. book类
public class Book implements Parcelable {
    public int booId;
    public String bookName;

    public Book(int booId, String bookName){
        this.booId=booId;
        this.bookName=bookName;
    }
    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(booId);
        dest.writeString(bookName);
    }

    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>(){

        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    private Book(Parcel in){
        booId=in.readInt();
        bookName=in.readString();
    }

    @Override
    public String toString() {
        return "bookId: "+booId +" bookName: "+bookName;
    }
}
  1. 服务端服务
/**
 *
 * @author xuanqis
 * 服务端服务,进行书籍的管理,可以取消注册监听器
 */
public class BookManagerServer extends Service {

    private static final String TAG = "BookManagerServer";

    /**
     * 判断服务是否已经死亡的原子变量,用在添加书籍的线程
     */
    private AtomicBoolean isServiceDestroy = new AtomicBoolean(false);
    /**
     * 书籍的列表,这里虽然是CopyOnWriteArrayList,但是在传递到客户端的过程中会转化为ArrayList
     * CopyOnWriteArrayList不是ArrayList的子类
     */
    private CopyOnWriteArrayList<Book> mBookLists = new CopyOnWriteArrayList<>();
    /**
     * 管理监听器的表,可以根据binder来对应监听器,同一个binder在里面只会有一个监听器
     */
    private RemoteCallbackList<IOnNewBookArrivedListener> mListeners=
            new RemoteCallbackList<>();

    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookLists;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookLists.add(book);
        }

        @Override
        public void registerListener(com.example.a2.aidl.IOnNewBookArrivedListener listener)
                throws RemoteException {
            mListeners.register(listener);
        }

        @Override
        public void unRegisterListener(com.example.a2.aidl.IOnNewBookArrivedListener listener)
                throws RemoteException {
            mListeners.unregister(listener);
        }
    };
    public BookManagerServer() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mBookLists.add(new Book(1, "Android"));
        mBookLists.add(new Book(2, "IOS"));
        new Thread(new ServerWorker()).start();
    }


    private void onNewBookArrived(Book book )throws RemoteException{
        mBookLists.add(book);

        final int N = mListeners.beginBroadcast();
        Log.d(TAG, "开始逐个进行监听器的开启:"+N);
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener listener = mListeners.getBroadcastItem(i);
            if (listener!=null){
                try{
                    listener.onNewBookArrived(book);
                }catch (RemoteException e){
                    e.printStackTrace();
                }
            }
        }
        mListeners.finishBroadcast();
    }

    /**
     * 每隔5s添加一本书籍
     */
    private class ServerWorker implements Runnable{
        @Override
        public void run() {
            while(!isServiceDestroy.get()){
                try{
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int bookId = mBookLists.size()+1;
                Book newBook = new Book(bookId, "new Book# "+bookId);
                try{
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }

        }
    }

}
  1. 客户端活动
/**
 * @author xuanqis
 * 客户端活动,能够增加书籍,查询书籍列表,在新书到达时接受书籍信息
 */
public class BookManagerActivity extends AppCompatActivity {

    private static final String TAG = "BookManagerActivity";
    //新书到达的消息what
    private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;

    private IBookManager bookManager;
    /**
     * 在主线程处理消息
     */
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    Log.d(TAG, "新来了一本书:" + msg.obj);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            bookManager = IBookManager.Stub.asInterface(service);
            try {
                List<Book> list = bookManager.getBookList();
                Log.i(TAG, "query book list, list type:" + list.getClass());
                Log.i(TAG, "query book list, list type:" + list.toString());
                Book book = new Book(3, "Android进阶");
                bookManager.addBook(book);
                Log.d(TAG, "加入:" + book);
                list = bookManager.getBookList();
                Log.d(TAG, "书单:" + list.toString());
                bookManager.registerListener(mIOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bookManager = null;
            Log.e(TAG, "连接关闭.");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);
        //绑定客户端和服务端
        Intent intent = new Intent(this, BookManagerServer.class);
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        //取消注册监听器
        if (bookManager != null && bookManager.asBinder().isBinderAlive()) {
            try {
                bookManager.unRegisterListener(mIOnNewBookArrivedListener);
                Log.d(TAG, "解除注册" + bookManager);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        //解除绑定
        unbindService(mServiceConnection);
        super.onDestroy();
    }


    /**
     * 新书到达的监听器,在客户端进程执行
     */
    private IOnNewBookArrivedListener mIOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book book) throws RemoteException {
            //这里的book是放在obj里面,要注意的是obj的跨进程只能支持系统设置的Parcelable类
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, book).sendToTarget();
        }
    };

}
发表在 Android | 留下评论

aidl书籍管理(无取消注册)

  1. book
public class Book implements Parcelable {
    public int booId;
    public String bookName;

    public Book(int booId, String bookName){
        this.booId=booId;
        this.bookName=bookName;
    }
    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(booId);
        dest.writeString(bookName);
    }

    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>(){

        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    private Book(Parcel in){
        booId=in.readInt();
        bookName=in.readString();
    }

    @Override
    public String toString() {
        return "bookId: "+booId +" bookName: "+bookName;
    }
}
  1. 服务端
/**
 *
 * @author xuanqis
 * 服务端服务,进行书籍的管理,无法取消注册监听器
 */
public class BookManagerServer extends Service {

    private static final String TAG = "BookManagerServer";

    /**
     * 判断服务是否已经死亡的原子变量,用在添加书籍的线程
     */
    private AtomicBoolean isServiceDestroy = new AtomicBoolean(false);
    /**
     * 书籍的列表,这里虽然是CopyOnWriteArrayList,但是在传递到客户端的过程中会转化为ArrayList
     * CopyOnWriteArrayList不是ArrayList的子类
     */
    private CopyOnWriteArrayList<Book> mBookLists = new CopyOnWriteArrayList<>();
    /**
     * 这里需要改,这个并不能实现移除监听器的功能,因为客户端监听器在服务端生成的是代理
     */
    private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListeners =
            new CopyOnWriteArrayList<>();

    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookLists;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookLists.add(book);
        }

        @Override
        public void registerListener(com.example.a2.aidl.IOnNewBookArrivedListener listener) throws RemoteException {
            if (!mListeners.contains(listener)){
                mListeners.add(listener);
            }
            else {
                Log.i(TAG, "已经存在了");
            }
            Log.i(TAG, "监听器的个数:"+mListeners.size());
        }

        @Override
        public void unRegisterListener(com.example.a2.aidl.IOnNewBookArrivedListener listener) throws RemoteException {
            if (mListeners.contains(listener)){
                mListeners.remove(listener);
            }
            else {
                Log.i(TAG, "不存在这个监听器");
            }
            Log.i(TAG, "监听器的个数:"+mListeners.size());
        }
    };
    public BookManagerServer() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mBookLists.add(new Book(1, "Android"));
        mBookLists.add(new Book(2, "IOS"));
        new Thread(new ServerWorker()).start();
    }


    private void onNewBookArrived(Book book )throws RemoteException{
        mBookLists.add(book);
        Log.d(TAG, "开始逐个进行监听器的开启:"+mListeners.size());
        for (IOnNewBookArrivedListener listener : mListeners) {
            Log.d(TAG, "告知监听器:"+listener);
            listener.onNewBookArrived(book);
        }
    }

    /**
     * 每隔5s添加一本书籍
     */
    private class ServerWorker implements Runnable{
        @Override
        public void run() {
            while(!isServiceDestroy.get()){
                try{
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int bookId = mBookLists.size()+1;
                Book newBook = new Book(bookId, "new Book# "+bookId);
                try{
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }

        }
    }

}
  1. 客户端活动
/**
 * @author xuanqis
 * 客户端活动,能够增加书籍,查询书籍列表,在新书到达时接受书籍信息
 */
public class BookManagerActivity extends AppCompatActivity {

    private static final String TAG = "BookManagerActivity";
    //新书到达的消息what
    private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;

    private IBookManager bookManager;
    /**
     * 在主线程处理消息
     */
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    Log.d(TAG, "新来了一本书:" + msg.obj);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            bookManager = IBookManager.Stub.asInterface(service);
            try {
                List<Book> list = bookManager.getBookList();
                Log.i(TAG, "query book list, list type:" + list.getClass());
                Log.i(TAG, "query book list, list type:" + list.toString());
                Book book = new Book(3, "Android进阶");
                bookManager.addBook(book);
                Log.d(TAG, "加入:" + book);
                list = bookManager.getBookList();
                Log.d(TAG, "书单:" + list.toString());
                bookManager.registerListener(mIOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bookManager = null;
            Log.e(TAG, "连接关闭.");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);
        //绑定客户端和服务端
        Intent intent = new Intent(this, BookManagerServer.class);
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        //这里理论上个取消注册,但是实际上传到服务端的跟原先生成的不是一个对象,所以不可能
        //通过remove来取消注册
        if (bookManager != null && bookManager.asBinder().isBinderAlive()) {
            try {
                bookManager.unRegisterListener(mIOnNewBookArrivedListener);
                Log.d(TAG, "解除注册" + bookManager);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        //解除绑定
        unbindService(mServiceConnection);
        super.onDestroy();
    }


    /**
     * 新书到达的监听器,在客户端进程执行
     */
    private IOnNewBookArrivedListener mIOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book book) throws RemoteException {
            //这里的book是放在obj里面,要注意的是obj的跨进程只能支持系统设置的Parcelable类
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, book).sendToTarget();
        }
    };

}
  1. aidl
// Book.aidl
package com.example.a2.aidl;

parcelable Book;

// IBookManager.aidl
package com.example.a2.aidl;

import com.example.a2.aidl.Book;
import com.example.a2.aidl.IOnNewBookArrivedListener;
interface IBookManager {
    List<Book> getBookList();

    void addBook(in Book book);

    void registerListener(IOnNewBookArrivedListener listener);
    void unRegisterListener(IOnNewBookArrivedListener listener);
}


// IOnNewBookArrivedListener.aidl
package com.example.a2.aidl;

import com.example.a2.aidl.Book;

interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book book);
}
发表在 Android | 留下评论

可重入自旋锁实现

import java.util.concurrent.atomic.AtomicReference;

public class Test {

    public static void main(String[] args) throws InterruptedException {
        SpinLock lock = new SpinLock();
        MyRunnable runnable = new MyRunnable(lock);;
        for (int i = 0; i < 100; i++) {
            Thread t = new Thread(runnable);
            t.start();
        }
        if (Thread.activeCount() > 2){
            Thread.yield();
        }
        if (runnable!=null){
            System.out.println(runnable.sum);
        }
    }
}

class MyRunnable implements Runnable{
    int sum;
    /**
     * 将锁放在创建线程的Runnable对象里面
     */
    private SpinLock lock;

    public MyRunnable(SpinLock lock) {
        this.lock = lock;
        sum=0;
    }

    @Override
    public void run() {
        this.lock.lock();
        this.lock.lock();
        sum++;
        this.lock.unLock();
        this.lock.unLock();
    }
}


/**
 * 可重入的自旋锁  (重新进入 不会出现死锁)
 */
class SpinLock {
    //持有自旋锁的线程对象 
    AtomicReference<Thread> owner = new AtomicReference<>();
    //用一个计数器 来做 重入锁获取次数的计数
    private int count;

    public void lock() {
        Thread cur = Thread.currentThread();
        //同一线程可以多次加锁
        if (cur == owner.get()) {
            count++;
            return;
        }
        //这里耗时很严重
        while (!owner.compareAndSet(null, cur)) {
        }
    }

    public void unLock() {
        Thread cur = Thread.currentThread();
        if (cur == owner.get()) {
            if (count > 0) {
                count--;
            } else {
                owner.compareAndSet(cur, null);
            }
        }
    }
}
发表在 java | 留下评论

binder示例

  1. 终于弄懂啦原理和具体怎么样的过程,看这篇就够了,传送门
    在这里Binder的实例是BookManager的实现

  2. 首先是实现了Parcelable接口的Book类

/**
 * @author baronzhang (baron[dot]zhanglei[at]gmail[dot]com)
 * 2017/10/17
 * 书籍类,价格和名称
 */
public class Book implements Parcelable {

    private int price;
    private String name;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.price);
        dest.writeString(this.name);
    }

    public Book() {
    }

    protected Book(Parcel in) {
        this.price = in.readInt();
        this.name = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public String toString() {
        return "Book{" +
                "price=" + price +
                ", name='" + name + '\'' +
                '}';
    }
}
  1. 然后是定义了功能的BookManager接口
/**
 * 这个类用来定义服务端 RemoteService 具备什么样的能力
 *
 * @author baronzhang (baron[dot]zhanglei[at]gmail[dot]com)
 * 05/01/2018
 * 里面是放了两个方法
 */
public interface BookManager extends IInterface {

    List<Book> getBooks() throws RemoteException;

    void addBook(Book book) throws RemoteException;
}
  1. 然后是实现BookManager的Stub抽象类,在这个类里将客户端的数据进行进行反序列化,并进一步的调用方法。

/** * @author baronzhang (baron[dot]zhanglei[at]gmail[dot]com) * 05/01/2018 * 处理Binder客户端请求的抽象类,进行反序列化,不进行对 反序列化的对象 执行具体的操作 * 处理对客户端的结果返回 */ public abstract class Stub extends Binder implements BookManager { private static final String DESCRIPTOR = "com.baronzhang.ipc.server.BookManager"; public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * 根据binder,返回对应的能供客户端调用的对象 * * @param binder 连接对应的binder * @return binder对应的客户端使用的对象 */ public static BookManager asInterface(IBinder binder) { //传入空,返回空的 if (binder == null) { return null; } //先找一下是不是同一进程的对象,是的话就直接返回对应的对象,不是的话就返回代理 //这里是客户进程进行访问 IInterface iin = binder.queryLocalInterface(DESCRIPTOR); if (iin != null && iin instanceof BookManager) { return (BookManager) iin; } return new Proxy(binder); } @Override public IBinder asBinder() { return this; } @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code) { case INTERFACE_TRANSACTION: reply.writeString(DESCRIPTOR); return true; //得到书籍的List case TRANSAVTION_getBooks: data.enforceInterface(DESCRIPTOR); List<Book> result = this.getBooks(); reply.writeNoException(); reply.writeTypedList(result); return true; //添加一本书,首先将书籍反序列化,然后调用添加方法 case TRANSAVTION_addBook: data.enforceInterface(DESCRIPTOR); Book arg0 = null; if (data.readInt() != 0) { arg0 = Book.CREATOR.createFromParcel(data); } this.addBook(arg0); reply.writeNoException(); return true; default: break; } return super.onTransact(code, data, reply, flags); } public static final int TRANSAVTION_getBooks = IBinder.FIRST_CALL_TRANSACTION; public static final int TRANSAVTION_addBook = IBinder.FIRST_CALL_TRANSACTION + 1; }
  1. 然后是服务service,这个定义在单独的进程中,里面有Stub的对象,这个对象其实就是整个应用能够进程通信的Binder对象。
/**
 * 返回binder,提供服务
 */
public class RemoteService extends Service {

    private List<Book> books = new ArrayList<>();

    public RemoteService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Book book = new Book();
        book.setName("三体");
        book.setPrice(88);
        books.add(book);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return bookManager;
    }

    /**
     * 具体的服务端对客户端传过来的数据进行处理的对象,实现了抽象类未实现的具体方法逻辑
     */
    private final Stub bookManager = new Stub() {
        @Override
        public List<Book> getBooks() throws RemoteException {
            synchronized (this) {
                if (books != null) {
                    return books;
                }
                return new ArrayList<>();
            }
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            synchronized (this) {
                if (books == null) {
                    books = new ArrayList<>();
                }

                if (book == null) {
                    return;
                }

                book.setPrice(book.getPrice() * 2);
                books.add(book);

                Log.e("Server", "books: " + book.toString());
            }
        }
    };
}
  1. 客户端进程调用Stub的方法得到BookManager的实例,但是其实是得到的代理,这个代理中有Binder的引用,当调用这个Binder的方法,实际上调用的是注册在ServiceManager里面的Binder实例的实际对象方法。

/** * @author baronzhang (baron[dot]zhanglei[at]gmail[dot]com) * 05/01/2018 * 客户端的代理类,将数据进行序列话,按格式传给服务端 */ public class Proxy implements BookManager { private static final String DESCRIPTOR = "com.baronzhang.ipc.server.BookManager"; private IBinder remote; public Proxy(IBinder remote) { this.remote = remote; } public String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public List<Book> getBooks() throws RemoteException { Parcel data = Parcel.obtain(); Parcel replay = Parcel.obtain(); List<Book> result; try { //加个token data.writeInterfaceToken(DESCRIPTOR); //调用服务端,将序列化的数据传进去,这里是没有数据要传 remote.transact(Stub.TRANSAVTION_getBooks, data, replay, 0); replay.readException(); result = replay.createTypedArrayList(Book.CREATOR); } finally { replay.recycle(); data.recycle(); } return result; } @Override public void addBook(Book book) throws RemoteException { Parcel data = Parcel.obtain(); Parcel replay = Parcel.obtain(); try { data.writeInterfaceToken(DESCRIPTOR); if (book != null) { data.writeInt(1); //将book写入到data中 book.writeToParcel(data, 0); } else { data.writeInt(0); } remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0); replay.readException(); } finally { replay.recycle(); data.recycle(); } } @Override public IBinder asBinder() { return remote; } }
  1. 客户端通过绑定得到的Binder来创建代理,对代理的操作就是对服务端BookManager的操作。

/** * 客户端活动 */ public class ClientActivity extends AppCompatActivity { private BookManager bookManager; private boolean isConnection = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_client); Button btn = findViewById(R.id.btn); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //没绑定,进行绑定 if (!isConnection) { attemptToBindService(); return; } //出错啦,直接是空 if (bookManager == null) { return; } try { Book book = new Book(); book.setPrice(101); book.setName("编码"); bookManager.addBook(book); Log.d("ClientActivity", bookManager.getBooks().toString()); } catch (RemoteException e) { e.printStackTrace(); } } }); } /** * 绑定服务 */ private void attemptToBindService() { Intent intent = new Intent(this, RemoteService.class); intent.setAction("com.baronzhang.ipc.server"); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); } private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { isConnection = true; //返回供客户端使用的对象,同进程就直接是服务中的对象,不同进程是个代理 bookManager = Stub.asInterface(service); if (bookManager != null) { try { List<Book> books = bookManager.getBooks(); Log.d("ClientActivity", books.toString()); } catch (RemoteException e) { e.printStackTrace(); } } } @Override public void onServiceDisconnected(ComponentName name) { isConnection = false; } }; @Override protected void onStart() { super.onStart(); if (!isConnection) { attemptToBindService(); } } @Override protected void onStop() { super.onStop(); if (isConnection) { unbindService(serviceConnection); } } }
发表在 Android | 留下评论

Material Design的使用

  1. 综合使用了
    Toolbar:替代ActionBar以支持MaterialDesign
    滑动菜单:滑动来选择菜单,通过左上角也可打开
    NavigationView:由菜单和HeadLayout来组成,HeadLayout里面还使用了CircleImageView,菜单的项是一整个的组
    悬浮按钮:加进去就行
    可交互提示SnackBar:能够点击的Toast,出现后会覆盖上面的。
    协调布局:CoordinatorLayout,能够监听子控件的事件,自动帮我们做出最合理的响应,能够解决悬浮按钮被覆盖的问题。
    卡片式布局:美观、具有悬浮的效果、能够设置圆弧。
    AppBarLayout:解决Toobar被主内容覆盖的问题,同时能够设置根据主内容的滚动而滚动。
    下拉刷新:使用这个布局包裹某个控件,控件就具有了下拉的显示刷新标志的功能,在处理了要做的刷新后将刷新置为false。
    可折叠标题栏:CollaspingToolBarLayout可以被折叠,设置折叠后颜色,折叠后是否保存,同时注意的是滚动的控件必须要能够响应滚动事件,才能使coordinator Layout做出合理响应。
    利用状态栏:在21版本以上的是可以设置占据状态栏的,,需要另外的处理。

  2. 具体代码
    Fruit类:

public class Fruit {
    private String name;
    private int imageId;

    public Fruit(String name, int imageId) {
        this.name = name;
        this.imageId = imageId;
    }

    public String getName() {
        return name;
    }

    public int getImageId() {
        return imageId;
    }
}

MainActivity:

public class MainActivity extends AppCompatActivity {
    /**
     * 滑动窗口、刷新工具
     */
    private DrawerLayout mDrawerLayout;
    private SwipeRefreshLayout mSwipeRefreshLayout;

    private Fruit[] mFruits = {
            new Fruit("Apple", R.drawable.apple),
            new Fruit("Banana", R.drawable.banana),
            new Fruit("Pear", R.drawable.pear),
            new Fruit("Pineapple", R.drawable.pineapple),
            new Fruit("Cherry", R.drawable.cherry),
            new Fruit("Watermelon", R.drawable.watermelon),
            new Fruit("Grape", R.drawable.grape),
            new Fruit("Strawberry", R.drawable.strawberry),
            new Fruit("Mango", R.drawable.mango),
            new Fruit("Orange", R.drawable.orange)
    };
    private List<Fruit> mFruitsList = new ArrayList<>();
    private FruitAdapter mFruitAdapter;

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

        //这里一定要将ActionBar换成Toolbar才能达到Material design的效果
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mDrawerLayout = findViewById(R.id.drawerlayout);
        final NavigationView navView = findViewById(R.id.nav_view);

        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            //左上的按钮叫做HomeAsUp按钮,设置其可见
            actionBar.setDisplayHomeAsUpEnabled(true);
//            设置按钮的图标
            actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
        }

        //悬浮按钮
        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                /**
                 * snackbar跟Toast差不多,多了一个按钮功能
                 */
                Snackbar.make(v, "Data delete", Snackbar.LENGTH_LONG)
                        .setAction("Undo", new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                Toast.makeText(MainActivity.this, "Data restored",
                                        Toast.LENGTH_LONG).show();
                            }
                        })
                        .show();
            }
        });
        //设置call为默认选定的
        navView.setCheckedItem(R.id.nav_call);
        navView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
                mDrawerLayout.closeDrawers();
                return true;
            }
        });

        //初始化数据并设置RecycleView
        initFruit();
        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
        recyclerView.setLayoutManager(layoutManager);
        mFruitAdapter = new FruitAdapter(mFruitsList);
        recyclerView.setAdapter(mFruitAdapter);

        //设置下拉刷新
        mSwipeRefreshLayout = findViewById(R.id.swipeRefreshLayout);
        mSwipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
        mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                refreshFruit();
            }
        });
    }

    /**
     * 进行下拉刷新的具体操作
     */
    private void refreshFruit() {
        MyApplication.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        initFruit();
                        mFruitAdapter.notifyDataSetChanged();
                        mSwipeRefreshLayout.setRefreshing(false);
                    }
                });
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        /**
         * 将编写的菜单设置为系统的菜单
         */
        getMenuInflater().inflate(R.menu.toolbar, menu);
        return true;
    }


    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        /**
         * 菜单的处理方法
         */
        switch (item.getItemId()) {
            //系统又默认的id
            case android.R.id.home:
                //直接start滑动
                mDrawerLayout.openDrawer(GravityCompat.START);
                break;
            case R.id.backup:
                Toast.makeText(MainActivity.this, "点击了backup", Toast.LENGTH_LONG).show();
                break;
            case R.id.delete:
                Toast.makeText(MainActivity.this, "点击了delete", Toast.LENGTH_LONG).show();
                break;
            case R.id.settings:
                Toast.makeText(MainActivity.this, "点击了settings", Toast.LENGTH_LONG).show();
                break;

            default:
                break;
        }
        return true;
    }


    private void initFruit() {
        mFruitsList.clear();
        for (int i = 0; i < 50; i++) {
            Random random = new Random();
            int index = random.nextInt(mFruits.length);
            mFruitsList.add(mFruits[index]);
        }
    }
}

适配器FruitAdapter:

/**
 * RecycleView的适配器
 * @author xuanqis
 */
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
    /**
     * 上下文和数据源
     */
    private Context mContext;
    private List<Fruit> mFruits;

    class ViewHolder extends RecyclerView.ViewHolder {
        CardView mCardView;
        ImageView fruitImage;
        TextView fruitName;
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            mCardView= (CardView) itemView;
            fruitImage=itemView.findViewById(R.id.fruit_image);
            fruitName=itemView.findViewById(R.id.fruit_name);
        }
    }

    /**
     * 数据一定要传过来
     * @param fruits fruit的list
     */
    public FruitAdapter(List<Fruit> fruits) {
        mFruits = fruits;
    }

    @NonNull
    @Override
    public FruitAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        //context只需要从父布局中得到即可
        if(mContext == null){
            mContext=viewGroup.getContext();
        }
        View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item, viewGroup, false);
        final ViewHolder holder = new ViewHolder(view);
        holder.mCardView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //可以直接得到当前的View Holder是第几个ViewHolder,原先我还以为需要在绑定中设置
                int position = holder.getAdapterPosition();
                Fruit fruit = mFruits.get(position);
                Intent intent = new Intent(mContext, FruitActivity.class);
                intent.putExtra(FruitActivity.FRUIT_NAME, fruit.getName());
                intent.putExtra(FruitActivity.FRUIT_IMAGE_ID, fruit.getImageId());
                mContext.startActivity(intent);
            }
        });
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull FruitAdapter.ViewHolder viewHolder, int i) {
        //绑定应该理解为将ViewHolder和对应的数据进行绑定
        Fruit fruit = mFruits.get(i);
        viewHolder.fruitName.setText(fruit.getName());
        Glide.with(mContext).load(fruit.getImageId()).into(viewHolder.fruitImage);
    }

    @Override
    public int getItemCount() {
        return mFruits.size();
    }
}

FruitActivity:

public class FruitActivity extends AppCompatActivity {
    public static final String FRUIT_NAME = "fruitName";
    public static final String FRUIT_IMAGE_ID = "fruitImage";

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

        Intent intent = getIntent();
        String fruitName = intent.getStringExtra(FRUIT_NAME);
        int fruitImageId = intent.getIntExtra(FRUIT_IMAGE_ID, 0);


        CollapsingToolbarLayout collapsingToolbarLayout = findViewById(R.id.collapsingToolbarLayout);
        ImageView fruitImageView = findViewById(R.id.fruit_image_view);
        TextView fruitContentText = findViewById(R.id.fruit_content_text);

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
        }

        collapsingToolbarLayout.setTitle(fruitName);
        Glide.with(this).load(fruitImageId).into(fruitImageView);
        String fruitContent = generateFruitContent(fruitName);
        fruitContentText.setText(fruitContent);

    }

    /**
     * 生成水果的内容
     * @param fruitName 水果名字,用这个生成
     * @return 水果的内容
     */
    private String generateFruitContent(String fruitName) {
        StringBuilder fruitContent = new StringBuilder();
        for (int i = 0; i < 500; i++) {
            fruitContent.append(fruitName);
        }
        return fruitContent.toString();
    }


    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                finish();
                return true;
            default:
                break;
        }
        return super.onOptionsItemSelected(item);
    }
}

MyApplication,可以设为系统的Application

public class MyApplication extends Application {
    private static final ExecutorService threadPool = new ThreadPoolExecutor(4, 8, 60,
            TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(4), new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r,"AsyncTask #" + mCount.getAndIncrement());
        }
    });

    public static void execute(Runnable runnable){
        threadPool.execute(runnable);
    }
}
  1. 布局
    Main布局
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 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:id="@+id/drawerlayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/colorPrimary"
                android:background="?attr/colorPrimary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:layout_scrollFlags="scroll|enterAlways|snap"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

        </android.support.design.widget.AppBarLayout>

        <android.support.v4.widget.SwipeRefreshLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/swipeRefreshLayout"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            >
            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                 />
        </android.support.v4.widget.SwipeRefreshLayout>



        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_margin="16dp"
            android:src="@drawable/ic_done"
            app:elevation="0dp" />
    </android.support.design.widget.CoordinatorLayout>


    <!--<TextView-->
    <!--android:layout_width="match_parent"-->
    <!--android:layout_height="match_parent"-->
    <!--android:layout_gravity="start"-->
    <!--android:background="#FFF"-->
    <!--android:text="this is a menu"-->
    <!--android:textSize="30sp" />-->


    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/nav_header"
        app:menu="@menu/nav_menu" />


</android.support.v4.widget.DrawerLayout>

每个项:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView 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="wrap_content"
    android:layout_margin="5dp"
    app:cardCornerRadius="4dp">


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/fruit_image"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:scaleType="centerCrop" />

        <TextView
            android:id="@+id/fruit_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_margin="5dp"
            android:textSize="16sp" />
    </LinearLayout>


</android.support.v7.widget.CardView>

NavigationView的头部:
nav_head.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="180dp"
    android:background="?attr/colorPrimary">

    <de.hdodenhof.circleimageview.CircleImageView
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:id="@+id/icon_image"
        android:src="@drawable/nav_icon"
        android:layout_centerInParent="true"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/mail"
        android:layout_alignParentBottom="true"
        android:text="tony greendev@gmail.com"
        android:textColor="#fff"
        android:textSize="14sp"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/username"
        android:layout_above="@id/mail"
        android:text="Tony Green"
        android:textColor="#fff"
        android:textSize="14sp"/>


</RelativeLayout>

navigationView的菜单栏:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <group android:checkableBehavior="single">
        <item
            android:id="@+id/nav_call"
            android:icon="@drawable/nav_call"
            android:title="Call" />
        <item
            android:id="@+id/nav_friends"
            android:icon="@drawable/nav_friends"
            android:title="Friends" />
        <item
            android:id="@+id/nav_location"
            android:icon="@drawable/nav_location"
            android:title="Location" />
        <item
            android:id="@+id/nav_mail"
            android:icon="@drawable/nav_mail"
            android:title="Mail" />
        <item
            android:id="@+id/nav_task"
            android:icon="@drawable/nav_task"
            android:title="Task" />
    </group>

</menu>

ToolBar:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/backup"
        android:icon="@drawable/ic_backup"
        android:title="Backup"
        app:showAsAction="always"/>
    <item
        android:id="@+id/delete"
        android:icon="@drawable/ic_delete"
        android:title="Delete"
        app:showAsAction="ifRoom"/>
    <item
        android:id="@+id/settings"
        android:icon="@drawable/ic_settings"
        android:title="Settings"
        app:showAsAction="never"/>

</menu>

水果详情活动的布局:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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=".FruitActivity"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:fitsSystemWindows="true">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsingToolbarLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            android:fitsSystemWindows="true">

            <ImageView
                android:id="@+id/fruit_image_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                app:layout_collapseMode="parallax"
                android:fitsSystemWindows="true"/>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin" />
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>


    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <android.support.v7.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="15dp"
                android:layout_marginTop="35dp"
                android:layout_marginRight="15dp"
                android:layout_marginBottom="15dp"
                app:cardCornerRadius="4dp">

                <TextView
                    android:id="@+id/fruit_content_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_margin="10dp" />
            </android.support.v7.widget.CardView>
        </LinearLayout>

    </android.support.v4.widget.NestedScrollView>


    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:src="@drawable/ic_comment"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end"/>
</android.support.design.widget.CoordinatorLayout>
  1. 状态栏

标准的style

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>


    <style name="FruitActivityTheme" parent="AppTheme">

    </style>

</resources>

v21的style

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="FruitActivityTheme" parent="AppTheme">
        <item
            name="android:statusBarColor">@android:color/transparent</item>
    </style>
</resources>
发表在 未分类 | 留下评论

文件下载-服务、通知加异步任务

  1. 活动的布局:就一个下载开始、暂停、结束
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity">

    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:id="@+id/start_download"
        android:text="开始下载"/>
    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:id="@+id/pause_download"
        android:text="暂停下载"/>
    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:id="@+id/stop_download"
        android:text="停止下载"/>




</LinearLayout>
  1. MainActivity:就是绑定一下服务,然后根据用户的点击启动下载等操作,注意要申请访问存储的权限,还有销毁的时候把服务解除绑定。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private DownloadService.DownloadBinder mDownloadBinder;
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mDownloadBinder = (DownloadService.DownloadBinder) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button start_download = findViewById(R.id.start_download),
                pause_download = findViewById(R.id.pause_download),
                stop_download = findViewById(R.id.stop_download);
        start_download.setOnClickListener(this);
        stop_download.setOnClickListener(this);
        pause_download.setOnClickListener(this);
        Intent intent = new Intent(this, DownloadService.class);
        startService(intent);
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
            }, 1);
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(MainActivity.this, "禁止权限将无法正常使用", Toast.LENGTH_LONG).show();
                    finish();
                }
                break;
            default:
                break;
        }
    }

    @Override
    public void onClick(View v) {
        if(mDownloadBinder == null){
            return ;
        }
        switch (v.getId()){
            case R.id.start_download:
                String url = "https://one.xuanjis.com/%E8%B5%84%E6%BA%90/eclipse-java/eclipse-java-2018-12-R-win32-x86_64.zip";
                mDownloadBinder.startDownload(url);
                break;
            case R.id.pause_download:
                mDownloadBinder.pauseDownload();
                break;
            case R.id.stop_download:
                mDownloadBinder.cancelDownload();
                default:
                    break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mServiceConnection);
    }
}

  1. 文件下载服务
public class DownloadService extends Service {
    private DownLoadTask mDownLoadTask;
    private String downloadURL;
    private DownLoadListener mDownLoadListener = new DownLoadListener() {

        @Override
        public void onProgress(int progress) {
            getNotificationManager().notify(1, getNotification("Downloading...", progress));
        }

        @Override
        public void onSuccess() {
            /**
             * 这里置空,lastprogress有点影响
             */
            mDownLoadTask = null;
            stopForeground(true);
            getNotificationManager().notify(1, getNotification("Download Success.", -1));
            Toast.makeText(DownloadService.this, "下载成功", Toast.LENGTH_LONG).show();
        }

        @Override
        public void onFailed() {
            mDownLoadTask = null;
            stopForeground(true);
            getNotificationManager().notify(1, getNotification("download failed", -1));
            Toast.makeText(DownloadService.this, "下载失败", Toast.LENGTH_LONG).show();
        }

        @Override
        public void onPaused() {
            mDownLoadTask = null;
            Toast.makeText(DownloadService.this, "下载暂停", Toast.LENGTH_LONG).show();
        }

        @Override
        public void onCanceled() {
            mDownLoadTask = null;
            stopForeground(true);
            Toast.makeText(DownloadService.this, "下载取消", Toast.LENGTH_LONG).show();
        }
    };

    private DownloadBinder mBinder = new DownloadBinder();

    class DownloadBinder extends Binder {
        /**
         * 开始下载
         * @param url
         */
        public void startDownload(String url) {
            if (mDownLoadTask == null) {
                downloadURL = url;
                mDownLoadTask = new DownLoadTask(mDownLoadListener);
                mDownLoadTask.execute(downloadURL);
                startForeground(1, getNotification("Downloading...", 0));
                Toast.makeText(DownloadService.this, "开始下载", Toast.LENGTH_LONG).show();
            }
        }

        public void pauseDownload() {
            if (mDownLoadTask != null) {
                mDownLoadTask.pauseDownload();
            }
        }

        /**
         * 注意的是文件下载成功后也可以进行取消操作
         */
        public void cancelDownload() {
            if (mDownLoadTask != null) {
                mDownLoadTask.cancelDownload();
            }
            //应用的删除文件操作我觉得没处理好
            if (downloadURL != null) {
                String fileName = downloadURL.substring(downloadURL.lastIndexOf("/"));
                String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
                File file = new File(directory + fileName);
                if (file.exists()) {
                    file.delete();
                }
                getNotificationManager().cancel(1);
                stopForeground(true);
            }
        }
    }


    public DownloadService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }


    /**
     * 得到系统支持的通知管理器
     *
     * @return
     */
    private NotificationManager getNotificationManager() {
        return (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    }


    /**
     * 得到对应的Notification变量
     *
     * @param title    通知的标题
     * @param progress 文件下载的进度
     * @return 满足条件的通知
     */
    private Notification getNotification(String title, int progress) {
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        builder.setSmallIcon(R.mipmap.ic_launcher);
        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
        builder.setContentIntent(pi);
        builder.setContentTitle(title);
        if (progress >= 0) {
            builder.setContentText(progress + "%");
            builder.setProgress(100, progress, false);
        }
        return builder.build();

    }
}
  1. 执行具体下载任务的异步任务,根据不同的状态回调不同的函数,来进行处理。
/**
 * 具体执行下载任务的异步任务
 */
public class DownLoadTask extends AsyncTask<String, Integer, Integer> {
    /**
     * 下载状态
     */
    public static final int TYPE_SUCCESS = 0;
    public static final int TYPE_FAILED = 1;
    public static final int TYPE_PAUSED = 2;
    public static final int TYPE_CANCELED = 3;
    /**
     * 回调的变量
     */
    private DownLoadListener mDownloadListener;
    private boolean isPaused = false;
    private boolean isCanceled = false;
    /**
     * 上次的进度
     */
    private int lastProgress;

    /**
     * 只需要传监听器,下载的地址由任务来决定
     * @param downloadListener
     */
    public DownLoadTask(DownLoadListener downloadListener) {
        mDownloadListener = downloadListener;
    }

    @Override
    protected Integer doInBackground(String... params) {
        InputStream is = null;
        RandomAccessFile savedFile = null;
        File file = null;
        try {
            long downloadLength = 0;
            String downloadURL = params[0];
            String fileName = downloadURL.substring(downloadURL.lastIndexOf("/"));
            String directory = Environment.getExternalStoragePublicDirectory(
                    Environment.DIRECTORY_DOWNLOADS).getPath();
            file = new File(directory + fileName);
            if (file.exists()) {
                downloadLength = file.length();
            }
            long contentLength = getContentLength(downloadURL);
            if (contentLength == 0) {
                return TYPE_FAILED;
            } else if (contentLength == downloadLength) {
                return TYPE_SUCCESS;
            }
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    .addHeader("RANGE", "bytes=" + downloadLength + "-")
                    .url(downloadURL)
                    .build();
            Response response = client.newCall(request).execute();
            if (response != null) {
                is = response.body().byteStream();
                //注意这里是随机访问文件,然后seek到指定的字节开始
                savedFile = new RandomAccessFile(file, "rw");
                savedFile.seek(downloadLength);
                byte[] b = new byte[1024];
                int total = 0;
                int len;
                while ((len = is.read(b)) != -1) {
                    if (isCanceled) {
                        return TYPE_CANCELED;
                    } else if (isPaused) {
                        return TYPE_PAUSED;
                    } else {
                        total += len;
                        savedFile.write(b, 0, len);
                        int progress = (int) ((total + downloadLength) * 100 / contentLength);
                        publishProgress(progress);
                    }

                }
                response.body().close();
                return TYPE_SUCCESS;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
                if (savedFile != null) {
                    savedFile.close();
                }
                if (isCanceled && file != null) {
                    file.delete();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return TYPE_FAILED;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        int progress = values[0];
        if (progress > lastProgress) {
            mDownloadListener.onProgress(progress);
            lastProgress = progress;
        }
    }

    @Override
    protected void onPostExecute(Integer integer) {
        /**
         * 根据不同的状态回调对应的方法
         */
        switch (integer) {
            case TYPE_SUCCESS:
                mDownloadListener.onSuccess();
                break;
            case TYPE_FAILED:
                mDownloadListener.onFailed();
                break;
            case TYPE_CANCELED:
                mDownloadListener.onCanceled();
                break;
            case TYPE_PAUSED:
                mDownloadListener.onPaused();
                break;
            default:
                break;

        }
    }

    public void pauseDownload(){
        isPaused=true;
    }

    public void cancelDownload(){
        isCanceled=true;
    }
    /**
     * 通过url来得到下载的文件的文件长度
     *
     * @param url 下载文件的url
     * @return
     * @throws IOException
     */
    private long getContentLength(String url) throws IOException {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(url)
                .build();
        Response response = client.newCall(request).execute();
        if (response != null && response.isSuccessful()) {
            long contentLength = response.body().contentLength();
            response.body().close();
            return contentLength;
        }
        return 0;

    }
}
  1. 回调的接口,实现是放在服务里
/**
 * 状态的回调函数
 */
public interface DownLoadListener {
    void onProgress(int progress);

    void onSuccess();

    void onFailed();

    void onPaused();

    void onCanceled();

}
发表在 Android | 留下评论

添加从相册选取照片

  1. 在前面拍照的基础上进行修改,布局activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/take_photo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="拍照" />

    <Button
        android:id="@+id/choose_from_album"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="从相册选取"/>

    <ImageView
        android:id="@+id/picture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />

</LinearLayout>
  1. MainActivity,启动一个选取照片的活动,然后根据返回的data,从对应的内容提供器中取出我们所需要的照片,进行显示,对里面的提供器还不了解。
public class MainActivity extends AppCompatActivity {
    private ImageView picture;
    private Uri mImageUri;
    private static final int CAMERA_CODE = 1;
    private static final int CHOOSE_PHOTO=2;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button takePhoto = findViewById(R.id.take_photo);
        picture = findViewById(R.id.picture);
        Button chooseFromAlbum=findViewById(R.id.choose_from_album);
        takePhoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //在相应的目录下创建文件
                File outputImage = new File(getExternalCacheDir() + "outputImage.jpg");

                //这个文件如果存在,就删除重新创建
                try {
                    if (outputImage.exists()) {
                        outputImage.delete();
                    }
                    outputImage.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //24版本的sdk要求高了,需要使用FileProvider来共享文件的目录
                //这里从那个内容提供器来得到文件Uri
                if (Build.VERSION.SDK_INT >= 24) {
                    mImageUri = FileProvider.getUriForFile(MainActivity.this,
                            "com.example.cameraalbumtest.fileprovider", outputImage);
                }
                //24以下的就没这么多要求了,直接得到Uri
                else {
                    mImageUri = Uri.fromFile(outputImage);
                }
                //启动拍照的活动,并且将输出Uri设置为我们创建的文件
                Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
                intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
                startActivityForResult(intent, CAMERA_CODE);
            }
        });

        chooseFromAlbum.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //首先要获得访问外部存储的权限
                if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED){
                    //这里的requestCode是回调的时候需要的
                    ActivityCompat.requestPermissions(MainActivity.this,
                            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1 );
                }else{
                    openAlbum();
                }
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        switch (requestCode) {
            //创建完成后,我们将图片在ImageView中进行展示
            case CAMERA_CODE:
                if (resultCode == RESULT_OK) {
                    try {
                        Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver()
                                .openInputStream(mImageUri));
                        picture.setImageBitmap(bitmap);
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case CHOOSE_PHOTO:
                //得到的intent只是信息,需要从信息得到具体的图片路径,再形成可展示的图片
                //这里再19为分割,不同的处理
                if(resultCode==RESULT_OK){
                    if(Build.VERSION.SDK_INT >= 19){
                        handleImageOnKitkat(data);
                    }
                    else {
                        handleImageBeforeKitkat(data);
                    }
                }
            default:
                break;
        }
    }

    /**
     * 启动查看相册
     */
    private void openAlbum(){
        Intent intent = new Intent("android.intent.action.GET_CONTENT");
        intent.setType("image/*");
        startActivityForResult(intent, CHOOSE_PHOTO);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode){
            case 1:
                if(grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){
                    openAlbum();
                }
                else {
                    Toast.makeText(MainActivity.this, "禁止权限将无法正常使用", Toast.LENGTH_SHORT).show();
                }
                break;

        }
    }


    private void handleImageOnKitkat(Intent data){
        String imagePath = null;
        //从返回来的数据中得到了一个uri,这个uri可以从内容提供器取得响应的数据
        Uri uri = data.getData();

        if(DocumentsContract.isDocumentUri(this, uri)){
            //取出这个什么documentId,不知道具体是什么
            String docId = DocumentsContract.getDocumentId(uri);
            //第一种情况,从原先的uri中取出id作为查询条件,从媒体提供器得到
            if("com.android.providers.media.documents".equals(uri.getAuthority())){
                String id = docId.split(":")[1];
                String selection = MediaStore.Images.Media._ID+"="+id;
                imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
            }
            //第二种情况,是从下载的内容提供器中得到
            else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())){
                Uri contentUri= ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),
                        Long.valueOf(docId));
                imagePath=getImagePath(contentUri, null);
            }
            //Scheme是方案的意思
            else if("content".equals(uri.getScheme())){
                imagePath = getImagePath(uri, null);
            }
            else if("file".equals(uri.getScheme())){
                imagePath=uri.getPath();
            }
            displayImage(imagePath);
        }
    }

    private void handleImageBeforeKitkat(Intent data){
        Uri uri = data.getData();
        String imagePath = getImagePath(uri, null);
        displayImage(imagePath);
    }

    /**
     * 通过Uri和Selection来得到真实的图片路径
     * @param uri 内容提供器的Uri
     * @param selection 查询的条件
     * @return
     */
    private String getImagePath(Uri uri, String selection){
        String path = null;
        Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
        if(cursor != null){
            if(cursor.moveToFirst()){
                path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));

            }
            cursor.close();
        }
        return path;
    }

    private void displayImage(String imagePath){
        if(imagePath != null){
            Bitmap bitmap=BitmapFactory.decodeFile(imagePath);
            picture.setImageBitmap(bitmap);
        }
        else {
            Toast.makeText(MainActivity.this, "获取图片失败", Toast.LENGTH_SHORT).show();
        }
    }
}
发表在 Android | 留下评论

拍照并显示

  1. 首先布局是一个拍照的按钮和拍完显示照片的ImageView
    activity_main:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/take_photo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="拍照" />

    <ImageView
        android:id="@+id/picture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />
</LinearLayout>
  1. MainActivity,在这里要创建文件,得到文件的Uri,然后将Uri给拍照的Activity,其中24及24版本以上的因为加强安全的缘故,需要使用内容提供器,在拍完照后要回调将照片进行显示。
public class MainActivity extends AppCompatActivity {
    private ImageView picture;
    private Uri mImageUri;
    private static final int CAMERA_CODE = 111;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button takePhoto = findViewById(R.id.take_photo);
        picture = findViewById(R.id.picture);
        takePhoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //在相应的目录下创建文件
                File outputImage = new File(getExternalCacheDir() + "outputImage.jpg");

                //这个文件如果存在,就删除重新创建
                try {
                    if (outputImage.exists()) {
                        outputImage.delete();
                    }
                    outputImage.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //24版本的sdk要求高了,需要使用FileProvider来共享文件的目录
                //这里从那个内容提供器来得到文件Uri
                if (Build.VERSION.SDK_INT >= 24) {
                    mImageUri = FileProvider.getUriForFile(MainActivity.this,
                            "com.example.cameraalbumtest.fileprovider", outputImage);
                }
                //24以下的就没这么多要求了,直接得到Uri
                else {
                    mImageUri = Uri.fromFile(outputImage);
                }
                //启动拍照的活动,并且将输出Uri设置为我们创建的文件
                Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
                intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
                startActivityForResult(intent, CAMERA_CODE);
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        switch (requestCode) {
            //创建完成后,我们将图片在ImageView中进行展示
            case CAMERA_CODE:
                if (resultCode == RESULT_OK) {
                    try {
                        Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver()
                                .openInputStream(mImageUri));
                        picture.setImageBitmap(bitmap);
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                break;
            default:
                break;
        }
    }
}
  1. 在Minafest中申明内容提供器,内容是:
<provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.cameraalbumtest.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>

其中使用的是FileProvider,deta-data中的file_paths需要创建,在res下创建xml文件夹,在xml文件夹下创建file_paths.xml文件,内容是

<?xml version="1.0" encoding="utf-8"?>
<paths
    xmlns:android="http://schemas.android.com/apk/res/android">
    <paths>
        <external-path
            name="my_images"
            path="storage"/>
    </paths>


</paths>

name属性这里可以随便填,path所代表的就是我们共享的目录,这个可以通过打印出照片目录来查看。

发表在 Android | 留下评论

强制下线功能的实现

  1. 下线将所有的活动释放,所以创建个管理活动的类
/**
 * 这个是实现将所有的Activity去掉的类
 */
public class ActivityCollector {
    public static List<Activity> sActivities = new ArrayList<>();

    public static void addActivity(Activity activity){
        sActivities.add(activity);
    }

    public static void removeActivity(Activity activity){
        sActivities.remove(activity);
    }

    public static void finishAll(){
        for(Activity activity:sActivities){
            if(!activity.isFinishing()){
                activity.finish();
            }
        }
        sActivities.clear();
    }
}
  1. 在所有的活动中都能强制下线,所以创建个基活动,在这个活动中进行广播的注册和解除,其中保证任何时候只有一个活动有广播。
public class    BaseActivity extends AppCompatActivity {

    private ForceOfflineReceiver mForceOfflineReceiver;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
    }


    @Override
    protected void onResume() {
        super.onResume();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("FORCE_OFFLINE");
        mForceOfflineReceiver=new ForceOfflineReceiver();
        registerReceiver(mForceOfflineReceiver, intentFilter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if(mForceOfflineReceiver!=null){
            unregisterReceiver(mForceOfflineReceiver);
            mForceOfflineReceiver=null;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }

    class ForceOfflineReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(final Context context, Intent intent) {
            AlertDialog.Builder builder = new AlertDialog.Builder(context);
            builder.setTitle("Warning");
            builder.setMessage("强制下线");
            builder.setCancelable(false);
            builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    ActivityCollector.finishAll();
                    Intent intent = new Intent(context, LoginActivity.class);
                    context.startActivity(intent);
                }
            });
            builder.show();
        }
    }
}
  1. 登录的活动,在这里进行登录,其实可以不继承base类的,不过还是统一了。
public class LoginActivity extends BaseActivity {
    private EditText accountText;
    private EditText passwordText;
    private Button mButton;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        passwordText=findViewById(R.id.password);
        accountText=findViewById(R.id.account);
        mButton=findViewById(R.id.login);

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String account = accountText.getText().toString();
                String password = passwordText.getText().toString();
                if(account.equals("admin") && password.equals("123456")){
                    Intent intent = new Intent(LoginActivity.this, MainActivity.class);
                    startActivity(intent);
                    finish();
                }
                else {
                    Toast.makeText(LoginActivity.this, "账号或密码错误", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }
}
  1. 主活动,登录后进入,里面只放了个强制下线的Button
public class MainActivity extends BaseActivity {

    private Button force_offline;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        force_offline=findViewById(R.id.force_offline);

        force_offline.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("FORCE_OFFLINE");
                sendBroadcast(intent);
            }
        });

    }
}
  1. 登录的Layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:orientation="horizontal">

        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:text="Account:"
            android:textSize="18sp" />

        <EditText
            android:id="@+id/account"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_weight="1" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:orientation="horizontal">

        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:text="Password:"
            android:textSize="18sp" />

        <EditText
            android:id="@+id/password"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_weight="1" />
    </LinearLayout>

    <Button
        android:id="@+id/login"
        android:layout_width="wrap_content"
        android:layout_height="60dp"
        android:layout_gravity="center_vertical"
        android:text="login" />

</LinearLayout>
  1. 主活动的布局
<?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">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/force_offline"
        android:text="下线"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

</android.support.constraint.ConstraintLayout>
发表在 未分类 | 留下评论

页面切换的ViewPager搭配FragmentAdapter实现

  1. 为了找错,加了BaseFragment和BaseActivity
public class BaseFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.i("xuanqis开始:", "onCreateView "+this.getActivity().getClass().getName());
        return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.i("xuanqis完成了:", getActivity().getClass().getName()+"的创建");
    }
}
public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i("xuanqis开始创建:", this.getClass().getName());

    }
}
  1. 四个页面里面一样
public class ContactFragment extends BaseFragment {


    public ContactFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_contact, container, false);
    }

}
  1. MainActivity
public class MainActivity extends BaseActivity {
    /**
     * Viewpager
     */
    private ViewPager mViewPager;
    /**
     * FragmentPager适配器,之前没用过
     */
    private FragmentPagerAdapter mAdapter;
    /**
     * Fragment的一个数据源
     */
    private List<Fragment> mFragments;

    /**
     * 各Fragment的布局
     */
    private LinearLayout mTabWechat;
    private LinearLayout mTabFriend;
    private LinearLayout mTabContact;
    private LinearLayout mTabSetting;

    /**
     * 各tab的图片
     */
    private ImageButton mImgWechat;
    private ImageButton mImgFriend;
    private ImageButton mImgContact;
    private ImageButton mImgSetting;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar =  findViewById(R.id.toolbar);

        setSupportActionBar(toolbar);

        Log.i("xuanqis", "1");

        initViews();    //初始化控件
        Log.i("xuanqis", "2");

        initEvents();   //初始化事件
        Log.i("xuanqis", "3");
        initDatas();    //初始化数据
        Log.i("xuanqis", "4");
        //第一次运行初始化界面,显示第一个碎片
        //initFirstRun(0);
    }


    /**
     * 将自己的菜单设置成系统的菜单
     * @param menu 菜单
     * @return 菜单是否可见
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.toolbar, menu);
        return true;
    }

    /**
     * 菜单选项被点击的处理方法
     * @param item 菜单选项
     * @return 返回true则说明在这里进行了处理,返回false则说明没处理
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        switch (id) {
            case R.id.action_settings:
                Toast.makeText(MainActivity.this, "action_settings", Toast.LENGTH_LONG).show();
                break;
            case R.id.action_share:
                Toast.makeText(MainActivity.this, "action_share", Toast.LENGTH_LONG).show();
                break;
            case R.id.ab_search:
                Toast.makeText(MainActivity.this, "ab_search", Toast.LENGTH_LONG).show();
                break;
            default:
                break;
        }
        if (id == R.id.action_settings) {
            return true;
        } else {
            return super.onOptionsItemSelected(item);
        }
    }

    /**
     * 初始化Toolbar的View, 包括整个的View和ImageView
     */
    private void initViews() {
        mViewPager =  findViewById(R.id.id_viewpager);

        mTabWechat =  findViewById(R.id.id_tab_wechat);
        mTabFriend = findViewById(R.id.id_tab_friend);
        mTabContact = findViewById(R.id.id_tab_contact);
        mTabSetting = findViewById(R.id.id_tab_setting);

        mImgWechat = findViewById(R.id.id_tab_wechat_img);
        mImgFriend = findViewById(R.id.id_tab_friend_img);
        mImgContact = findViewById(R.id.id_tab_contact_img);
        mImgSetting = findViewById(R.id.id_tab_setting_img);

    }

    /**
     * 给tabbar的四个空间设置监听器
     */
    private void initEvents() {
        //设置四个Tab的点击事件
        mTabWechat.setOnClickListener(onClickListener);
        mTabFriend.setOnClickListener(onClickListener);
        mTabContact.setOnClickListener(onClickListener);
        mTabSetting.setOnClickListener(onClickListener);
    }

    /**
     * 设置点击图片变绿
     */
    View.OnClickListener onClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            resetImgs();
            switch (v.getId()) {
                case R.id.id_tab_wechat:
                    selectTab(0);
                    break;
                case R.id.id_tab_friend:
                    selectTab(1);
                    break;
                case R.id.id_tab_contact:
                    selectTab(2);
                    break;
                case R.id.id_tab_setting:
                    selectTab(3);
                    break;
            }
        }
    };

    /**
     * 四个ImageButton设置为灰色
     */

    private void resetImgs() {
        mImgWechat.setImageResource(R.mipmap.tab_weixin_normal);
        mImgFriend.setImageResource(R.mipmap.tab_find_frd_normal);
        mImgContact.setImageResource(R.mipmap.tab_address_normal);
        mImgSetting.setImageResource(R.mipmap.tab_settings_normal);
    }

    /**
     * 将对应的图片设为绿色
     * @param i 图片所在的Fragment的位置
     */
    private void selectTab(int i) {
        //根据点击的Tab设置对应的ImageButton为绿色
        switch (i) {
            case 0:
                mImgWechat.setImageResource(R.mipmap.tab_weixin_pressed);
                break;
            case 1:
                mImgFriend.setImageResource(R.mipmap.tab_find_frd_pressed);
                break;
            case 2:
                mImgContact.setImageResource(R.mipmap.tab_address_pressed);
                break;
            case 3:
                mImgSetting.setImageResource(R.mipmap.tab_settings_pressed);
                break;
        }
        //设置当前点击的Tab所对应的页面
        //setCurrentFragment(i);
        mViewPager.setCurrentItem(i);
    }

    /**
     * 1. 创建Fragment并加入数据源中
     * 2. 创建FragmentPagerAdapter并为ViewPager设置
     * 3. 为ViewPager添加监听器
     */
    private void initDatas() {
        mFragments = new ArrayList<>();
        //将四个Fragment加入集合中
        mFragments.add(new WechatFragment());
        mFragments.add(new FriendFragment());
        mFragments.add(new ContactFragment());
        mFragments.add(new SettingFragment());

        /**
         * 初始化适配器,适配器只需要返回Fragment和总数量的方法
         */
        mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public Fragment getItem(int position) {//从集合中获取对应位置的Fragment
                return mFragments.get(position);
            }

            @Override
            public int getCount() {//获取集合中Fragment的总数
                return mFragments.size();
            }

        };
        //不要忘记设置ViewPager的适配器
        mViewPager.setAdapter(mAdapter);
        //设置ViewPager的切换监听
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            //页面滚动事件,这里没有设置,其实微信是有这个的
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            /**
             * 页面选中的事件,根据用户选中来
             * @param position 页面的位置
             */
            @Override
            public void onPageSelected(int position) {
                //可以直接设置position对应的集合中的Fragment
                mViewPager.setCurrentItem(position);
                resetImgs();
                selectTab(position);
            }

            @Override
            //页面滚动导致状态改变事件
            public void onPageScrollStateChanged(int state) {

            }
        });
    }

}
  1. activity_main,里面引入了button.xml来加入
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"        />
    <android.support.v4.view.ViewPager
        android:id="@+id/id_viewpager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
    </android.support.v4.view.ViewPager>
    <include layout="@layout/bottom"/>
</LinearLayout>
  1. button.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="55dp"
    android:gravity="center"
    android:background="@color/material_blue_grey_800">
    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:id="@+id/id_tab_wechat"
        android:gravity="center"
        android:orientation="vertical">

        <ImageButton
            android:id="@+id/id_tab_wechat_img"
            android:clickable="false"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/tab_weixin_pressed"
            android:background="#00000000"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#ffffff"
            android:text="微信"/>
    </LinearLayout>
    <LinearLayout
        android:id="@+id/id_tab_friend"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:orientation="vertical">

        <ImageButton
            android:id="@+id/id_tab_friend_img"
            android:clickable="false"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/tab_find_frd_normal"
            android:background="#00000000"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#ffffff"
            android:text="朋友"/>
    </LinearLayout>
    <LinearLayout
        android:id="@+id/id_tab_contact"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:orientation="vertical">

        <ImageButton
            android:id="@+id/id_tab_contact_img"
            android:clickable="false"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/tab_address_normal"
            android:background="#00000000"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#ffffff"
            android:text="通讯录"/>
    </LinearLayout>
    <LinearLayout
        android:id="@+id/id_tab_setting"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:orientation="vertical">

        <ImageButton
            android:id="@+id/id_tab_setting_img"
            android:clickable="false"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/tab_settings_normal"
            android:background="#00000000"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#ffffff"
            android:text="设置"/>
    </LinearLayout>

</LinearLayout>
  1. fragment_contact.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ContactFragment">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="通讯录d" />

</FrameLayout>
  1. toolBar
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app = "http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/ab_search"
        android:orderInCategory="90"
        android:title="action_search"
        app:actionViewClass="android.support.v7.widget.SearchView"
        app:showAsAction="ifRoom"/>
    <item
        android:id="@+id/action_share"
        android:orderInCategory="80"
        android:title="action_share"
        app:actionProviderClass="android.support.v7.widget.ShareActionProvider"
        app:showAsAction="ifRoom"/>
    <item
        android:id="@+id/action_settings"
        android:orderInCategory="100"
        android:title="action_settings"
        app:showAsAction="never"/>

</menu>
发表在 未分类 | 留下评论