Android 中级

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容