美文网首页
Android 中级

Android 中级

作者: 小甜妮子__ | 来源:发表于2017-02-18 18:40 被阅读51次

    1.LayoutInflater
    (1)主要是用于加载布局;
    (2)setContentView()的内部原理就是LayoutInflater;
    (3)LayoutInflater的实例:
    <pre>
    //实质是后者的封装
    LayoutInflater layoutInflater = LayoutInflater.from(context);
    // And
    LayoutInflater layoutInflater = (LayoutInflater) context
    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    </pre>
    (3)实例化之后使用inflate()方法来加载布局
    <pre>
    // resourceId:要加载的布局的ID
    // root:给改布局的外层再嵌套一个父布局
    layoutInflater.inflate(resourceId, root);
    </pre>
    (4)原理:LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的。

    2.Serivice
    (1)Serivce启动:startService(),且一旦以此方式启动的Serivce在stopService()调用之前只能启动一次,即onCreate()只执行一次,知道onDestroy();
    (2)Serivice与Activity的通信:onBind()方法起关键作用。
    <pre>
    private MyService.MyBinder myBinder;
    </br>
    Intent bindIntent = new Intent(this, MyService.class);
    bindService(bindIntent, connection, BIND_AUTO_CREATE);
    </br>
    private ServiceConnection connection = new ServiceConnection() {

        @Override  
        public void onServiceDisconnected(ComponentName name) {  
        }  
    
        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            myBinder = (MyService.MyBinder) service;  
            myBinder.startDownload();  
        }  
    };
    

    </pre>
    (3)Service与Thread的关系--没有任何关系
    不少Android初学者都可能会有这样的疑惑,Service和Thread到底有什么关系呢?什么时候应该用Service,什么时候又应该用Thread?答案可能会有点让你吃惊,因为Service和Thread之间没有任何关系!

    之所以有不少人会把它们联系起来,主要就是因为Service的后台概念。Thread我们大家都知道,是用于开启一个子线程,在这里去执行一些耗时操作就不会阻塞主线程的运行。而Service我们最初理解的时候,总会觉得它是用来处理一些后台任务的,一些比较耗时的操作也可以放在这里运行,这就会让人产生混淆了。但是,如果我告诉你Service其实是运行在主线程里的,你还会觉得它和Thread有什么关系吗?
    你可能会惊呼,这不是坑爹么!?那我要Service又有何用呢?其实大家不要把后台和子线程联系在一起就行了,这是两个完全不同的概念。Android的后台就是指,它的运行是完全不依赖UI的。即使Activity被销毁,或者程序被关闭,只要进程还在,Service就可以继续运行。比如说一些应用程序,始终需要与服务器之间始终保持着心跳连接,就可以使用Service来实现。你可能又会问,前面不是刚刚验证过Service是运行在主线程里的么?在这里一直执行着心跳连接,难道就不会阻塞主线程的运行吗?当然会,但是我们可以在Service中再创建一个子线程,然后在这里去处理耗时逻辑就没问题了。
    额,既然在Service里也要创建一个子线程,那为什么不直接在Activity里创建呢?这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况。

    一个比较标准的Service就可以写成:
    <pre>
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    new Thread(new Runnable() {
    @Override
    public void run() {
    // 开始执行后台任务
    }
    }).start();
    return super.onStartCommand(intent, flags, startId);
    }

    class MyBinder extends Binder {

    public void startDownload() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 执行具体的下载任务
            }
        }).start();
    }
    

    }
    </pre>
    (4)创建前台Service
    Service几乎都是在后台运行的,一直以来它都是默默地做着辛苦的工作。但是Service的系统优先级还是比较低的,当系统出现内存不足情况时,就有可能会回收掉正在后台运行的Service。如果你希望Service可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台Service。前台Service和普通Service最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。当然有时候你也可能不仅仅是为了防止Service被回收才使用前台Service,有些项目由于特殊的需求会要求必须使用前台Service,比如说墨迹天气,它的Service在后台更新天气数据的同时,还会在系统状态栏一直显示当前天气的信息。
    <pre>
    public class MyService extends Service {

    public static final String TAG = "MyService";
    
    private MyBinder mBinder = new MyBinder();
    
    @Override
    public void onCreate() {
        super.onCreate();
        Notification notification = new Notification(R.drawable.ic_launcher,"有通知到来", System.currentTimeMillis());
        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,notificationIntent, 0);
        notification.setLatestEventInfo(this, "这是通知的标题", "这是通知的内容",pendingIntent);
        //通过此方法设置为前台Serivice
        startForeground(1, notification);
        Log.d(TAG, "onCreate() executed");
    }
    
    .........
    

    }
    </pre>
    (5)远程Serivce--可以实现Android跨进程通信
    将一个普通的Service转换成远程Service其实非常简单,只需要在注册Service的时候将它的android:process属性指定成:remote就可以了,代码如下所示:
    <pre>
    <service
    android:name="com.example.servicetest.MyService"
    android:process=":remote" >
    </service>
    </pre>
    为什么将MyService转换成远程Service后就不会导致程序ANR了呢?这是由于,使用了远程Service后,MyService已经在另外一个进程当中运行了,所以只会阻塞其所在进程中的主线程,并不会影响到当前的应用程序。(测试验证可以用Process.myPid()进程ID)

    Paste_Image.png

    可以看出进程ID和包名都以改变。

    远程Serivice的弊端:
    远程Serivice与Activity通信不再能使用bindSerivice()的方式了,此时只能使用AIDL来进行跨进程通信了。

    (6)AIDL(Android Interface Definition Language)是Android接口定义语言的意思,它可以用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。
    http://blog.csdn.net/guolin_blog/article/details/9797169

    3.Gson
    http://stormzhang.com/android/2014/05/22/android-gson/
    </br>
    4.Android 布局优化
    (1)在Android开发中性能优化可能包括:Java代码优化, 算法优化, SQLite优化, 布局优化等。
    (2)在Android UI布局过程中,通过遵守一些惯用、有效的布局原则,我们可以制作出高效且复用性高的UI,概括来说包括如下几点:

    --尽量多使用RelativeLayout和LinearLayout, 不要使用绝对布局AbsoluteLayout,布局层次一样的情况下LinearLayout比RelativeLayout要好, 但往往RelativeLayout可以简单实现LinearLayout嵌套才能实现的布局;</br>
    --将可复用的组件抽取出来并通过include标签使用;
    (既能提高UI的制作和复用效率,也能保证制作的UI布局更加规整和易维护。)
    header.xml布局:
    <pre>
    <?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="match_parent" >
    <Button
    android:id="@+id/button"
    android:layout_width="match_parent"
    android:layout_height="@dimen/dp_40"
    android:layout_above="@id/text"/>
    <TextView
    android:id="@+id/text"
    android:layout_width="match_parent"
    android:layout_height="@dimen/dp_40"
    android:layout_alignParentBottom="true"
    android:text="@string/app_name" />
    </RelativeLayout>
    </pre>
    main_activity.xml中使用:
    <pre>
    <include layout="@layout/header" />
    </pre>
    --使用ViewStub标签来加载一些不常用的布局;
    viewstub标签同include标签一样可以用来引入一个外部布局,不同的是,viewstub引入的布局默认不会扩张,即既不会占用显示也不会占用位置,从而在解析layout时节省cpu和内存。 viewstub常用来引入那些默认不会显示,只在特殊情况下显示的布局,如进度布局、网络失败显示的刷新布局、信息出错出现的提示布局等。
    例如显示错误的布局:
    <pre>
    //error.xml
    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:background="@android:color/white"
    android:padding="10dip"
    android:text="Message"
    android:textColor="@android:color/black" />
    </pre>
    <pre>
    //main.xml
    <ViewStub
    android:id="@+id/error_layout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:layout="@layout/error" />
    </pre>
    <pre>
    //MainActivity种使用方法
    private View errorView;

    private void showError() {
    // not repeated infalte
    if (errorView != null) {
    errorView.setVisibility(View.VISIBLE);
    return;
    }

    ViewStub stub = (ViewStub)findViewById(R.id.error_layout);
    errorView = stub.inflate();
    

    }

    private void showContent() {
    if (errorView != null) {
    errorView.setVisibility(View.GONE);
    }
    }
    </pre>
    --使用merge标签减少布局的嵌套层次;
    (用来合并当前布局和父布局,有两种情况:)
    布局根结点是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity内容布局的parent view就是个FrameLayout,所以可以用merge消除只剩一个:
    <pre>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="hello world" />

        <RelativeLayout 
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center" >
    
            <include layout="@layout/header" />
    
        </RelativeLayout>
    

    </merge>
    </pre>
    某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中:
    <pre>
    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_above="@id/text"/>
    
    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_alignParentBottom="true"
        android:text="@string/app_name" />
    

    </merge>
    </pre>

    5.Serializable和Parcelable
    <pre>
    package com.tutor.objecttran;
    import java.io.Serializable;
    public class Person implements Serializable {
    private static final long serialVersionUID = -7060210544600464481L;
    private String name;
    private int age;
    public String getName() {
    return name;
    }
    public void setName(String name) {
    this.name = name;
    }
    public int getAge() {
    return age;
    }
    public void setAge(int age) {
    this.age = age;
    }
    }
    </pre>
    <pre>
    package com.tutor.objecttran;
    import android.os.Parcel;
    import android.os.Parcelable;
    public class Book implements Parcelable {
    private String bookName;
    private String author;
    private int publishTime;

    public String getBookName() {
        return bookName;
    }
    public void setBookName(String bookName) {
        this.bookName = bookName;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
    public int getPublishTime() {
        return publishTime;
    }
    public void setPublishTime(int publishTime) {
        this.publishTime = publishTime;
    }   
    public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>() {
        public Book createFromParcel(Parcel source) {
            Book mBook = new Book();
            mBook.bookName = source.readString();
            mBook.author = source.readString();
            mBook.publishTime = source.readInt();
            return mBook;
        }
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
    public int describeContents() {
        return 0;
    }
    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeString(bookName);
        parcel.writeString(author);
        parcel.writeInt(publishTime);
    }
    

    }
    </pre>

    6.Handler和Message
    过程:
    创建Handler-->发送消息到MessageQueue-->Lopper.loop()取出消息并回调dispatchMessage()方法-->handleMessage处理消息。
    (1)异步消息处理线程;
    (2)在Handler创建的源码种得知:在任何一个线程中如果要new Handler(),必先调用Looper.prepare()为当前线程创建一个Looper对象,之后才能创建Handler对象,且每一个线程只能创建一个Looper对象;
    (3)对于主线程:程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法,可以查看ActivityThread中的main()方法;

    Paste_Image.png
    (4)其他可以在子线程中进行UI操作的方法:
    --Handler的post()方法;
    <pre>
    public class MainActivity extends Activity {
    private Handler handler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    handler = new Handler();
    new Thread(new Runnable() {
    @Override
    public void run() {
    //我们在Runnable对象的run()方法里更新UI,效果完全等同于在handleMessage()方法中更新UI
    handler.post(new Runnable() {
    @Override
    public void run() {
    // 在这里进行UI操作
    }
    });
    }
    }).start();
    }
    }
    </pre>
    --View的post()方法;
    源码中实质是调用了Handler.post()方法;
    <pre>
    public boolean post(Runnable action) {
    Handler handler;
    if (mAttachInfo != null) {
    handler = mAttachInfo.mHandler;
    } else {
    ViewRoot.getRunQueue().post(action);
    return true;
    }
    return handler.post(action);
    }
    </pre>
    --Activity的runOnUiThred()方法;
    直接看源码就一目了然了:
    <pre>
    public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
    mHandler.post(action);
    } else {
    action.run();
    }
    }
    </pre>
    即:不管是使用哪种方法在子线程中更新UI,其实背后的原理都是相同的,必须都要借助异步消息处理的机制来实现。
    </br>
    7.AsyncTask
    (1)AsyncTask的基本用法:
    --AsyncTask是一个抽象类;</br>
    --AsyncTask类指定三个泛型参数,这三个参数的用途如下:
    1)Params:在执行AsyncTask时需要传入的参数,可用于在后台任务中使用;
    2)Progress:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位;
    3)Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型;</br>
    --需要去重写的方法有以下四个:
    1)onPreExecute()
    这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等;
    2)doInBackground(Params...)
    这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法来完成;
    3)onProgressUpdate(Progress...)
    当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新;
    4)onPostExecute(Result)
    当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
    一个比较完整的自定义AsyncTask就可以写成如下方式:
    <pre>
    class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
    @Override
    protected void onPreExecute() {
    progressDialog.show();
    }
    @Override
    protected Boolean doInBackground(Void... params) {
    try {
    while (true) {
    int downloadPercent = doDownload();
    publishProgress(downloadPercent);
    if (downloadPercent >= 100) {
    break;
    }
    }
    } catch (Exception e) {
    return false;
    }
    return true;
    }
    @Override
    protected void onProgressUpdate(Integer... values) {
    progressDialog.setMessage("当前下载进度:" + values[0] + "%");
    }
    @Override
    protected void onPostExecute(Boolean result) {
    progressDialog.dismiss();
    if (result) {
    Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show();
    } else {
    Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
    }
    }
    }
    </pre>
    (2)分析AsyncTask的源码
    http://blog.csdn.net/guolin_blog/article/details/11711405

    相关文章

      网友评论

          本文标题:Android 中级

          本文链接:https://www.haomeiwen.com/subject/pegrwttx.html