概述
四大组件:
Activity
AndroidRuntime: android.util.AndroidRuntimeException: Calling startActivity from outside of
an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag .Is this really what you want?
3).singleTask:栈内复用模式。
这是一种单实例模式,只要Activity在一个栈中存在,那么多次启动Activity都不会重新创建实例,和singleTop一样,系统会回调其onNewIntent。当一个具有singleTask模式的Activity请求启动后,比如Activity A,系统首先会寻找是否存在A想要的任务栈,如果不存在,就重新创建一个任务栈,然后创建A的实例后把A放入栈中。如果存在A所需要的任务栈,这时就要看栈中的A的实例存在,如果实例存在,那么系统就会把A调到栈顶并调用它的onNewIntent方法,如果实例不存在,就创建A的事例并把A压入栈中。举例:
a.比如目前任务栈中S1的情况是ABC,这个时候Activity D以singleTask的,模式请求启动,其所需要的是任务栈S2,由于S2和D的实例都不存在,所以系统会先创建任务栈S2,然后再创建D的实例并将其入栈S2.
b.另外一种情况,假设D所需要的任务栈为S1,那么由于S1已经存在了,所以系统会直接创建D的实例,并将其入栈S1.
c.如果D所需要的任务栈为S1,此时S1的情况为ADBC,根据栈内复用的原则,此时D不会创建,系统会把D切换到栈顶并调用其onNewIntent方法,同时由于singleTask默认具有clearTop的效果,会导致栈内所有在D上面的Activity全部出栈,于是最终S1的情况为AD。
4).singleInstance:单实例模式。
加强版的singleTask模式,除了具备singleTask的所有特性外,还加强了一点,具有此模式的Activity只能单独位于一个任务栈中。
public class MyApp extends Application
{
private Bitmap mBitmap;
public Bitmap getBitmap()
{
return mBitmap;
}
public void setBitmap(Bitmap bitmap)
{
this.mBitmap = bitmap;
}
}
<pre name="code" class="java"><application android:name=".MyApp" android:icon="@drawable/icon" android:label="@string/app_name">
</application>
获得Bitmap对象的代码:
ImageView imageview = (ImageView)findViewById(R.id.ivImageView);
MyApp myApp = (MyApp)getApplication();
imageview.setImageBitmap(myApp.getBitmap());
上面的代码可以在任何Service和Activity中使用,全局的。
8.Activity的Task相关: http://blog.csdn.net/liuhe688/article/details/6761337
package com.example.result;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
public static final int REQUEST_CODE1 = 1;
public static final int REQUEST_CODE2 = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,
SecondActivity.class);
startActivityForResult(intent, REQUEST_CODE1);
}
});
Button button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,
ThridActivity.class);
startActivityForResult(intent, REQUEST_CODE2);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE1) {
Toast.makeText(getApplicationContext(),
"111->" + data.getStringExtra("njj"), Toast.LENGTH_SHORT)
.show();
}
if (requestCode == REQUEST_CODE2) {
Toast.makeText(getApplicationContext(),
"222->" + data.getStringExtra("njj"), Toast.LENGTH_SHORT)
.show();
}
}
}
package com.example.result;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class SecondActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Button button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("njj", "niejianjian - second");
setResult(RESULT_OK, intent);
finish();
}
});
}
}
package com.example.result;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class ThridActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thrid);
Button button3 = (Button) findViewById(R.id.button3);
button3.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("njj", "niejianjian - thrid");
setResult(RESULT_OK, intent);
finish();
}
});
}
}
参考博客:
http://blog.csdn.net/liuhe688/article/details/6733407
BroadcastReceiver
<receiver android:name="com.example.boradcasttest.MyReceive2" >
<intent-filter android:priority="30" >
<action android:name="njj.test.receive" />
</intent-filter>
</receiver>
Intent intent = new Intent();
intent.setAction("njj.test.receive");
sendBroadcast(intent);
IntentFilter filter = new IntentFilter();
filter.addAction("njj.test.receive"); // 添加actionyongyu匹配需要接受的广播
registerReceiver(receiver, filter); // receiver是一个广播对象。
<span style="white-space:pre"> </span>BroadcastReceiver receiver = new BroadcastReceiver() {
<span style="white-space:pre"> </span>@Override
<span style="white-space:pre"> </span>public void onReceive(Context context, Intent intent) {
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>};
取消绑定只需要执行
unregisterReceiver(receiver);
一般在onStart中注册,在onStop中取消解绑。
6.动态注册的广播,永远要快于静态注册的广播,不管静态注册的优先级设置有多高,不管动态注册的优先级有多低。因为代码中注册的运行在内存中,xml在系统中。
<receiver android:name="com.example.boradcasttest.MyReceive1" >
<intent-filter android:priority="20" >
<action android:name="njj.test.receive" />
</intent-filter>
</receiver>
<receiver android:name="com.example.boradcasttest.MyReceive2" >
<intent-filter android:priority="0" >
<action android:name="njj.test.receive" />
</intent-filter>
</receiver>
在一个按钮的点击事件中发送广播
Intent intent = new Intent();
intent.setAction("njj.test.receive");
intent.putExtra("key", "发送给receive1的广播");
sendOrderedBroadcast(intent, null);
public class MyReceive1 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("onreceive");
if (intent.getAction().equals("njj.test.receive")) {
Toast.makeText(context, intent.getStringExtra("key"),
Toast.LENGTH_SHORT).show();
Bundle bundle = new Bundle();
bundle.putString("re1", "有序广播");
setResultExtras(bundle);
// abortBroadcast(); // 终止传递
}
}
}
public class MyReceive2 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = getResultExtras(true);
String msg = bundle.getString("re1");
String msg2 = intent.getStringExtra("key");
System.out.println("msg -> " + msg);
System.out.println("msg2 -> " + msg2);
}
}
广播会先传递到优先级高的MyReceive1,然后在传递到MyReceive2,在MyReceive1中可以将广播终止传递,也可以传递给下一个接受者数据。
参考博客:http://blog.csdn.net/liuhe688/article/details/6955668
Service
package com.example.boradcasttest;
import android.app.Activity;
import android.app.IntentService;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity {
MyConn myConn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myConn = new MyConn();
Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("click ----> button1");
Intent intent = new Intent(MainActivity.this, MyService1.class);
startService(intent);
}
});
Button button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("click ----> button2");
Intent intent = new Intent(MainActivity.this, MyService1.class);
stopService(intent);
}
});
Button button3 = (Button) findViewById(R.id.button3);
button3.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("click ----> button3");
Intent intent = new Intent(MainActivity.this, MyService1.class);
bindService(intent, myConn, BIND_AUTO_CREATE);
}
});
Button button4 = (Button) findViewById(R.id.button4);
button4.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("click ----> button4");
unbindService(myConn);
}
});
Button button5 = (Button) findViewById(R.id.button5);
button5.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("click ----> button5");
Intent intent = new Intent();
intent.setAction("stop.service1");
sendBroadcast(intent);
}
});
}
private class MyConn implements ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder service) {
// 可以通过IBinder的对象 去使用service里面的方法
}
public void onServiceDisconnected(ComponentName name) {
}
}
}
package com.example.boradcasttest;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.IBinder;
public class MyService1 extends Service {
@Override
public IBinder onBind(Intent intent) {
System.out.println("MyService1 -> onBind");
return null;
}
@Override
public void onRebind(Intent intent) {
System.out.println("MyService1 -> onRebind");
super.onRebind(intent);
}
@Override
public boolean onUnbind(Intent intent) {
System.out.println("MyService1 -> onUnbind");
return super.onUnbind(intent);
}
@Override
public void onCreate() {
System.out.println("MyService1 -> onCreate");
super.onCreate();
}
@Override
@Deprecated
public void onStart(Intent intent, int startId) {
System.out.println("MyService1 -> onStart");
IntentFilter filter = new IntentFilter();
filter.addAction("stop.service1");
registerReceiver(receiver, filter);
super.onStart(intent, startId);
}
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
System.out.println("MyService1 -> bindService");
return super.bindService(service, conn, flags);
}
@Override
public void unbindService(ServiceConnection conn) {
System.out.println("MyService1 -> unbindService");
super.unbindService(conn);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
System.out.println("MyService1 -> onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public boolean stopService(Intent name) {
System.out.println("MyService1 -> stopService");
stopSelf(); // 停止服务
return super.stopService(name);
}
@Override
public void onDestroy() {
System.out.println("MyService1 -> onDestroy");
super.onDestroy();
}
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("MyService1 -> receiver");
if (intent.getAction().equals("stop.service1")) {
context.stopService(intent);
}
}
};
}
<service android:name="MyService1" ></service>
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification noti = new Notification.Builder(this)
.setContentTitle("Title")
.setContentText("Message")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.build();
startForeground(12346, noti);
return Service.START_STICKY;
}
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
//经测试,Service里面是不能进行耗时的操作的,必须要手动开启一个工作线程来处理耗时操作
System.out.println("onStart");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("睡眠结束");
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
public class MyIntentService extends IntentService {
public MyIntentService() {
super("yyyyyyyyyyy");
}
@Override
protected void onHandleIntent(Intent intent) {
// 经测试,IntentService里面是可以进行耗时的操作的
//IntentService使用队列的方式将请求的Intent加入队列,然后开启一个worker thread(线程)来处理队列中的Intent
//对于异步的startService请求,IntentService会处理完成一个之后再处理第二个
System.out.println("onStart");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("睡眠结束");
}
}
public class ServiceDemoActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
startService(new Intent(this,MyService.class));//主界面阻塞,最终会出现Application not responding
//连续两次启动IntentService,会发现应用程序不会阻塞,而且最重的是第二次的请求会再第一个请求结束之后运行(这个证实了IntentService采用单独的线程每次只从队列中拿出一个请求进行处理)
startService(new Intent(this,MyIntentService.class));
startService(new Intent(this,MyIntentService.class));
}
}
IntentServicede 的好处:
public class HelloIntentService extends IntentService {
/**
* A constructor is required, and must call the super IntentService(String)
* constructor with a name for the worker thread.
*/
public HelloIntentService() {
super("HelloIntentService");
}
/**
* The IntentService calls this method from the default worker thread with
* the intent that started the service. When this method returns, IntentService
* stops the service, as appropriate.
*/
@Override
protected void onHandleIntent(Intent intent) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
}
}
如果需要重写其他回调方法,如onCreate,onStartCommand()等,一定要调用super()方法,保证intentservice正确处理worker线程,只有onHandleIntent()和onBind()不需要这样,如:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
return super.onStartCommand(intent,flags,startId);
}
Service对象不能自己启动,需要通过某个Activity、Service或者其他Context对象来启动,启动的方法有两种,Context.startService()和Context.bindService()。两种方式的生命周期是不同的。
context.startService:
如果service还没有运行,则android先调用onCreate()方法,然后调用onStart();如果service已经运行,则至调用onStart(),所以一个service的onStart()方法可能会重复调用多次。stopService的时候会直接调用onDestory(),如果是调用者自己直接退出而没有调用stopService的话,service会一直在后台运行,该service的调用者再次启动起来后可以通过stopService关闭service
1.在同一个应用任何地方调用startService()方法就能启动Service了,然后系统会回调service类的onCreate()以及onStart()方法,这样启动的service会一直运行在后台,知道Context.stopService()或者selfStop()方法被调用。
2.另外一种bindService()方法的意思是,把这个Service和调用service的客户类绑起来,如果调用这个客户类被销毁,service也会被销毁,用这个方法的一个好处是,bindService()方法执行后service会回调上边提到的onBind()方法,你可以从这里返回一个实现了IBind接口的类,在额客户端操作这个类就能和这个服务通信了, 比如得到service运行的状态或其他操作,如果service还没有运行,使用这个方法启动service就会onCreate()方法而不会调用onStart().
3.startService()的目的是回调onStart()方法,onCreate()方法是在Service不存在的时候调用的,如果Service存在(例如之前调用了bindService,那么Service的onCreate方法已经被调用了)那么startService()将跳过onCreate()方法。
4.bindService()目的是回调onBind()方法,它的作用是在Service和调用者之间建立一个桥梁,并不负责更多的工作(例如一个service需要连接服务器的操作),一般使用bindService来帮顶到一个现有的Service(即通过StartService启动的服务)
由于Service的onStart()方法只有在startService()启动service的情况下才会调用,故使用onStart()的时候要注意这点。
5.与Service通信并且让它持续运行:如果我们想要保持和Service的通信,又不想让Service随着Activity退出而退出。你可以先startService()然后在bindService()。当你不需要帮顶的时候就执行unbindService()方法,执行这个方法只会触发Service的onUnbind()方法而不会把这个Service销毁,这样就可以既保持和service的通信,也不会随着activity销毁而销毁。
6.为什么是foregorund:默认启动的Service是被标记为background,当前运行的Activity一般被标记为foreground,也就是说你给service设置了foreground那么他就和正在运行的Activity类似优先级得到了一定的提高。当让这并不能保证你service永远不被杀掉。只是提高了他的优先级
7.如果先是bind了,那么start的时候就直接运行service的onStart方法,如果是先start,那么bind的时候就直接运行onBind方法,如果你先bind上了,就stop不掉了,只能先执行UnbindService,再stopService,所以是先start还是先bind行为是有区别的。
8.如果打算采用Context.startService()方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onStart()。如果调用startService()方法前服务已经被创建,多次调用startService()方法,并不会导致多次创建服务,但会导致多次调用onStart()方法,采用startService()方法启动的服务只能调用Context.stopService()方法结束服务,服务结束时会被调用onDestory()。
9.如果打算采用Context.bindService()方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onBind()方法,这个时候调用者和服务绑定在一起,调用者退出,系统就会先调用服务的onUnbind()方法,接着调用onDestory()方法,如果调用bindService()方法前服务已经被绑定,多次调用bindService()并不会导致多次创建服务及绑定(也就是说onCreate和onBind方法并不会多次调用)。如果调用者希望与正在绑定的服务解除绑定,可以调用unbindService()方法。
10.Service和Thread的区别:
Thread是程序执行的最小单位,他是分配CPUde基本但那位,可以用Thread来执行一些异步的操作。
Service是android的一种机制,当它运行的时候,如果是Local Service,那么对应的Service是运行在主进程的main线程上的。如onCreate,onStart这些函数在被系统调用的时候都是在主进程的main线程上运行的。如果Remote Service,那么对应的Service则是运行在独立进程的main线程上,因此不要把Service理解成线程,它和线程半毛钱关系都没有。
那么我们为什么要用Service? Thread的运行是独立于Activity的,也就是说当一个Activity被finish之后,如果你没有主动停止Thread或者Thread中的run方法没有执行完毕,Thread也会一直执行。因此这里会出现一个问题,当Activity被finish掉之后,你不在持有该Thread的引用,另一方面,你没有办法在不同的Activity中对同一个Thread进行控制。
举个例子:如果你的Thread需要不停的隔一段时间就要连接服务器做某种同步的话,该Thread需要在Activity没有start的时候就运行。这个时候当你start一个Activity就没有办法在该Activity里面控制之前创建的Thread。因此你需要创建并启动一个Service。在Service里面创建、运行并控制该Thread,这样便解决了该问题(因为任何Activity都可以控制同一个Service,而系统也只会创建一个对应的Service实例)
因此你可以把Service想象成一种消息服务,而你可以在任何有Context的地方调用StartService、Context.stopService、Context.bindService、Context.unbindService,来控制它,你也可以在Service中注册BroadcastReceiver,在其他地方通过发送broadcast来控制它,当然这些都是Thread做不到的。
Service中可以正常显示Toast,IntentService中不能正常显示Toast,在2.3系统上,不显示toast,在4.3系统上,toast显示,但是不会消失。
2. 原因
Toast要求运行在UI主线程中。
Service运行在主线程中,因此Toast是正常的。
IntentService运行在独立的线程中,因此Toast不正常。
3. 在IntentService中显示Toast
利用Handler,将显示Toast的工作,放在主线程中来做。具体有两个实现方式。
Handler的post方式实现,这个方式比较简单。
private void showToastByRunnable(final IntentService context, final CharSequence text, final int duration) {
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(context, text, duration).show();
}
});
}
Handler的msg方式实现,这个方式比较复杂。
Handler msgHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
Toast.makeText(ToastIntentService.this, msg.getData().getString("Text"), Toast.LENGTH_SHORT).show();
super.handleMessage(msg);
}
};
private void showToastByMsg(final IntentService context, final CharSequence text, final int duration) {
Bundle data = new Bundle();
data.putString("Text", text.toString());
Message msg = new Message();
msg.setData(data);
msgHandler.sendMessage(msg);
}
4. 关于耗时操作
Service中如果有耗时的操作,要开启一个Thread来做。
IntentService是在独立的线程中,所以可以进行一些耗时操作。
5. 考虑AsyncTask与Service的使用区别
如果是全后台的工作,使用Service,结果的提示可以使用Notification。
如果是异步工作,工作结束后需要更新UI,那么最好使用Thread或者AsyncTask。
Service弹出dialog : http://blog.csdn.net/djun100/article/details/23756681
ContentProvider
1.Android有一个独到之处,就是数据库只能被它的创建者所使用,其他的应用是不能访问到的,所以如果你想实现不同应用之间的数据共享,就不得不用contentprovider了。在Android中,contentprovider是一个特殊的存储数据的类型,他提供了一套标准的接口用来获取以及操作数据,并且,android自身也提供了几个现成的contentprovider:Contacts,Browser,CallLog,Settings,MediaStore。
Intent
android应用的三大组件——Activitied、Services、BroadcastReceiver,通过消息触发,这个消息就称作意图(Intent)。
2.Intent可以传递的数据类型:从原码查看:
1).基本数据类型
Intent putExtra(String name, int[] value)
Intent putExtra(String name, float value)
Intent putExtra(String name, byte[] value)
Intent putExtra(String name, long[] value)
Intent putExtra(String name, float[] value)
Intent putExtra(String name, long value)
Intent putExtra(String name, String[] value)
Intent putExtra(String name, boolean value)
Intent putExtra(String name, boolean[] value)
Intent putExtra(String name, short value)
Intent putExtra(String name, double value)
Intent putExtra(String name, short[] value)
Intent putExtra(String name, String value)
Intent putExtra(String name, byte value)
Intent putExtra(String name, char[] value)
Intent putExtra(String name, CharSequence[] value)
public Intent putExtra(String name, CharSequence value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putCharSequence(name, value);
return this;
}
2).传递Bundle
public Intent putExtra(String name, Bundle value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putBundle(name, value);
return this;
}
3).传递Parcelable对象
public Intent putExtra(String name, Parcelable value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putParcelable(name, value);
return this;
}
4).传递Serializable对象
public Intent putExtra(String name, Serializable value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putSerializable(name, value);
return this;
}
5).传递Intent对象
public Intent putExtras(Intent src) {
if (src.mExtras != null) {
if (mExtras == null) {
mExtras = new Bundle(src.mExtras);
} else {
mExtras.putAll(src.mExtras);
}
}
return this;
}
6).传递IBinder对象
@Deprecated
public Intent putExtra(String name, IBinder value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putIBinder(name, value);
return this;
}
归根结底都是通过new Bundle()来实现数据封装,而Bundle则是通过map来存储数据的
public Bundle() {
mMap = new ArrayMap<String, Object>();
mClassLoader = getClass().getClassLoader();
}
A description of an Intent and target action to perform with it. Instances of this class are created with getActivity(Context, int, Intent, int), getActivities(Context, int, Intent[], int), getBroadcast(Context, int, Intent, int), and getService(Context, int, Intent, int); the returned object can be handed to other applications so that they can perform the action you described on your behalf at a later time.
获得一个PendingIntent实例,通过getActivity、getBroadcast、getService、getActivities等方法。要得到实例,参数必须必须传入的是Intent和context。
PendingIntent是一种特殊的Intent。主要区别在于Intent的执行是立刻的,而PendingIntent则不是。PendingIntent执行的操作实质上是参数Intent传递进来的操作,正由于PendingIntent中保存有当前app的context,使它赋予了外部app的能力,使得外部app可以如同当前app一样的执行pengdingIntent里面的intent,就算在执行时当前app已经不存在了,也能同pendingintent里面的context来执行。另外还可以处理intent执行后的操作。主要用在Notification、SmsManager和AlarmManager等。
PendingIntent和Intent的区别:(1).Intent是立即使用的,而PendingIntent可以的呢感到时间发生后触发,PendingIntent可以cancel 。(2).Intent在程序结束后即终止,而PendingIntent在程序结束后仍然有效 。(3).PendingIntent自带context,而Intent需要在某个Context中运行 。(4).Intent在原task中执行,PendingIntent在新的task中执行。
PendingIntnet的匹配规则:如果两个PendingIntent它们内部的Intent相同并且requesstCode也相同,那么这两个PendingIntent就是相同的。
Intent的匹配规则:如果两个Intent的componentName和intent-filter都相同,那么这两个Intent就是相同的,即使它们的Extras不同。
参考博客:http://blog.csdn.net/zeng622peng/article/details/6180190 http://blog.csdn.net/yuzhiboyi/article/details/8484771
http://lovelydog.iteye.com/blog/1666340
4.IntentFilter
顾名思义,IntentFilter对象负责过滤掉无法响应和处理的intent,只将自己关心的intent接受进来进行处理,intentFilter实行白名单管理,但IntentFilter只会过滤掉隐式的intent,显式的intent会直接传送到目标组件。android组件可以有一个或者多个intentfilter,每个之间都互相独立,只需要其中一个验证通过即可。除了用于过滤广播的intentfilter可以在代码中创建外,其他的intentfilter都必须在Androidmanifest.xml文件中进行声明。
http://blog.csdn.net/today520/article/details/7000048
Action是要求Intent中必须有一个action且必须能够和过来规则中的某个action相同,而category要求Intent可以没有category,但是如果你一旦有category,不管有几个,每个都要能够和过来规则中的任何一个category相同。
为什么不设置category也能匹配?原因是系统在调用startActivity和startActivityForResult的时候会默认为Intent加上“android.intent.category.DEFAULT”这个category。所以category就可以匹配前面的过滤规则中的第三个category。同时,为了我们的activity能够接受到隐式调用,就必须在intent-filter中指定“android.intent.category.DEFAULT”这个category。
参考博客:http://blog.csdn.net/annkie/article/details/8483253
Hanlder
主线程初始化的时候,默认创建了looper。Handler在使用之前必须有looper的存在,否则会报错。如果在非主线成使用handler,必须创建looper。
ActivityThread中的main()方法中,系统已经自动调用了Looper.prepare();
不是的,有以下两种:
一,如果不带参数的实例化:Handler handler = new Handler();那么这个会默认用当前线程的looper。
二,一般而言如果Handler是要来刷新操作UI的,那么就需要在主线程下跑。那么在主线程 Handler handler = new Handler()。
三,如果在其他线程,也要满足这个功能的话,要Handler handler = new Handler(Looper.getMainLooper());不用刷新ui,只是处理消息。
四,当前线程如果是主线程的话,Handler handler = new Handler();不是主线程的话,Looper.prepare(); Handler handler = new Handler();Looper.loop();或者Handler handler = new Handler(Looper.getMainLooper())。
消息循环和消息队列都是属于THread,而Handler本身并不具有Looper和MessageQueue;但是消息系统的建立和交互,是Thread将Looper和MessageQueue交给某个Handler维护建立消息模型,所以消息系统模型的核心就是Looper。消息循环和消息队列都是由Looper建立的,而建立Handler的关键就是这个Looper。
一个Thread同时可以对应多个Handler,一个Handler同时只能属于一个Thread。Handler属于那个Thread取决于Handler在哪个Thread中建立。在一个Thread中Looper也是唯一的,一个Thread对应一个Looper,建立Handler的Looper来自哪个Thread,Handler就属于哪个Thread。故建立Thread消息系统,就是将Thread的Looper交给Handler去打理,实现消息系统模型,完成消息的异步处理。
在线程中建立Handler:
1).获得Thread的Looper交给Handler的成员变量引用维护;
2).通过Looper获取MessageQueue交个Hanler的成员变量引用维护。
消息发送和派发接口:
1).post(Runnable)消息,Runnable是消息回调,经过消息循环引发消息回调函数执行;
2).sendMessage(Message)消息,经过消息循环派发消息处理函数中处理消息;
3).diapatchMessage派发消息,若是post或带有回调函数则执行回调函数,否则执行消息处理函数Handler的handleMessage(通常派生类重写)。
简单的几个方法使用:package com.example.handlertestdemo;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity {
public int a = 0;
Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 0:
a++;
System.out.println("a = " + a);
sendEmptyMessageDelayed(0, 1000);
if (a == 5) {
if (hasMessages(0)) {
removeMessages(0); // 终止消息的执行
}
}
break;
case 1:
System.out.println("msg.what = 1 ---> msg0 is exits = "
+ hasMessages(0));
sendEmptyMessageDelayed(1, 7000);
removeCallbacks(runnable); // 终止线程的执行
break;
default:
break;
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handler.sendEmptyMessageDelayed(0, 3000);
handler.sendEmptyMessage(1);
}
});
}
Runnable runnable = new Runnable() {
@Override
public void run() {
// 执行内容
}
};
}
简单的说,Handler获取当前线程中的looper对象,looper用来从存放Message的MessageQueue中取出Message,再有Handler进行Message的分发和处理.
Message Queue(消息队列):用来存放通过Handler发布的消息,通常附属于某一个创建它的线程,可以通过Looper.myQueue()得到当前线程的消息队列.
Handler:可以发布或者处理一个消息或者操作一个Runnable,通过Handler发布消息,消息将只会发送到与它关联的消息队列,然也只能处理该消息队列中的消息.
Looper:是Handler和消息队列之间通讯桥梁,程序组件首先通过Handler把消息传递给Looper,Looper把消息放入队列。Looper也把消息队列里的消息广播给所有的Handler:Handler接受到消息后调用handleMessage进行处理.
Message:消息的类型,在Handler类中的handleMessage方法中得到单个的消息进行处理,在单线程模型下,为了线程通信问题,Android设计了一个Message Queue(消息队列),线程间可以通过该Message Queue并结合Handler和Looper组件进行信息交换。下面将对它们进行分别介绍:
1. Message
Message消息,理解为线程间交流的信息,处理数据后台线程需要更新UI,则发送Message内含一些数据给UI线程。
2. Handler
Handler处理者,是Message的主要处理者,负责Message的发送,Message内容的执行处理。后台线程就是通过传进来的 Handler对象引用来sendMessage(Message)。而使用Handler,需要implement 该类的 handleMessage(Message)方法,它是处理这些Message的操作内容,例如Update UI。通常需要子类化Handler来实现handleMessage方法。
3. Message Queue
Message Queue消息队列,用来存放通过Handler发布的消息,按照先进先出执行。
每个message queue都会有一个对应的Handler。Handler会向message queue通过两种方法发送消息:sendMessage或post。这两种消息都会插在message queue队尾并按先进先出执行。但通过这两种方法发送的消息执行的方式略有不同:通过sendMessage发送的是一个message对象,会被 Handler的handleMessage()函数处理;而通过post方法发送的是一个runnable对象,则会自己执行。
4. Looper
Looper是每条线程里的Message Queue的管家。Android没有Global的Message Queue,而Android会自动替主线程(UI线程)建立Message Queue,但在子线程里并没有建立Message Queue。所以调用Looper.getMainLooper()得到的主线程的Looper不为NULL,但调用Looper.myLooper() 得到当前线程的Looper就有可能为NULL。对于子线程使用Looper,API Doc提供了正确的使用方法:这个Message机制的大概流程:
1. 在Looper.loop()方法运行开始后,循环地按照接收顺序取出Message Queue里面的非NULL的Message。
2. 一开始Message Queue里面的Message都是NULL的。当Handler.sendMessage(Message)到Message Queue,该函数里面设置了那个Message对象的target属性是当前的Handler对象。随后Looper取出了那个Message,则调用该Message的target指向的Hander的dispatchMessage函数对Message进行处理。在dispatchMessage方法里,如何处理Message则由用户指定,三个判断,优先级从高到低:
1) Message里面的Callback,一个实现了Runnable接口的对象,其中run函数做处理工作;
2) Handler里面的mCallback指向的一个实现了Callback接口的对象,由其handleMessage进行处理;
3) 处理消息Handler对象对应的类继承并实现了其中handleMessage函数,通过这个实现的handleMessage函数处理消息。
由此可见,我们实现的handleMessage方法是优先级最低的!
3. Handler处理完该Message (update UI) 后,Looper则设置该Message为NULL,以便回收!
在网上有很多文章讲述主线程和其他子线程如何交互,传送信息,最终谁来执行处理信息之类的,个人理解是最简单的方法——判断Handler对象里面的Looper对象是属于哪条线程的,则由该线程来执行!
1. 当Handler对象的构造函数的参数为空,则为当前所在线程的Looper;
2. Looper.getMainLooper()得到的是主线程的Looper对象,Looper.myLooper()得到的是当前线程的Looper对象。
为什么要使用Handler?
有时候需要在子线程中进行耗时的I/O操作,可能是读取文件或者访问网络等,当耗时操作完成以后可能需要在UI上做一些改变,由于Adnroid开发规范的限制,我们并不能在子线程中访问UI控件,否则会出发程序异常,这个时候通过Handler就可以将更新UI的操作切换到主线程中执行。因此,本质上来说,Handler并不是专门用于更新UI的,只是被常常用来更新UI。
MessageQueue虽然叫做消息队列,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表的。
MessageQueue只是一个消息的存储单元,它并不能去处理消息,Looper会以无限循环的形式去查询是否有新消息,如果有的话就处理消息,否则一直等待。
线程默认是没有Looper的,如果需要使用Handler就必须为线程创建Looper。我们经常提到的主线程,也叫做UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Looper的原因。
系统之所以提供Handler,主要原因就是为了解决在子线程中无法访问UI的矛盾。
系统为什么不允许在子线程中访问UI呢?
因为Andorid的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。
为什么不对UI控件的访问加上锁机制?
缺点有两个:首先加上锁机制会让UI访问逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。所以最高效的方法就是采用单线程模型来处理UI操作。
Handler创建完毕,这个时候其内部的Looper以及MessageQueue就可以和Handler一起工作了,然后通过Handler的post方法将一个Runable投递到Handler内部的Looper中去处理,也可以通过Handler的send方法来发送一个消息,这个消息同样会在Looper中去处理。其实post方法最终也是通过send方法处理的。
Send方法的工作过程:
Handler的send方法被调用是,它会调用MessageQueue的enqueueMessage方法将这个消息放入消息队列中,然后Looper发现有新的消息到来是,就会出来这个消息,最终消息中的Runnable或者Handler的handleMessage方法会被调用。
Thread是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据。
当某些数据以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。
对于Handler来说,它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同线程具有不同的Looper、
线程是一次性消费品,如果多次创建和销毁线程是很耗费资源的,所以就构建了一个循环线程Looper Thread,当有耗时任务投放到该循环线程中的时候,线程执行耗时任务,当执行完毕了,循环线程处于等待状态,等待下一个耗时任务被投放进来。
1).HandlerThread适用于构建循环线程
2).在创建Handler作为HandlerThread线程消息执行者的时候必须调用start方法之后,因为创建Handler需要的Looper参数是从HandlerThread类中获得的,而Looper对象的赋值又是在HandlerThread的run方法中创建。
1.Cancelling a Task
我们可以在任何时刻来取消我们的异步任务的执行,通过调用 cancel(boolean)方法,调用完这个方法后系统会随后调用 isCancelled() 方法并且返回true。如果调用了这个方法,那么在 doInBackgroud() 方法执行完之后,就不会调用 onPostExecute() 方法了,取而代之的是调用 onCancelled() 方法。为了确保Task已经被取消了,我们需要经常调用 isCancelled() 方法来判断,如果有必要的话。
2.在使用AsyncTask做异步任务的时候必须要遵循的原则:
AsyncTask类必须在UI Thread当中加载,在Android Jelly_Bean版本后这些都是自动完成的
AsyncTask的对象必须在UI Thread当中实例化
execute方法必须在UI Thread当中调用
不要手动的去调用AsyncTask的onPreExecute, doInBackground, publishProgress, onProgressUpdate, onPostExecute方法,这些都是由Android系统自动调用的
AsyncTask任务只能被执行一次
参考博客:
http://www.cnblogs.com/codingmyworld/archive/2011/09/14/2174255.html
http://www.androidchina.net/313.html
ListView
public View getView( int position, View convertView, ViewGroup parent) {
View view = convertView;
ViewHolder holder;
if (view == null) {
LayoutInflater inflater = (LayoutInflater) mContext
.getSystemService(Context. LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout. list_item, null);
holder = new ViewH older();
// 每项的视图布局是一样的
holder. image = (ImageView) view.findViewById(R.id.image );
holder. tv_title = (TextView) view.findViewById(R.id.tv_title );
holder. tv_info = (TextView) view.findViewById(R.id.tv_info );
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
// 每项的数据都不一样
holder. image.setImageDrawable( dataList.get(position). image);
holder. tv_title.setText( dataList.get(position). title);
holder. tv_info.setText( dataList.get(position). info);
return view;
}
private static class ViewHolder {
public ImageView image;
public TextView tv_title;
public TextView tv_info;
}
首先我们给出一个没有任何优化的
Listview
的
Adapter
类,我们这里都继承自
BaseAdapter
,这里我们使用一个包含
100
个字符串的
List
集合来作为
ListView
的项目所要显示的内容,每一个条目都是一个自定义的组件,这个组件中只包含一个
textview
:
package com.alexchen.listviewoptimize;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends Activity {
private ListView lv_demo;
private List<String> list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv_demo = (ListView) findViewById(R.id.lv_demo);
//list为要加载的条目文本的集合,这里总共是100条
list = new ArrayList<String>();
for (int i = 0; i < 100; i++) {
list.add("条目" + i);
}
lv_demo.setAdapter(new MyAdapter());
}
private class MyAdapter extends BaseAdapter {
@Override
public int getCount() {
return list.size();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//listview_item里只有一个textview
View view = View.inflate(MainActivity.this, R.layout.listview_item, null);
//使用每一次都findviewById的方法来获得listview_item内部的组件
TextView tv_item = (TextView) view.findViewById(R.id.tv_item);
tv_item.setText(list.get(position));
return view;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
}
}
优化一:
也是最普通的优化,就在MyAdapter类中的getView方法中,我们注意到,上面的写法每次需要一个View对象时,都是去重新inflate一个View出来返回去,没有实现View对象的复用,而实际上对于ListView而言,只需要保留能够显示的最大个数的view即可,其他新的view可以通过复用的方式使用消失的条目的view,而getView方法里也提供了一个参数:convertView,这个就代表着可以复用的view对象,当然这个对象也可能为空,当它为空的时候,表示该条目view第一次创建,所以我们需要inflate一个view出来,所以在这里,我们使用下面这种方式来重写getView方法:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
// 判断convertView的状态,来达到复用效果
if (null == convertView) {
//如果convertView为空,则表示第一次显示该条目,需要创建一个view
view = View.inflate(MainActivity.this, R.layout.listview_item, null);
} else {
//否则表示可以复用convertView
view = convertView;
}
// listview_item里只有一个textview
TextView tv_item = (TextView) view.findViewById(R.id.tv_item);
tv_item.setText(list.get(position));
return view;
}
优化二:
上面是对 view 对象的复用做的优化,我们经过上面的优化之后,我们不需要每一个 view 都重新生成了。下面我们来解决下一个每一次都需要做的工作,那就是 view 中组件的查找:TextView tv_item = (TextView) view.findViewById(R.id.tv_item);
实际上,
findViewById
是到
xml
文件中去查找对应的
id
,可以想象如果组件多的话也是挺费事的,如果我们可以让
view
内的组件也随着
view
的复用而复用,那该是多美好的一件事啊。。实际上谷歌也推荐了一种优化方法来做应对,那就是重新建一个内部静态类,里面的成员变量跟
view
中所包含的组件个数类型相同,我们这里的
view
只包含了一个
TextView
,所以我们的这个静态类如下:
private static class ViewHolder {
private TextView tvHolder;
}
那么这个viewHolder类我们要如何使用才可以达到复用效果呢?基本思路就是在convertView为null的时候,我们不仅重新inflate出来一个view,并且还需要进行findviewbyId的查找工作,但是同时我们还需要获取一个ViewHolder类的对象,并将findviewById的结果赋值给ViewHolder中对应的成员变量。最后将holder对象与该view对象“绑”在一块。
当convertView不为null时,我们让view=converView,同时取出这个view对应的holder对象,就获得了这个view对象中的TextView组件,它就是holder中的成员变量,这样在复用的时候,我们就不需要再去findViewById了,只需要在最开始的时候进行数次查找工作就可以了。这里的关键在于如何将view与holder对象进行绑定,那么就需要用到两个方法:setTag和getTag方法了:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder holder;
// 判断convertView的状态,来达到复用效果
if (null == convertView) {
// 如果convertView为空,则表示第一次显示该条目,需要创建一个view
view = View.inflate(MainActivity.this, R.layout.listview_item, null);
//新建一个viewholder对象
holder = new ViewHolder();
//将findviewbyID的结果赋值给holder对应的成员变量
holder.tvHolder = (TextView) view.findViewById(R.id.tv_item);
// 将holder与view进行绑定
view.setTag(holder);
} else {
// 否则表示可以复用convertView
view = convertView;
holder = (ViewHolder) view.getTag();
}
// 直接操作holder中的成员变量即可,不需要每次都findViewById
holder.tvHolder.setText(list.get(position));
return view;
}
经过上面的做法,可能大家感觉不太到优化的效果,根据Google的文档,实际优化效果在百分之5左右。
优化三:
上面的两个例子中ListView都是显示的本地的List集合中的内容,List的长度也只有100个,我们可以毫不费力一次性加载完这100个数据;但是实际应用中,我们往往会需要使用Listview来显示网络上的内容,比如说我们拿使用ListView显示新闻为例:
其一:假如网络情况很好,我们使用的手机也许能够一下子加载完所有新闻数据,然后显示在ListView中,用户可能感觉还好,假如说在网络不太顺畅的情况下,用户加载完所有网络的数据,可能这个list是1000条新闻,那么用户可能需要面对一个空白的Activity好几分钟,这个显然是不合适的
其二:我们知道Android虚拟机给每个应用分配的运行时内存是一定的,一般性能不太好的机器只有16M,好一点的可能也就是64M的样子,假如说我们现在要浏览的新闻总数为一万条,即便是网络很好的情况下,我们可以很快的加载完毕,但是多数情况下也会出现内存溢出从而导致应用崩溃的情况。
那么为了解决上面的两个问题,我们需要进行分批加载,比如说1000条新闻的List集合,我们一次加载20条,等到用户翻页到底部的时候,我们再添加下面的20条到List中,再使用Adapter刷新ListView,这样用户一次只需要等待20条数据的传输时间,不需要一次等待好几分钟把数据都加载完再在ListView上显示。其次这样也可以缓解很多条新闻一次加载进行产生OOM应用崩溃的情况。
实际上,分批加载也不能完全解决问题,因为虽然我们在分批中一次只增加20条数据到List集合中,然后再刷新到ListView中去,假如有10万条数据,如果我们顺利读到最后这个List集合中还是会累积海量条数的数据,还是可能会造成OOM的情况,这时候我们就需要用到分页,比如说我们将这10万条数据分为1000页,每一页100条数据,每一页加载时都覆盖掉上一页中List集合中的内容,然后每一页内再使用分批加载,这样用户的体验就会相对好一些。
Android的线程和线程池
AsyncTask来说,它的底层用到了线程池,对于IntentService和HandlerThread来说,它的底层则直接就是线程。
AsyncTask封装了线程池和Handler,它主要是为了方便开发者在子线程中更新UI。
HandelrThread是一种具有消息循环的线程,在它的内部可以使用Handler。
IntentService是一个服务,系统对其进行了封装使它可以更方便的执行后台任务,IntentService内部采用了HandlerThread的来执行任务,当任务执行完毕后IntentService会自动退出。
IntentService的优点:从任务执行的角度看,IntentService的作用很像一个后台线程,但是IntentService是一种服务,他不容易被系统杀死从未可以尽量保证任务的进行,而如果一个后台线程,由于这个时候进程中没有活动的四大组件,那么这个进程的优先级就会变得很低,很容易被系统杀死。
主线程(main Thread)是指进程所拥有的线程。
主线程的作用是运行四大组件以及处理它们和用户的交互,而子线程的作用则是执行耗时任务,比如网络请求、I/O操作等。
AsyncTask
一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报错。
HandlerThread继承了Thread,它是一种可以使用Handler的Thread。在run方法中通过Looper.prepaer()来创建消息队列,并通过Looper.loop()来开启消息循环,这样在实际的使用中就允许在HandlerThread中创建Handler了。
普通Thread要用于在run方法中执行一个耗时任务,而HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式来通知HandlerThread执行一个具体的任务。
由于HandlerThread的run方法是一个无限循环,因此当明确不需要再使用HandlerThread时,可以通过它的quit和quitSafely方法来终止线程的执行。
IntentService是一种特殊的Service,它继承了Service并且他是一个抽象类,因此必须创建它的子类才能使用IntentService。由于IntentService是服务的原因,它的优先级别单纯的线程高很多,所以IntentService比较适合执行一些高优先级的后台任务,不易被杀死。
动画
Android的动画可以分为三种:View动画、帧动画、属性动画。
View动画包括:旋转、平移、缩放、透明度。分别对应的是RotationAnimation、TranslateAnimation、ScaleAnimation、AlphaAnimation。
<set>标签表示动画集合,对应AnimationSet类,它可以包含若干个动画,并且它的内部也是可以嵌套其他动画集合的。
自定义动画:说它简单,是因为派生一种新动画只需要继承Animation这个抽象类,然后重写它的initialize和applyTransformation方法,在initialize方法中做一些初始化工作,在applyTransformation中进行相应的矩阵变换即可,很多时候需要采用Camera来简化矩阵变换的过程。说它复杂,是因为自定义View动画的过程主要是矩阵变换的过程,而矩阵变换是数学上的概念。
View动画可以在ViewGroup中控制子元素的出场效果,在Activity中实现不同Activity之间的切换效果。
LayoutAnimation作用于ViewGroup,为ViewGroup指定一个动画,这样当它的子元素出场时都会具有同样的动画效果。这种效果常常被用在ListView上,例如每个item都以一定的动画形式出现。
Activity有默认的切换效果,但是可以自定义,主要用到了overridePendingTransition(int enterAnim, int exitAnim ); 这个方法,这个方法必须在startActivity(Intent)或者finish()之后调用才能生效。
当系统配置发生改变后,Activity会被销毁,其onPause、onStop、onDestory均会被调用,同时由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity的状态。这个方法的调用时机是在onStop之前,这个方法只出现在Activity被异常终止的情况下,正常情况下系统不会回调这个方法的。当Activity被重新创建以后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象作为参数传递给onRestoreInstanceState和onCreate方法,因此,我们可以通过onRestoreInstanceState和onCreate方法来判断Activity是否被重建了,如果被重建了,那么我们就可以取出之前保存的数据并恢复,从时序上来说,onRestoreInstanceState的调用实际在onStart之后。
关于保存和恢复View层次结构,系统的工作流程就是这样的:首先Activity被意外终止时,Activity会调用onSaveInstanceState去保存数据,然后Activity委托Window去保存数据,接着Window在委托它上面的顶级容器去保存数据,顶层容器是一个ViewGroup,一般来说他可能是DecorView。最后顶层容器再去一一通知它的子元素来保存数据,这样整个数据保存过程就完成了。可以发现,这是一种典型的委托思想,上层委托下层、父容器委托子元素去处理一件事情。这种思想还用在了例如View的绘制过程、事件分发等。
接受的未知可以选择onRestoreInstanceState或者onCreate。二者的区别是:onRestoreInstanceState一旦被调用,其参数Bundle saveInstenceState一定是有值的,我们不用额外判断是否为空;但是onCreate就不行,onCreate如果正常启动的话,其参数Bundle saveInstanceState为null,所以必须额外判断。官方建议onRestoreInstanceState去恢复数据。
如果进程中没有四大组件在执行,那么这个进程将很快被系统杀死。
View
自定义View
http://blog.csdn.net/u012975370/article/details/50033553#t4
DecorView为整个Window界面的最顶层View。
DecorView只有一个子元素为LinearLayout,代表整个window界面,包括通知栏、标题栏、内容显示栏三块区域。
一个视图实际上是具有两对宽高的,一对是测量宽高,一对是绘制宽高。
绘制VIew本身的内容,通过调用View.onDraw(canvas)函数实现
绘制自己的孩子通过dispatchDraw(canvas)实现
View组件的绘制会调用draw(Canvas canvas)方法,draw过程中主要是先画Drawable背景,对 drawable调用setBounds()然后是draw(Canvas c)方法.有点注意的是背景drawable的实际大小会影响view组件的大小,drawable的实际大小通过getIntrinsicWidth()和getIntrinsicHeight()获取,当背景比较大时view组件大小等于背景drawable的大小。画完背景后,draw过程会调用onDraw(Canvas canvas)方法,然后就是dispatchDraw(Canvas canvas)方法, dispatchDraw()主要是分发给子组件进行绘制,我们通常定制组件的时候重写的是onDraw()方法。值得注意的是ViewGroup容器组件的绘制,当它没有背景时直接调用的是dispatchDraw()方法, 而绕过了draw()方法,当它有背景的时候就调用draw()方法,而draw()方法里包含了dispatchDraw()方法的调用。因此要在ViewGroup上绘制东西的时候往往重写的是dispatchDraw()方法而不是onDraw()方法,或者自定制一个Drawable,重写它的draw(Canvas c)和 getIntrinsicWidth(),getIntrinsicHeight()方法,然后设为背景。
EXACYTLY 一般是设置了明确的值或者是MATH_PARENT
AT_MOST 表示子布局限制在一个最大值内,一般为WARP_CONTENT
UNSPECIFIED 表示子布局想要多大就多大,很少使用
- 每个Activity,都至少有一个Window,这个Window实际类型为PhoneWindow,当Activity中有子窗口,比如Dialog的时候,就会出现多个Window。Activity的Window是我们控制的,状态栏和导航栏的Window由系统控制。
- 在DecorView的里面,一定有一个id为content的FraneLayout的布局容器,咱们自己定义的xml布局都放在这里面。
- Activity的Window里面有一个DecorView,它使继承自FrameLayout的一个自定义控件,作为整个View层的容器,及View树的根节点。
- Window是虚拟的概念,DecorView才是看得见,摸得着的东西,Activity.setContentView()实际调用的是PhoneWindow.setContentView(),在这里面实现了DecorView的初始化和id为content的FraneLayout的布局容器的初始化,并且会根据主题等配置,选择不同的xml文件。而且在Activity.setContentView()之后,Window的一些特征位将被锁定。
- Activity.findViewById()实际上调用的是DecorView的findviewById(),这个方法在View中定义,但是是final的,实际起作用的是在ViewGroup中被重写的findViewTraversal()方法。
- Activity的mWindow成员变量是在attach()的时候被初始化的,attach()是Activity被通过反射手段实例化之后调用的第一个方法,在这之后生命周期方法才会依次调用
- 在onResume()刚执行之后,界面还是不可见的,只有执行完Activity.makeVisible(),DecorView才对用户可见
- ViewManager这个接口里面就三个接口,添加、移除和更新,实现这个接口的有WindowManager和ViewGroup,但是他们两个面向的对象是不一样的,WindowManager实现的是对Window的操作,而ViewGroup则是对View的增、删、更新操作。
- WindowManagerImpl是WindowManager的实现类,但是他就是一个代理类,代理的是WindowManagerGlobal,WindowManagerGlobal一个App里面就有一个,因为它是单例的,它里面管理了App中所有打开的DecorView,ContentView和PhoneWindow的布局参数WindowManager.LayoutParams,而且WindowManagerGlobal这个类是和WMS通信用的,是通过IWindowSession对象完成这个工作的,而IWindowSession一个App只有一个,但是每个ViewRootImpl都持有对IWindowSession的引用,所以ViewRootImpl可以和WMS喊话,但是WMS怎么和ViewRootImpl喊话呢?是通过ViewRootImpl::W这个内部类实现的,而且源码中很多地方采用了这种将接口隐藏为内部类的方式,这样可以实现六大设计原则之一——接口最小原则,这样ViewRootImpl和WMS就互相持有对方的代理,就可以互相交流了
- ViewRootImpl这个类每个Activity都有一个,它负责和WMS通信,同时相应WMS的指挥,还负责View界面的测量、布局和绘制工作,所以当你调用View.invalidate()和View.requestLayout()的时候,都会把事件传递到ViewRootImpl,然后ViewRootImpl计算出需要重绘的区域,告诉WMS,WMS再通知其他服务完成绘制和动画等效果,当然,这是后话,咱们以后再说。
- Window分为三种,子窗口,应用窗口和系统窗口,子窗口必须依附于一个上下文,就是Activity,因为它需要Activity的appToken,子窗口和Activity的WindowManager是一个的,都是根据appToken获取的,描述一个Window属于哪种类型,是根据LayoutParam.type决定的,不同类型有不同的取值范围,系统类的的Window需要特殊权限,当然Toast比较特殊,不需要权限
- PopupWindow使用的时候,如果想触发按键和触摸事件,需要添加一个背景,代码中会根据是否设置背景进行不同的逻辑判断
- Dialog在Activity不可见的时候,要主动dismiss掉,否则会因为appToken为空crash
- Toast属于系统窗口,由系统服务NotificationManagerService统一调度,NotificationManagerService中维持着一个集合ArrayList,最多存放50个Toast,但是NotificationManagerService只负责管理Toast,具体的显示工作由Toast::TN来实现
- View中的performClick()的出发条件:
由源码可以看出,只要是使用了view.setOnClickListener()方法设置监听器,就会自动触发view.performClick()。需要注意的是,如果同时使用了view.setOnTouchListener()方法,则有可能存在拦截view.performClick()的响应事件,因为当view.OnTouchEvent()在event.getAction() == MotionEvent.ACTION_DOWN时返回false,系统会认为view不需要处理Touch事件,则后续的Touch事件(move、up、click)就不会被传进来,所以也不会触发view.performClick(),而view.setOnTouchListener()相当于是重写了view.OnTouchEvent(),所以在写view的TouchListener处理时,需要留意view是否存在点击事件监听,如果有,则在适当的位置使用view.performClick()触发点击事件。public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false; }
子线程更新ui的三种方式
1.通用的就是handler
2.通过runOnUiThread方法
方法内部实现如下: public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
使用方法如下:
new Thread(new Runnable() {
@Override
public void run() {
// 此处执行耗时操作,结束后,执行runOnUiThread将线程切换到主线程去更新ui
runOnUiThread(new Runnable() {
@Override
public void run() {
// 更新ui操作
}
});
}
}).start();
如果在非上下文中环境中,可以通过一下方法来实现:
final Activity activity = (Activity) mTextView.getContext();
new Thread(new Runnable() {
@Override
public void run() {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
}
}).start();
在其他地方,则需要传递Activity对象,因为runOnUiThread方法是Activity的方法。
3.通过view.post(runnable)来实现
mTextView.post(new Runnable() {
@Override
public void run() {
}
});
以上不管哪种方法,原理都是将更新ui的消息从子线程中传递到主线程中,因为,更新view只能在主线程,这个是无法改变的。
4.子线程直接更新ui的极端情况
android的UI访问是没有加锁的,这样会导致多个线程访问ui会不安全,那么既然这样,为什么不加锁呢,因为加上锁机制会让ui访问的逻辑变得复杂,其次锁机制会降低ui访问的效率,锁机制会阻塞某些线程的执行。所以android规定,只能在主线程更新ui。
子线程真的无法更新ui吗,答案是no,因为有些极端情况,还是可以更新ui的。
我们在onCreate中直接执行以下代码:
Log.i("niejianjian", " -> onCreate -> " + Thread.currentThread().getId());
new Thread(new Runnable() {
@Override
public void run() {
Log.i("niejianjian", " -> Thread -> " + Thread.currentThread().getId());
mTextView.setText("fjsdlj l");
}
}).start();
发现更新ui是可以成功的,我们可以打印当前的线程,发现确实是在子线程中,因为主线程的id是1。但是我们在将Thread睡眠200ms,
Log.i("niejianjian", " -> onCreate -> " + Thread.currentThread().getId());
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("niejianjian", " -> Thread -> " + Thread.currentThread().getId());
mTextView.setText("fjsdlj l");
}
}).start();
结果就报错了,log如下:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6257)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:868)
ViewRootImpl是ViewRoot的实现类,异常主要是checkThread抛出的,因为里面对线程做了判断:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
因为view的绘制过程中,都会执行ViewRootImpl的checkThread来检查是否是主线程更新,所以onCreate中的子线程可以更新ui,主要是因为ViewRootImpl还没有创建,所以无法进行检查。
ViewRootImpl的创建在onResume方法回调之后,而我们一开篇是在onCreate方法中创建了子线程并访问UI,在那个时刻,ViewRootImpl是没有创建的,无法检测当前线程是否是UI线程,所以程序没有崩溃一样能跑起来,而之后修改了程序,让线程休眠了200毫秒后,程序就崩了。很明显200毫秒后ViewRootImpl已经创建了,可以执行checkThread方法检查当前线程。
参考: http://www.cnblogs.com/xuyinhuan/p/5930287.html
Bitmap的加载和Cache
Android对单个应用施加了内存限制,16MB,这导致在加载Bitmap的时候很容易出现内存溢出,异常信息为:
java.lang.OutofMemoryError:bitmap size exceeds VM budget
比较常使用的缓存策略为LruCache和DiskLruCache。其中LruCache常备用做内存缓存,额DiskLruCache常被用作存储缓存。
Lru是Least Recnetly Used的缩写,也就是最近最少使用算法,其核心思想为:当缓存快满时,会淘汰掉近期最少使用的缓存目标。
如何加载一个Bitmap?
BitmapFactory提供了四类方法:decodeFile、decodeResource、decodeSream、decodeByteArray,分别支持从文件系统、资源、输入流以及字节数组中加载出一个Bitmap,其中decodeFile和decodeResource又简介调用了decodeStream方法。
如何高效的加载Bitmap?
采用BitmapFactory.Options来加载所需尺寸的图片。一般情况下ImageView都没有原始图片那么大,如果将图片整个加载,在设给imageView的话,显然没必要,因为并没有办法显示原始的图片。通过BitmapFactory.Options就可以按一定的采样率来加载缩小后的图片了,这样就可以降低内存占用从而在一定程度上避免了OOM。
通过BitmapFactory.Options来缩放图片,主要用到了参数inSampleSize参数,即采样率。
当inSampleSize为1时,采样后的图片大小为原始大小;数值必须是大于一的整数,当小于1时,相当于是1。比如insample是2,那么采样后的图片的宽高均为原图的1/2,而像素是原图的1/4,所以内存大小也为原图的1/4。inSample的取值应该总是2的指数,如果不是2的指数,系统会自动向下取整(并不完全成立)。
如何获取采样率?
1).将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片
2).从BitmapFactory.Options中取出图片的原始宽高信息,它们对应于outWidth和outHeight参数
3).根据采样率的规则并结合目标View的所需大小,计算出采样率inSampleSize
4).将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片。
优化列表的卡顿现象:
1).不要在getView中执行耗时操作。必须通过异步操作来实现,比如ImageLoader
2).控制异步任务的刷新频率
3).开启硬件加速。设置android:hardwareAccelerated=”true”即可
针对控制刷新频率的详解:(为什么要在列表滑动的时候停止加载图片)
在getView方法中会通过ImageLoader的bindBitmap方法来异步加载图片,但是如果用户可以频繁的上下滑动,这个会在一瞬间产生上百个异步任务,这些异步任务会造成线程池的拥堵并随即带来大量的Ui更新操作,这是没有意义的。由于一瞬间存在了大量的UI更新操作,这些UI操作是运行在主线程,所以造成了一定程度的卡顿。
具体实现时,可以给ListView和GridView设置setScrollListener,并在OnScrollListener的onScrollStateChanged方法中判断是否滑动,如果是就停止加载图片。
JNI
Java JNI的本意是Java Native Interface,它是为了方便Java调用C、C++等本地代码所封装的一层接口。
NDK是Android所提哦功能的一个工具集合,通过NDK可以在Android中更方便地通过JNI来访问本地代码,比如C或者C++。
NDK的好处:
1).提高代码的安全性。由于so库反编译比较困难,因此NDK提高了Android程序的安全性。
2).可以很方便地使用目前已有的C/C++开源库。
3).便于平台间的移植。通过C/C++实现的动态库可以很方便的在其他平台上使用。
4).提高程序在某些特定情形下的执行效率,但是并不能明显提升Android程序的性能。
JNI的开发流程
1.在java中声明native方法
2.编译java源文件得到class文件,然后通过javah命令导出JNI的头文件
3.实现JNI方法
4.编译so库并在java中调用
Toast
首先Toast也属于窗口系统,但是并不是属于App的,是由系统同一控制的。
关于这一块不想说太多,具体实现机制请参考后面的文章。
为了看下面的内容,你需要知道以下几件事情:
- Toast的显示是由系统Toast服务控制的,与系统之间的通信方式是Binder
- 整个Toast系统会维持最多50个Toast的队列,依次显示
- 负责显示工作的是Toast的内部类TN,它负责最终的显示与隐藏操作
- 负责给系统Toast服务发送内容的是INotificationManager的实现类,它负责在Toast.show()里面把TN对象传递给系统消息服务,service.enqueueToast(pkg, tn, mDuration);这样Toast服务就持有客户端的代理,可以通过TN来控制每个Toast的显示与隐藏。
HTTP
1.HTTP协议可以通过传输层的TCP协议在额客户端和服务器之间传输数据。
2.HTTP协议采用了请求/响应的工作方式。基于HTTP1.0协议的客户端每次向服务器发出请求后,服务器就会向客户端返回响应消息(包括请求是否正确以及锁请求的数据),在确认客户端已经收到响应消息后,服务器就会关闭网络连接,并不保存任何历史信息和状态信息。HTTP协议也被认为是无状态协议。
3.通讯过程:
通过客户端Socket建立连接;向服务器发送请求;服务器回送响应;服务器关闭连接。
4.HTTP1.1协议增加了持久连接支持。也就是服务器将关闭客户端连接的主动权还给了客户端,客户端向服务器发送一个请求并接受到一个响应后,只要不调用Socket类的close方法关闭网络,就可以继续想服务器发送HTTP请求。
5.Android6.0(API23)删除了http.client的一些类,导致HttpPost类完全找不到了。
Android性能优化
1.布局优化
减少布局文件的层级,这样就减少了Android绘制工作。
如果可以使用LinearLayout也可以使用RelativeLayout时,就选择LinearLayout。这是因为RelativeLayout的功能比较复杂,它的布局过程需要花费更多的CPU时间。LinearLayout和FrameLayout一样是一种简单高校的ViewGroup。如果使用LinearLayout布局需要嵌套才能完成,此时建议使用RelativeLayout。
在不影响层级深度的情况下,使用LinearLayout而不是Relativelayout。因为Relative会让子view执行两次onMeasure,LinearLayout在有weight的时候,才会让子view执行两次onMeasure。Measure耗时越长,绘制效率越低。
布局优化另一种手段是采用<include>标签、<merge>标签和ViewStub。<include>标签只要用于布局重用,<merge>标签一般和<include>配合使用,它可以降低减少布局的层级,而ViewStub则提供了按需加载的功能,当需要时才会将ViewStub中布局加载到内存,这就提高了程序的初始化效率。
1).<include>标签只支持以android:layout_开头的属性,比如android:layout_width、android:layout_height,其他属性不支持,比如android:background。
2).android:id是特例,如果<include>和被包含的布局文件的根源素都指定了id属性,那么以<include>指定的id属性为准。
3).如果<include>标签指定了android:layout_*这种属性,那么要求android:layout_width、android:layout_height必须存在,否则其他android:layout_*形式的属性都无效。
4).<merge>标签一般和<include>标签一起使用从而减少布局的层级。如果父布局是LinearLayout,而且orientation=”vertical”,<include>标签中包含的布局中包含两个按钮,也是LinearLayout布局,方向也是orientaion=”vertical”,此时,再被包含的布局中再写LinearLayout就是多要点,通过merge标签就额可以去掉多余的那一层LinearLayout。
5).ViewStub继承了View,它非常轻量级且宽高都是0 。因此它本身不参与任何的布局和绘制过程。ViewStub的意义在于按需加载所需的布局文件,在实际开发中,有很多布局文件在正常情况下不会显示,比如网络异常时的界面,这个时候就没有必要在整个界面初始化的时候将它加载进来,在需要的时候在加载。
<ViewStub
android:id="@+id/stub_import"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:inflatedId="@+id/panel_import"
android:layout="@layout/activity_test" />
其中stub_import是ViewStub的id,而panel_import是layout/activity_test这个布局的根元素的id。在需要加载ViewStub中的布局的时候,可以按照如下两种方式加载:
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
或者
View importpPanel = ((ViewStub)findViewById(R.id.stub_import)).inflate();
当ViewStub
通过
setVisibility
或者
inflate
方法加载后,
ViewStub
就会被它内部的布局替换掉,这个时候
ViewStub
就不再是整个布局结构中的一部分了。
ViewStub
不支持
<merge>
。
干货连接:
http://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ==&mid=2650821434&idx=1&sn=dd404347eb5f953f7a5737a31ae864e8&chksm=80b787a4b7c00eb297a81316483f
2.绘制化
绘制优化是指View的onDraw方法要避免执行大量的操作,主要体现在两方面。
1).onDraw中不要创建新的布局对象,这是因为onDraw方法可能会被频繁调用,这样就会在一瞬间产生大量的临时对象,这不仅占用了过多的内存而且还会导致系统更加频繁的gc,降低了程序的执行效率。
2).另一方面,onDraw方法中不要做耗时的任务,也不能执行成千上万次的循环操作,尽管每次循环都很轻量级,但是大量的循环仍然十分抢占CPU的时间片,这会造成View的绘制过程不流畅。按照Google官方给出的性能优化典范中的标准,View的绘制帧率保证60fps是最佳的,这就是要求每帧的绘制时间不超过16ms(1000/16),虽然程序很难保证这个时间,但是尽量降低onDraw方法复杂度总是切实有效的。
3.内存泄漏优化
内存泄漏的优化分为两方面,一方面是开发过程中避免写出有内存泄漏的代码,另一方面是通过一些分析工具比如MAT来找出潜在的内存泄漏进行解决。
1).静态变量导致的内存泄漏
这面的代码将导致内存泄漏,Activity不能正常销毁。
public class MainActivity1 extends Activity {
private static final String TAG = "MainActivity1";
private static Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
}
}
上面的代码改造下,
sView
是一个静态变量,它内部持有了当前
Activity
,所以
Activity
仍然无法释放。
public class MainActivity1 extends Activity {
private static final String TAG = "MainActivity1";
private static View mView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mView = new View(this);
}
}
2).单例模式导致的内存泄漏
首先提供一个单例模式TestManger,TestManager可以接受外部的注册并将外部的监听器存储起来。
public class TestManager {
private List<onDataArrivedListener> mOnDataArrivedListeners = new ArrayList<onDataArrivedListener>();
private static class SingletonHolder {
public static final TestManager INSTANCE = new TestManager();
}
private TestManager() {
}
public static TestManager getInstance() {
return SingletonHolder.INSTANCE;
}
public synchronized void registerListener(onDataArrivedListener listener) {
if (!mOnDataArrivedListeners.contains(listener)) {
mOnDataArrivedListeners.add(listener);
}
}
public synchronized void unregisterListener(onDataArrivedListener listener) {
mOnDataArrivedListeners.remove(listener);
}
public interface onDataArrivedListener {
public void onDataArrived(Object data);
}
}
接着再让
Activity
实现
OnDataArrivedListener
接口并向
TestManager
注册监听,如下所示,下面的代码由于缺少解注册的操作所以会引起内存泄漏,泄漏的原因是
Activity
的对象被单例模式的
TestManager
所持有的,而单例模式的特点就是其生命周期和
Application
保持一致,因此
Activity
对象无法被及时释放。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TestManager.getInstance().registerListener(this);
}
3).属性动画导致的内存泄漏
从Android3.0开始,Google提供了属性动画,属性动画中有一类无限循环的动画,如果在Activity中播放此类动画且没有在onDestory中停止动画,那么动画会一直被播放下去,尽管已经无法在界面上看到动画效果了,并且这个时候Activity的View动画会被动画持有,而View又持有了Activity,最终Activity无法释放。下面的动画是无限动画,会泄漏当前Activity,解决方法就是在Activity的onDestory中调用animator.cancel()来停止动画。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button mButton = (Button) findViewById(R.id.button1);
ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "rotation",
0, 360).setDuration(2000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.start();
}
1.你可以提醒系统定期回收,system.gc(),对于那些你不用的东西,你可以设置为null或者0,系统就会回收,还有其他的东西。
2.可以自己手动关闭,比如bitmap,就有bitmap.recycle();方法,还有cursor.close();用完记得关闭。
3.如果是特殊的一些自己写的东西,你可以写一个回收的方法或者类,来回收,比如动画。
onPause中不用去释放,onStop里才要释放那些你的代码自己用的东西
Android内存泄漏就这样产生了:http://blog.csdn.net/sunchaoenter/article/details/7224926
如何避免内存泄漏:http://blog.csdn.net/sunchaoenter/article/details/7209635
4.响应速度优化
响应速度优化的核心思想就是避免在主线程中做耗时操作。
如果Activity5秒钟无法响应屏幕触摸事件或者键盘输入事件就会出现ANR,而BroadcastReceiver如果10秒之内还未完成操作,也会出现ANR。
5.LIstView优化
首先要采用ViewHolder并避免在getView中执行耗时操作;
要根据列表的滑动状态来控制任务的执行频率,比如当列表快速滑动时显示是不太合适开启大量的异步任务的;
最后可以尝试开启硬件加速来使ListView的滑动更加流畅。
ListView的优化策略完全适用于GridView。
6.线程优化
线程优化的思想就是采用线程池,避免程序中存在大量的Thread。线程池可以重用内部的线程,从而避免了线程的创建和销毁所带来的性能开销,同时线程池还能有效地控制线程池的最大并发数,避免大量的线程因互相抢占系统资源从而导致阻塞线程的发生。
7.其他建议
避免创建过多的对象;
不要过多使用枚举,枚举占用的内存空间要比整型大;
常量请使用static fianl来修饰;
使用一些Android特有的数据结构,比如SpareArray和Pair等,它们都具有更有的性能;
适当使用软引用和弱引用;
采用内存缓存和磁盘缓存;
尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄漏。
综合技术
1.用户使用中,遇到crash,如何获得crash信息。
可以通过CrashHandler来监视应用的crash信息,给程序设置一个CrashHandler,这样当程序crash时就会调用CrashHandler的uncaughtException方法。在方法中获得crash信息,然后上传服务器。
2.Android有一个限制,就是整个应用的方法数不能超过65536,否则会编译错误,并且无法安装到手机上。Google提供了multidex方案解决这个问题,通过将一个dex文件拆分为多个dex文件来避免单个dex文件方法数越界的问题。(dex文件:Andorid平台上可执行文件类型)。
方法越界的另一种解决方案是动态加载。动态加载可以直接加载一个dex形式的文件,将部分代码打包到一个单独的dex文件中(也就是dex格式的jar或者apk),并在程序运行时根据需要去动态加载dex中的类,这种方式既可以解决环节方法数越界额问题,也可以为程序提供按需加载的特性,同时这还为应用按模块更新提供了可能性。
3.反编译:
在Android中反编译主要通过dex2Jar以及apktool来完成。dex2Jar可以将apk转换成一个jar包,这个jar包在通过反编译工具jd-gui来打开就可以查看到反编译后的Java代码了。Apktool主要用于应用的解包和二次打包。
1. Inent大多是应用层的功能整合,Binder则大多用在系统层的整合
2. 接口能不能继承接口:可以,因为手动创建一个aidl文件,会自动在gen目录下生成同名的 java文件,这个java文件本身就是接口,而且它还继承了android.os.IInterface,所以我认为接口可以继承接口。所以在Binder中传输的接口都需要继承IInterface接口。
3. 尽管Book类和IBookManager在相同的包下,但是在IBookManager中仍需导入Book类,这是AIDL的特殊之处。
4. java synchronized详解:http://www.cnblogs.com/GnagWang/archive/2011/02/27/1966606.html
5. 在android中的主线程也就是UI线程,UI线程中才可以操作界面元素。在主线程中执行耗时操作,会造成UI阻塞,导致ANR;在非主线程中更新UI,会直接报错。
案例:Serrvice向Activity发送广播,更新UI :http://blog.csdn.net/u012975370/article/details/50033553#t0
6. android对单个应用所使用的最大内存做了限制,可以利用多进程模式增大一个应用可使用的内存。
7. android中使用多进程的方法:
1).给四大组件在AndroidManifest.xml中指定android:process属性。
2).非常规方法:通过JNI在native层去fork一个新的进程。
8. 使用多进程会造成如下的几个方面的问题:
1) 静态成员和单例模式完全失效
2) 线程同步机制完全失效
3) SharedPreferences的可靠性下降
4) Application会多次创建
9. Thread的运行是独立于Acitivty的,当Accitivty被finish之后,如果你没有主动停止Thread或者Thread中的run方法没有执行完毕的话,Thread也会一直执行。
13. Android的动画类型:View Animation , Drawable Animation , Property Animation 。
View Animation 只是支持简单的缩放、平移、旋转、透明等基本属性,View动画对View的影响做操作,它并不能改变View的位置参数以及宽高。如果想要保留动画后的状态需要属性fillAfter,设置为true,否则动画完成后自动消失。 Property Animation解决了View Animation不能真正改变View属性的问题。
14. getX/getY返回的是相对于当前View左上角的x和y坐标,
getRawX/getRawY返回的是相对于手机屏幕左上角的x和y坐标。
15.
scrollTo()相对于初始位置滚动某段距离
scrollBy()相对于当前位置滚动某段距离
16.使用Toast时,建议定义一个全局的Toast对象,这样可以避免连续显示Toast时不能取消上一次Toast消息的情况(如果你有连续弹出Toast的情况,避免使用Toast.makeText)
17.使用Adapter的时候,如果你使用了ViewHolder做缓存,在getView的方法中无论这项的每个视图是否需要设置属性(比如TextView设置的属性可能为null,item的某一个按钮的背景为透明、某一项的颜色为透明等),都需要为每一项的所有视图设置属性(textview的属性为空也需要设置setText(“”),背景透明也需要设置),否则在滑动的过程中会出现内容的显示错乱。
18.不同API版本的AsyncTask实现不一样,有的是可以同时执行多个任务,有的API中只能同时执行一个线程,所以在程序中同时执行多个AsyncTask时有可能遇到一个AsyncTask的excute方法后很久都没有执行。调用AsyncTask的excute方法不能立即执行程序的原因分析及改善方案
19.系统上安装了很多浏览器,如何指定某个浏览器访问固定的网页
Intent intent =newIntent();
intent.setAction("android.intent.action.VIEW");
Uri content_url =Uri.parse("http://www.163.com");
intent.setData(content_url);
intent.setClassName("com.android.browser","com.android.browser.BrowserActivity");
startActivity(intent);
只要修改以intent.setClassName("com.android.browser","com.android.browser.BrowserActivity");
中相应的应用程序packagename 和要启动的activity即可启动其他浏览器来
uc浏览器":"com.uc.browser", "com.uc.browser.ActivityUpdate“
opera浏览器:"com.opera.mini.android", "com.opera.mini.android.Browser"
qq浏览器:"com.tencent.mtt", "com.tencent.mtt.MainActivity"
20.在Activity中弹出对话框,并不会执行onPause,所以对话框消失,也不会执行onResume方法。因为我们创建dialog的时候,需要传入一个Acitivity的上下文,既然使用者Activity的上下文,就认为Acitivity是在交互状态中所以,每一失去焦点,就不会执行onPause方法。
Java基础:
1.面向对象
面向过程:强调的是功能行为
面向对象:将功能行为进行封装,强调的是具备功能行为的对象。
例如:将大象装进冰箱
这个过程包括,打开冰箱->存储->关闭冰箱。
这三个行为的对象是冰箱
如果我们面向过程,就会执行打开、存储、关闭这三个操作。
但是我们面向对象,我们只需要记住冰箱,让冰箱去执行打开、存储、关闭的过程即可。
而且,我们从执行者变成了指挥者。
原来我们需要自己执行打开、存储、关闭操作。
现在我们只需要指挥冰箱去打开、存储、关闭。至于执行过程,怎么执行,我们不需要关心。
2.Java多线程
多线程尽可能利用CPU的资源,提高效率
只有运行状态的线程才会有机会执行代码,主线程的终止不会影响其他的正在运行中的线程,主线程终止也就是main()方法退出了,只有进程中的所有线程都终止了,进程(JVM进程)才会退出,只要有线程没有终止,进程就不会退出。
线程编程的两种方式:
1.Thread类
线程(Thread)包含一个可以运行的过程:run()方法。
2.创建一个具体线程的步骤如下:
第一,继承Thread类。
第二,覆盖run方法,就是更新运行过程,实现用户自己的过程。
第三,创建线程实例,就是创建一个线程。
第四,使用线程实例的start()方法启动线程,启动以后线程会尽快的去并发执行run()。
方法一:继承Thread类
步骤:
1).继承Thread类,覆盖run()方法,提供并发运行的过程。
2).创建这个类的实例
3).使用start()方法启动线程。
方法二:实现Runnable接口
步骤:
1).实现Runnbale接口,实现run()方法,提供并发运程的过程
2).创建这个类的实例,用这个实例作为Thread构造器参数,创建爱你Thread类
3).使用start()方法启动线程
class MyRunnable implements Runnable {
@Override
public void run() {
}
}
public class TestThread {
public static void main(String[] args) {
Runnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
线程的常用方法和属性
1.线程的优先级
t.setPriority(Thread.MAX_PRIORITY);
默认有10级优先级,优先级高的线程获得running状态的机会多,机会的多少不能代码干预。默认的优先级是5.
2.后台线程
t.setDaemon(true);
Java进程的结束:当前所以前台线程都结束时,java进程结束。
当前台线程全都结束时,不管后台线程有没有执行结束,都要被停止。
3.获得线程的名字
geName();
4.获得当前线程
Thread main = Thread.currentThread();
Thread的状态
New新建状态 -> Runnable可运行(就绪)状态 -> Running运行状态 -> Block阻塞(挂起)状态 -> Dead死亡状态
Sleep状态的打断唤醒
1.Thread.sleep(times) Running->Block ->Runnable
2.Interrupt()方法打断/中断。
使用该方法可以让一个线程提前唤醒另外一个sleep Block的线程。
3.被唤醒线程会出现中断异常。
Sleep()和wait()的区别
Sleep:Thread类中定义的方法,表示线程休眠,会自动唤醒
Wait:Object中定义的方法,需要手工调用notify()或者notifyAll()方法
Sleep就是正在执行的线程主动让出了cpu,cpu去执行其他的线程,在sleep指定的时间过后,cpu才会回到这个线程上继续执行,如果当前线程进入同步锁,sleep方法并不会释放锁,即使当前线程使用sleep方法让出了cpu,但其他被同步锁挡住了的线程也无法得到执行。Wait是指在一个已经进入同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到暂时的可以得到同步锁并运行。只有其他线程调用notify方法,(notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了, 但不是马上得到锁,因为锁还在别人的手里,别人还没有释放。如果notify方法后面的代码还有很多,需要这些代码执行完后才会释放锁,可以在notify方法后增加一个等待和一些代码,看看效果),调用wait方法的线程就会接触wait状态和程序可以再次得到锁后继续向下运行。
public class TestThread {
public static void main(String[] args) {
new Thread(new Thread1()).start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new Thread(new Thread2()).start();
}
private static class Thread1 implements Runnable {
@Override
public void run() {
synchronized (TestThread.class) {
System.out.println("enter thread1...");
System.out.println("thread1 is waiting");
try {
/*
* 释放锁有两种方式,第一种方式是程序自然离开监视器的范围,也就是离开了synchronized关键字关下的代码范围,
* 另一种方式就是synchronized关键字关下的代码内部调用监视器对象的wait方法
* ,这里,使用wait方法释放锁。
*/
TestThread.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread1 is going on...");
System.out.println("thread1 is being over !");
}
}
}
private static class Thread2 implements Runnable {
@Override
public void run() {
System.out.println("enter thread2...");
System.out
.println("thread2 notify other thread can release wait status");
/*
* 由于notify方法并不释放锁,即使thread2调用下面的sleep方法休息10毫秒,但thread1仍然不会执行,
* 因为thread2没有释放锁,所以thread1无法得锁
*/
TestThread.class.notify();
System.out.println("thread2 is sleeping ren millisecond");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread2 is going on...");
System.out.println("thread2 is being over !");
}
}
}
Runnable和Thread的区别:
在实际开发中一个多线程的操作很少使用Thread类,而是通过Runnable接口完成的。但是在使用Runnable定义的子类中没有start()方法,只有Thread类中才有,查看Thread元那么会发现,有一个构造函数public Thread(Runnable target);此构造方法接受Runnable的子类实例。
由于java类有单继承多接口的特点,所以使用Runnable可以避免点继承的局限性;适用于资源共享。其实,Thread也是Runnable的子类。
http://mars914.iteye.com/blog/1508429
http://blog.csdn.net/wwww1988600/article/details/7309070
线程安全:
线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,没有写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
线程池:
Java线程池是通过hashmap获取当前的线程,保持线程同步。
子线程更新UI:
Android的UI是单线程的,为了避免拖住GUI,一些较费时的对象应该交给独立的线程去执行,如果幕后的线程来执行UI对象,Android就会发出错误讯息CalledFromWrongThreadException。
堆主要是用来存放对象的,栈主要是用来执行程序的。
Thread类中的start()和run()方法的区别:
Start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果是不一样的。当你调用run()方法的时候,只会在原来的线程中调用,没有新的线程启动,start()才会启动新线程。
Java内存模型对一个线程所做的变动能被其他线程可见提供了保证,它们之间是线程发生关系,这个关系定义了一些规则让程序员在并发线程时思路更清晰,比如,线程发生关系确保了:
1).线程内的代码能够按先后顺序执行,这被成为程序次序规则;
2).对于同一个锁,一个解锁操作一定要发生在时间上后发生的另一个锁定操作之前,也叫做管理锁定规则;
3).前一个对volatile的写操作在后一个volatile的读操作之前,也叫做volatile变量规则;
4).一个线程内的任何操作都必须在这个线程start()调用之后,也叫做线程启动规则;
5).一个线程的所以操作都会在线程终止之前,线程终止规则;
6).一个对象的终结才做必须在这个对象构造完成之前,也叫对象终结规则;
7).可传递性。
50个java多线程的面试题:http://www.androidchina.net/589.html
Java集合:
Collection
List
LinkedList
ArrayList
Vector
Stack
Set
Map
Hashtable
HashMap
WeakHashMap
Map:提供key到value的映射
List:有序,可重复的集合
ArrayList:实质就是一个会自动增长的数组。
1).查询效率比较高,增删效率比较低,适用于查询比较频繁,增删动作比较少的元素管理的集合。
2).加载大批量的数据时,先进行手动扩容(调用ensureCapacity(int minCapacity))
LinkedList:底层是用双向循环链表来实现的。
查询效率低,但是增删效率很高,适用于增删动作的比较频繁,查询次数较少的元素管理的集合。
Set:无序的,不允许有重复的元素集合。
HashSet:
Object类中的hashCode()的方法是所有类都会继承的方法,这个方法会算出一个Hash码值返回,HashSet会用Hash码值去和数组长度取模,对象的模值(这个模值就是对象要存放在数组中的位置,和数组的下标相同)相同时才会判断数组中的元素和要加入的对象的内容是否相同,如果不同才会再找位置添加进去,相同则不允许添加。
如果数组中的元素和要加入的对象的hashCode()返回了相同的Hash码值,才会用equals()方法来判断两个对象的内容是否相同。
注意:要存入HashSet的集合对象中的自定义类必须hashCode()、euqals()两个方法,才能保证集合中元素不重复。
Map:存放key-value对(有关系的两个对象,一个叫做key,一个叫做value,同时存入)
HashMap:基于哈希表的Map接口的实现,此实现提供所有可选的映射操作,并允许使用null值和null键。
Hashtable:同HashMap,一般不使用
HashMap和Hashtable的区别:
HashMap:非线程安全,不支持并发控制,允许空的键值对。
Hashtable:是线程安全,支持并发控制,不允许有空的键值对。
Key一般是8中基本类型的封装类或者是String类,拿自己自定义的类作为key没有意义。
线性表List
List表示有先后次序的对象集合,比如歌曲列表。
1).ArrayList = Object[] + 线性表操作(增删改差)
2).StringBuilder = char[] + 操作(增删改查)
ArrayList和 StringBuilder相比,基本操作都差不多,但是ArrayList除了可以添加String类型,还可以添加其他对象。
StringBuilder打印出来是一个字符串,ArrayList打印出来,是一个类似于数组,用中括号包裹起来,中间用逗号隔开的一组内容。
对于Vector&ArrayList、Hashtable&HashMap,要记住线程安全的问题,记住Vector和Hashtable是旧的,是java一旦生就是提供的,它们是线程安全的,ArrayList和HashMap是java2时才提供的,它们是线程不安全的。所以,一般单线程的时候,用HashMap和ArrayList,因为不需要考虑线程安全,但是多线程时就需要用Vector和Hashtable。
Vector和ArrayList都有一个初始的容量大小,当存储进它们里面的元素超过容量时,就会自动增加大小,Vector默认增加为原来的两倍,ArrayList默认增加为原来的1.5倍。ArrayList都可以设置初始的空间大小,Vector可以设置增长的空间大小,而ArrayList则不可以设置。
LinkedList和ArrayList
ArrayList是使用变长数组算法实现的,ArrayList继承自List();
1).ArrayList和Vector的比较:Vector线程安全的,效率较低,也是使用可变长数组算法实现的,继承自List接口。ArrayList,线程不安全的,效率高速度快,现在常用。
2).LinkedLIst和ArrayList的比较:LinkedList是采用双向循环链表实现的List。ArrayList是采用变长数组算法实现的List。
3).区别:ArrayList采用的数组形式保存对象,LinkedList基于链表的数据结构。对于随机访问get和set方法,ArrayList优于LinkedList,因为LinkedList要移动指针;对于新增add和删除remove操作,LinkedList比较占优势,因为ArrayList要移动数据。
Vector的放过是线程同步的,是线程安全的,而ArrayList的方式不是,由于线程的同步必然影响性能,因此ArrayList的性能比Vector好;当Vector或ArrayList中的元素超过它的初始化大小时,Vector会将它的容量翻倍,而ArrayList只会增加50%,所以,ArrayList有利于节约内存空间。
最后
以上就是粗犷小猫咪为你收集整理的android开发经验笔记总计四大组件:IntentHanlderListViewAndroid的线程和线程池 动画View子线程更新ui的三种方式Bitmap的加载和CacheJNIToastHTTPAndroid性能优化综合技术Java基础:Java集合:的全部内容,希望文章能够帮你解决android开发经验笔记总计四大组件:IntentHanlderListViewAndroid的线程和线程池 动画View子线程更新ui的三种方式Bitmap的加载和CacheJNIToastHTTPAndroid性能优化综合技术Java基础:Java集合:所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复