Service要点全解析

转载:

1、Service概述

Service的主要作用是,让系统可以在后台干一些不与用户交互的操 作,这些操作可能会比较耗时,比如去下载一些网络资源等;也可能是一项长期运行的工作,比如说监听电话来电、播放音乐等。初听起来,Service与线程 Thread很像,但Service和Thread完全是两个不同的东西啊。

(1)Service不是运行在一个独立的进程中,它和我们的应用程序在同一个进程中;

(2)Service也不是一个线程,相反,Service是运行在主线程的,因此我们不能直接在Service中干上面那些耗时操作,因为它会很耗CPU,阻塞主线程,很容易出现ANR错误(Application Not Responding),合适的做法是,在Service中开启一个Thread,进行上面的耗时操作。

如果我们要在Service中开启线程进行工作,我们也可以使用Service的一个子类IntentService,IntentService类中已经开启了线程,我们只需要实现一个方法即可,后面具体介绍。

2、Service用法

和Activity类似,我们要使用Service,只需要通过Intent发出请求即可。当然,在使用Service前,记得在AndroidManifest.xml中进行声明。启动方式有两种:

我们先在Activity中加入两组四个按钮,一组为startService的启动按钮和相应的停止按钮;一组为bindService的启动按钮和相应的停止按钮。如图:

启动方式一:startService()

在Activity中,我们如下使用:

  1. public class MainActivity extends Activity implements OnClickListener{
  2.     private Button startServiceBtn,stopServiceBtn,bindServiceBtn,unBindServiceBtn;
  3.     @Override
  4.     protected void onCreate(Bundle savedInstanceState) {
  5.         super.onCreate(savedInstanceState);
  6.         setContentView(R.layout.activity_main);
  7.         startServiceBtn = (Button) findViewById(R.id.startService);
  8.         stopServiceBtn = (Button) findViewById(R.id.stopService);
  9.         bindServiceBtn = (Button) findViewById(R.id.bindService);
  10.         unBindServiceBtn = (Button) findViewById(R.id.unBindService);
  11.         startServiceBtn.setOnClickListener(this);
  12.         stopServiceBtn.setOnClickListener(this);
  13.         bindServiceBtn.setOnClickListener(this);
  14.         unBindServiceBtn.setOnClickListener(this);
  15.     }
  16.     @Override
  17.     public void onClick(View v) {
  18.         switch (v.getId()) {
  19.         case R.id.startService://通过startService()的方式启动Service
  20.             Intent intent = new Intent(this,ServiceTest.class);
  21.             startService(intent);
  22.             break;
  23.         case R.id.stopService:
  24.             stopService(new Intent(this,ServiceTest.class));
  25.             break;
  26.         case R.id.bindService://通过bindService()的方式启动Service
  27.             //TODO
  28.             break;
  29.         case R.id.unBindService:
  30.             //TODO
  31.             break;
  32.         }
  33.     }
  34. }
我们的Service如下:
  1. public class ServiceTest extends Service {
  2.     @Override
  3.     public void onCreate() {
  4.         super.onCreate();
  5.         Log.d(“TAG”“onCreate”);
  6.     }
  7.     @Override
  8.     public int onStartCommand(Intent intent, int flags, int startId) {
  9.         Log.d(“TAG”“onStartCommand”);
  10.         return super.onStartCommand(intent, flags, startId);
  11.     }
  12.     @Override
  13.     public IBinder onBind(Intent arg0) {
  14.         Log.d(“TAG”“onBind”);
  15.         return null;
  16.     }
  17.     @Override
  18.     public boolean onUnbind(Intent intent) {
  19.         Log.d(“TAG”“onUnbind”);
  20.         return super.onUnbind(intent);
  21.     }
  22.     @Override
  23.     public void onDestroy() {
  24.         Log.d(“TAG”“onDestroy”);
  25.         super.onDestroy();
  26.     }
  27. }

上面,点击“startService”按钮,我们就通过startService()将传进来的Intent中的Service启动了,启动时Log打印如下:

说明,我们第一次启动Service的时候,会执行onCreate()和onStartCommand()方法,如果我们这个时候,再次点击“startService”按钮,此时打印如下:

可以看到,如果一个Service已经运行了,再次启动这个Service,只会进入onStartCommand(),onCreate()方法只会在第一次启动的时候进行初始化。Service不像我们的Activity,Activity每次通过Intent启动时都会创建一个新的Activity(默认模式下),然后放入到栈中。而同一个Service,应用中只会存在一个实例,在第一次创建时通过onCreate初始化,运行后,再次启动只是进入onStartCommand。

点击“stopService”后,Service被销毁,进入onDestroy()方法。不管前面我们启动了多少次Service,只要在外部调用一次Context.stopService()或者在Service内部自己调用一次stopSelf(),Service就会被销毁。

上面这种启动方式,在启动完Service后,这个Service就开始在后台运行了,同时,也与启动它的Activity失去了联系,因为不能通过ServiceTest service = new ServiceTest()的方式启动Service,因而我们的Activity中不能获取到ServiceTest的实例,也就不能够控制启动后的Service干Activity想干的事,这个Service只能干它内部定义好的功能,没有与启动它的Activity交互能力。

为了解决与启动Service的组件的通信能力,有一种解决方案就是通过广播的形式, 我们在Activity中发出一些想用操作广播,在Service中注册该广播,Service接收到该广播信息后,完成相应的功能。但是这种方案不够优 雅,频繁发送广播比较消耗性能,同时,由于广播接受者中的onReceive()中,不能执行长时间的工作,时间超过后,可能就直接跳出了方法。因此,这 种方案不是首选。

启动方式二:bindService()

如上面所述,如果要求我们的Service能够和启动Service的组件进行通信,我们可以使用bindService的启动方式。

首先改造Service,Service和组件之间是通过管道IBinder接口进行通信的。

  1. public class ServiceTest extends Service {
  2.     private MyLocalBinder mBinder = new MyLocalBinder();
  3.     @Override
  4.     public void onCreate() {
  5.         super.onCreate();
  6.         Log.d(“TAG”“onCreate”);
  7.     }
  8.     @Override
  9.     public int onStartCommand(Intent intent, int flags, int startId) {
  10.         Log.d(“TAG”“onStartCommand”);
  11.         return super.onStartCommand(intent, flags, startId);
  12.     }
  13.     class MyLocalBinder extends Binder{
  14.         public ServiceTest getServiceInstance(){
  15.             return ServiceTest.this;
  16.         }
  17.         //…这里也可以继续写方法对外提供
  18.     }
  19.     @Override
  20.     public IBinder onBind(Intent arg0) {
  21.         Log.d(“TAG”“onBind”);
  22.         return mBinder;
  23.     }
  24.      //对外提供的访问方法
  25.     public void downLoad(){
  26.         System.out.println(“下载操作方法…”);
  27.     }
  28.     @Override
  29.     public boolean onUnbind(Intent intent) {
  30.         Log.d(“TAG”“onUnbind”);
  31.         return super.onUnbind(intent);
  32.     }
  33.     @Override
  34.     public void onDestroy() {
  35.         Log.d(“TAG”“onDestroy”);
  36.         super.onDestroy();
  37.     }
  38. }

继续改造我们的Activity,在ASctivity中获得连接通道,如下:

  1. private ServiceTest mService;
  2.     private boolean isConnected = false;//我们在使用bindService时,最好定义一个是否连接的标志,方便我们在组件中通信前的判断操作
  3.     ServiceConnection connection = new ServiceConnection() {
  4.         @Override
  5.         public void onServiceConnected(ComponentName className, IBinder binder) {
  6.             ServiceTest.MyLocalBinder localBinder = (MyLocalBinder)binder;    //先获得管道
  7.             mService = localBinder.getServiceInstance();    //通过管道,拿到Service的实例
  8.             isConnected = true;
  9.             mService.downLoad();//拿到Service实例后想干嘛干嘛
  10.         }
  11.         //注意:这个方法当Service意外运行失败时调用,如系统杀死这个Service或者运行Service时遇到崩溃,调用unBinderService并不会调用该方法
  12.         @Override
  13.         public void onServiceDisconnected(ComponentName arg0) {
  14.             isConnected = false;
  15.         }
  16.     };
  17.     @Override
  18.     public void onClick(View v) {
  19.         switch (v.getId()) {
  20.         case R.id.startService://通过startService()的方式启动Service
  21.             Intent intent = new Intent(this,ServiceTest.class);
  22.             startService(intent);
  23.             break;
  24.         case R.id.stopService:
  25.             stopService(new Intent(this,ServiceTest.class));
  26.             break;
  27.         case R.id.bindService://通过bindService()的方式启动Service
  28.             Intent intent2 = new Intent(this,ServiceTest.class);
  29.             bindService(intent2, connection, BIND_AUTO_CREATE);
  30.             break;
  31.         case R.id.unBindService:
  32.             unbindService(connection);
  33.             break;
  34.         }
  35.     }
  36.     @Override
  37.     protected void onDestroy() {
  38.         super.onDestroy();
  39.         if(isConnected){
  40.             unbindService(connection);
  41.             isConnected = false;
  42.         }
  43.     }

点击“bindService”按钮,我们就通过bindService()将传进来的Intent中的Service启动了,启动时Log打印如下:

我 们通过bindService()方法第一次启动后,会进入Service的onCreate()和onBind()方法。如果我们另一个组件(如 Activity),又对同一个Service发起了bindService()操作(也就是说在bindService()中传入了不同的 ServiceConnection),此时只会进入onBind()方法。也就是说onCreate也只是在Service第一次创建时执行。

我们点击“unBindService”时,打印如下:

此时走的是onUnbind和onDestroy方法。

可以看到,不管通过哪种方式启动Service,同一个Service在整个应用程序中只会有一个实例存在,如果通过bindService启动,获得的IBinder实例也都是同一个。四大组件中,只有在Activity、Service、Content Providers中通过bindService启动Service。

3、Service生命周期

从上面的打印日志可以看到,两种启动Service方式走的生命周期方法是不同的,官方的生命周期图很好地描述了两种启动方式下的回调:

两种方式都是只有在第一次启动没有运行的Service时,才会进入onCreate()方法。当Service启动后,后面多次继;启动该Service,只会进入onStartCommand()或者onBind()。

(1)当我们通过startService()方法启动Service时,不管前面我们启动了多少次Service,只要在外部调用一次stopService()或者在Service内部自己调用一次stopSelf(),Service就会被销毁;

(2)当我们通过bindService()启动Service时,前面我们多次启动Service后,当没有客户端连接后(即所有客户端发出了unBindService),这个Service将会被系统销毁;

(3)当这个 Service既被startService启动,又被bindService启动时,即使当所有连接的客户端断开连接后,Service也不会被销毁, 除非再调用一次stopService或内部使用stopSelf(),这个时候Service才会被销毁。或者说如果我们调用了stopService或内部使用stopSelf(),Service也不会被销毁,只有当所有的客户端断开连接后,Service才会被销毁。也就是说,在这种情况下,Service必须在既没有任何Activity关联又停止的情况下,Service才会被销毁。这种情况下的生命周期如下:

注意:为了让我们的Service不过多的浪费CPU资源、内存资源,我们需要在必要的时候进行解除绑定。

(1)当我们的Service只在Activity被用户可见的时候,才与Activity进行交互,那我们应该在Activity的onStart()中bindService,在onStop()中unBindService();

(2)如果我们希望Activity即使在后台时也能够与Service交互,那我们应该在onCreate()中bindService(),在onDestroy()中unBindService(),即Activity在整个生命周期中要与Service交互。

4、IntentService

正 如我们第一部分所谈到的,Service和Thread完全是两个不同的东西,Service主要功能是可以在后台执行一些操作,这些操作不需要与用户交 互。Service是直接运行在主线中中的,如果我们需要在Service中执行耗时操作,为了避免ANR错误,是需要在Service中开启线程来执行 的。对于使用Service有开启线程需求的开发者来说,Android提供了IntentService给用户,IntentService内部已经帮我们开启了线程,我们只需要实现它定义的onHandleIntent()方法,在里面实现我们的功能即可。注意,IntentService不能处理多个线程的请求,但是可以处理多个启动Service的请求。

IntentService提供的功能:

(1)内部创建了一个工作线程,来处理每个启动Service的请求传来的Intent;

(2)内部有一个工作队列,来分别处理多个启动Service的请求,因此避免了多线程的问题;

(3)在所有的请求处理完后,自动停止服务,因此我们不必在Service内部自己写stopSelf();

(4)提供了默认onBind()的实现,直接返回null,意味着IntentService只能通过startService()的方式启动;

(5)提供了默认onStartCommand()的实现,它将我们的Intent放入到了工作队列中,然后执行我们具体实现的onHandleIntent()方法。

一个简单实例如下:

  1. public class IntentServiceTest extends IntentService {
  2.     public IntentServiceTest(){//提供一个默认的构造方法,调用父类构造方法,传入工作线程的名字
  3.         super(“IntentServiceTest”);
  4.     }
  5.     @Override
  6.     protected void onHandleIntent(Intent arg0) {
  7.         long endTime = System.currentTimeMillis() + 5*1000;
  8.           while (System.currentTimeMillis() < endTime) {
  9.               synchronized (this) {
  10.                   try {
  11.                       wait(endTime – System.currentTimeMillis());
  12.                   } catch (Exception e) {
  13.                   }
  14.               }
  15.           }
  16.     }
  17. }

使用IntentService主要是注意两点,一是定义一个默认构造方法,里面调用父 类构造方法,并定义工作线程的名字;第二是实现onHandleIntent()方法,我们在这里面具体实现我们Service需要做的事情。可以看到, 如果我们要使用开启线程的Service,IntentService提供了一种非常好的方案,让用户只需要关注与onHandleIntent接口的具 体实现即可,同时用工作队列的形式支持多启动服务的访问。

5、让我们的Service到前台运行

我 们的Service默认都是在后台默默运行的,用户基本察觉不到有Service在运行。此时,Service的优先级是比较低的,当系统资源不足的时 候,很容易被销毁。因此,如果我们想让用户知道有Service在后台运行,如音乐播放器,或者想让Service一直保持运行状态,不容易被系统回收, 此时,就可以考虑使用前台Service。前台Service必须要设置有一个Notification到手机的状态栏,类似360一样,因此前台 Service能够与用户进行交互,它的优先级也就提高了,在内存不足的时候,不会优先去回收前台Service。

创建前台Service也很简单,就是设置一个Notification到状态栏,如下:

  1. @Override
  2.     public void onCreate() {
  3.         super.onCreate();
  4.         Log.d(“TAG”“onCreate”);
  5.         Notification notification  = new Notification(R.drawable.ic_launcher,“前台Service通知来了”,System.currentTimeMillis());
  6.         Intent notificationIntent = new Intent(this,MainActivity.class);
  7.         PendingIntent pendingIntent = PendingIntent.getActivity(this0, notificationIntent, 0);
  8.         notification.setLatestEventInfo(this“通知标题”“前台Service内容”, pendingIntent);
  9.         //设置到前台运行,第一个参数为通知notification的唯一ID
  10.         startForeground(1, notification);
  11.     }

运行效果如下:

如果,我们要移除掉这个前台Service,只需要调用stopService()即可。这个方法并不会停止Service,只是移除掉Notification。

6、如何保证Service不被杀死

(本部分内容来自:http://www.cnblogs.com/rossoneri/p/4530216.html

这个倒是有点流氓软件的意思,但有些特定情况还是需要服务能保持开启不被杀死,当然这样做我还是在程序里添加了关闭服务的按钮,也就是开启了就杀不死,除非在软件里关闭。

服务不被杀死分3种来讨论

1.系统根据资源分配情况杀死服务

2.用户通过 settings -> Apps -> Running -> Stop 方式杀死服务

3.用户通过 settings -> Apps -> Downloaded -> Force Stop 方式杀死服务

第一种情况:

用户不干预,完全靠系统来控制,办法有很多。比如 onStartCommand() 方法的返回值设为 START_STICKY ,服务就会在资源紧张的时候被杀掉,然后在资源足够的时候再恢复。当然也可设置为前台服务,使其有高的优先级,在资源紧张的时候也不会被杀掉。

第二种情况:

用户干预,主动杀掉运行中的服务。这个过程杀死服务会通过服务的生命周期,也就是会调用 onDestory() 方法,这时候一个方案就是在 onDestory() 中发送广播开启自己。这样杀死服务后会立即启动。如下:

@Overridepublicvoid onCreate() {
// TODO Auto-generated method stubsuper.onCreate();

mBR = new BroadcastReceiver() {
@Overridepublicvoid onReceive(Context context, Intent intent) {
// TODO Auto-generated method stubIntent a = new Intent(ServiceA.this, ServiceA.class);
startService(a);
}
};
mIF = new IntentFilter();
mIF.addAction("listener");
registerReceiver(mBR, mIF);
}

@Overridepublicvoid onDestroy() {
// TODO Auto-generated method stubsuper.onDestroy();

Intent intent = new Intent();
intent.setAction("listener");
sendBroadcast(intent);

unregisterReceiver(mBR);
}

当然,从理论上来讲这个方案是可行的,实验一下也可以。但有些情况下, 发送的广播在消息队列中排的靠后,就有可能服务还没接收到广播就销毁了(这是我对实验结果的猜想,具体执行步骤暂时还不了解)。所以为了能让这个机制完美 运行,可以开启两个服务,相互监听,相互启动。服务A监听B的广播来启动B,服务B监听A的广播来启动A。经过实验,这个方案可行,并且用360杀掉后几 秒后服务也还是能自启的。到这里再说一句,如果不是某些功能需要的服务,不建议这么做,会降低用户体验。

也就是如下操作,启动服务时,我们开启两个Service,A和B,在 A的onCreate中注册B的广播事件,在B的onCreate中注册A的广播事件,当A被销毁时,进入onDestroy()方法时,发送A的广 播,B接收到广播之后再启动A;同理,如果B被销毁,发送广播给A,让A启动B。

第三种情况:

强制关闭就没有办法。这个好像是从包的level去关的,并不走完整的生命周期。所以在服务里加代码是无法被调用的。处理这个情况的唯一方法是屏蔽掉 force stop和 uninstall 按钮,让其不可用。方法自己去找吧。当然有些手机自带的清理功能就是从这个地方清理的,比如华为的清理。所以第三种情况我也没有什么更好的办法了。

10 things you didn’t know about Android’s Service component

英文链接:https://medium.com/@workingkills/10-things-didn-t-know-about-android-s-service-component-a2880b74b2b3#.k5ni2oe4g

I’m terribly sorry for the clickbait-y title, I just couldn’t resist.

Over the years I had more than a few conversations with other fellow Android developers, and one constant topic that kept popping up is how much the Service component is misunderstood, across both beginners and experts. This list is the result of those discussions: it has been wandering in the back of my mind for quite some time, and I finally decided to give it physical (digital?) shape here.

This is not meant to explain in detail how Service works, the official documentation (here, here and here) does a surprisingly good job at that; instead, it’s a collection of overlooked/misunderstood/forgotten concepts that are essential to grasp in order to master the subject.


1. A Service is not a “better AsyncTask”

It’s not made to perform generic asynchronous/background operations: its purpose is to execute logic even when no Activityis visible; think of it as an invisible Activity.

Keep in mind that each Service is a special component that has a cost, not just for your app, but for the entire system.

2. A Service by default runs on the main thread, in the same process of your application

You can optionally run it in a different process, but you should avoid itunless it’s absolutely necessary and you understand all the costs associated with doing so.

3. There’s no magic in IntentService

It works by creating a HandlerThread on which it queues the work to be done, a technique you can easily use outside of a Service.

It’s a simple class, long only 164 lines (74 without comments), go check it out!

4. There can only be one instance of a Service at a time

No matter how you create it, there can only ever be one instance at a time, even when external applications/processes interact with it.

5. A Service can be killed pretty easily

Don’t think that memory pressure is an “exceptional” condition: design your Service to gracefully handle restarts by the system, it’s a natural part of its lifecycle.

You can flag it as foreground to make it harder to kill, but do it only if absolutely necessary.

Note that when code is running inside onCreate(), onStartCommand(), oronDestroy(), it’s treated as if it was in foreground even if it’s not.

See here to understand how likely it is for your process to be killed.

6. A Service can be “started”, “bound”, or both at the same time

Stopping it explicitly won’t terminate it as long as there are bound components, and unbinding all components won’t terminate it until it’s explicitly stopped (if it was ever started). Also note that no matter how many times you call startService(), a single call to stopService() orstopSelf() will stop it.

See this useful chart:

Lifecycle of started and bound modes

7. START_FLAG_REDELIVERY avoids losing input data

If you pass data while starting a Service, returning the flagSTART_FLAG_REDELIVERY in onStartCommand() is useful to avoid losing it if the Service is killed while processing it.

8. Foreground notifications can be partially hidden

A foreground Service has to show a persistent notification, but you can give it a priority value of PRIORITY_MIN to hide it from the status bar (it will still be visible in the notification shade).

9. A Service can start an Activity

Like every Context that is not an Activity, you can start an Activity from aService if you add the FLAG_ACTIVITY_NEW_TASK flag.

You can also show Toast and Status Bar notifications.

10. You are allowed to use the Single Responsibility Principle

Incredibly enough, you should not bundle your business logic in the Serviceclass, but in a separate class. That way, among many other benefits, you are free to run it anywhere else, if needed. Amazing!


Now keep this list in mind, spread the word, and help stop Service abuse!

Easy Android: Launch screen

英文链接:https://medium.com/android-news/easy-android-launch-screen-39668f786004#.6xj4ako4r

Today, we are going to have a look at the splash screen of Android, theLaunch screen.

A branded launch screen

Understanding the launch screen

First of all, what is a launch screen? Well it is basically the same thing as a splash screen, but with a different name.

The most important thing however is to understand how the launch screen should work and how to implement it in the correct way.

According to the Material Design guidelines:

“The launch screen is a user’s first experience of your application.”

Okay, so from reading this line, the launch screen seems very important and something that you really shouldn’t mess up with…?!? And that is correct. The launch screen is the very first thing that the user associate your app with, therefor, it is very important that you don’t implement it in the wrong way.

The launch screen’s main function is to act as a placeholder until the rest of your app has loaded.

As you can see below (from my app Party Starter) the launch screen acts like a placeholder until the rest of the UI is ready.

此处有动画,请看原链接!

This app isn’t very heavy and doesn’t take very long to load – the launch screen isn’t being shown for long.

However, if the app launches for the first time after being installed from Google Play then the loading takes considerably longer and instead of showing the user a blank canvas of nothing, we chose to to show a launch screen with our brand until the app has finished loading. Remember, the launch screen is the first thing that the user experiences with your app. A more detailed view of what is happening is shown below:

User opens the app — launch screen is shown and loads the app in the background — final UI is ready

What the launch screen is not

It is important to remember that the launch screen is firstly a placeholder,secondly a place to advertise your brand at.

Do not show the launch screen if you don’t have to.

You should not in any way display the launch screen if it not necessary, the user will see the launch screen as something that slows down the user experience rather than a placeholder. If the app is already loaded in the phone’s memory, skip the launch screen and head directly to the UI.

And lastly, implement the launch screen so that it is the first thing that the user sees. Do not show the launch screen after displaying a blank canvas as shown below. That is not the way to go!

A big no-no!

The code

After we have learned what a launch screen is, it is time to do the coding part. This is super simple and doesn’t require a single line of Java, amazing isn’t it? We are actually going to work with themes!

In your styles.xml:
Create a new theme style that looks like your existing but add this line to the style:

<item name=”android:windowBackground”>@drawable/launch_screen</item>

It should look like this:

<--! styles.xml -->
<style name="PartyTheme" parent="Theme.AppCompat.NoActionBar">
    <item name="windowNoTitle">true</item>
    <item name="colorPrimary">@color/pink500</item>
    <item name="colorPrimaryDark">@color/transparentToolbar</item>
    <item name="colorAccent">@color/pink500</item>
    <item name="android:windowBackground">@drawable/launch_screen</item>

</style>

Next up is to create a drawable that acts like our background to the style that we just created. Right click on your drawable folder > New > Drawable resource file. Paste the following code to the new file:

<--! launch_screen.xml -->
<?xml version="1.0" encoding="utf-8"?>

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <color android:color="@color/background_material_light"/>
    </item>
    <item>
        <bitmap
            android:src="@drawable/launch_logo"
            android:tileMode="disabled"
            android:gravity="center"/>
    </item>
</layer-list>

As you can see, this drawable has a background colour and a bitmap with our logo in the middle. Remember to set the bitmap’s gravity to center if you want it to located in the center of the screen.

Now, go to your manifest and change the theme of the launch activity to the style we created earlier.

<--! AndroidManifest.xml -->
<activity
    android:name=".MainActivity"
    android:label="@string/app_name"
    android:theme="@style/PartyTheme" >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />    </intent-filter>
</activity>

You are now done.

Now your style’s background will display the launch screen. And because of the order that Android shows the different parts of the app, the launch screen will now be shown instead of a blank canvas when to app is being loaded, cool right?

Final notes

Depending on how your MainActivity look like, you might need to change the background colour of your RelativeLayout/LinearLayout/etc to the original colour that you had previously.

Creating a launch screen is a very task to do and gives your app a more branded look (if that is what you want). Implementing a launch screen in the correct way does not only make your app look better, it also functions as it should: as a placeholder.

[转载] Activity与Fragment易混点归纳

Android开发中Activity和Fragment是非常重要的两个知识点,这里我们就分别归纳一下,在使用Activity和Fragment时需要注意的一些事项,特别是对于Fragment,在对其进行管理时我们要选择好的一些方式。

一、Activity要点

Activity负责展示UI给用户,负责和用户的交互操作。本部分主要对Activity的生命周期、如何保存状态信息、如何讲一个Activity设置成窗口模式、启动模式和完全退出Activity的方式,五部分进行总结。

1、生命周期

Android中是通过Activity栈来管理创建出的Activity,当一个新的Activity被启动时,这个新的Activity就会被加入到Activity栈的栈顶,同时呈现到用户前台,供用户交互,原来的栈顶元素被压入到第二位置,但是它的状态信息还保存着。
掌握Activity生命周期,重点是理解官方给出的生命周期图:
当我们通过context.startActivity()启动一个新的Activity,这个新的Activity被压入到Activity栈顶,来到了屏幕的前台,开始一个完整的Activity生命周期。
–onCreate():只在Activity被刚创建时执行,在onCreate方法中我们初始化我们的Activity,比如,加载布局文件,获取控件(findViewById),绑定触摸事件与用户进行操作等。一般情况下,Activity生命周期的切换不会再触发这个方法的执行,只有当系统极度缺乏内存资源,并且这个Activity没有处在用户前台时,此时该Activity可能被杀死,当再次回到这个Activity,因为这个Activity已被杀死,此时就需要重新创建(就相当于重新startActivity()了),因此会再次进入onCreate进行初始化工作。
–onStart()、onResume():在加载完布局后,系统执行一些内部的启动操作,执行到onResume时,用户可以看到完整的UI界面了,此时Activity处于运行状态。
–onPause():当前的Activity失去了焦点,但依然可以看见,比如当我们点击了一个对话框出来、打开了一个非全屏的Activity、打开了一个透明的Activity,此时原来的Activity就会进入onPause()方法,它依然持有状态信息和内存资源,只有当系统极度缺乏内存资源时,才会杀死处于onPause状态的Activity。
–onStop():当一个Activity被另一个Activity完全覆盖的时候,对用户来说这个Activity不可见了,此时这个Activity就进入onPause状态,它也依然保存着状态信息和资源,但是容易被系统杀死,当内存不是那么充足的时候。
–onDestory():当Activity处于onPause和onStop状态时,系统可能因系统资源吃紧会杀死该Activity,在系统回收该Activity之前,会调用onDestory()方法,在里面进行一些资源的释放工作。onDestory()的调用,可能是用户主动的行为,也可能是因系统资源不足系统需要回收该Activity,在回收前调用。
在Activity被杀死的情况下,当这个Activity再次回到用户前台时,需要重新初始化,即再次进入onCreate,如上图左边的环形图。
在Activity没有被杀死的情况下,处于onPause和onStop状态的Activity再次回到前台时,需要系统还原一些状态,对于onPause状态,由于它处于”比较活跃的一种状态”,只需要进入到onResume中由系统设置一些信息即可重新回到前台,对于onStop状态,因为它处于很有可能被销毁的一种状态,部分资源可能丢失,需要先进入onRestart(),然后再次进入onStart()方法,进行回到前台的准备工作。
Thevisible lifetime(可见的生命周期)从onStart()到进入onStop()前(即onStart–>onPause),这个Activity都可被用户看见,这期间,不一定处于前台,也不一定能够供用户交互(比如处于onPause状态时)
Theforeground lifetime(前台生命周期):从onResume()到进入onPause前,这个期间,Activity处于可以和用户交互时期。这个时期,可能也会频繁在onResume和onPause状态间切换。

2、保存状态信息(推荐在onPause中保存)

从前面生命周期中可以看到,当Activity处于onPause和onStop状态时都有可能被系统回收,因此对于我们该Activity中的一些关键数据最好能够保存下来,当用户再次进入时能够回到原来的状态。官方文档中是推荐我们在onPause()中区保存信息的,比如将数据写入到SharedPreference中。Android还提供了onSaveInstanceState(Bundle)方法来保存数据,通过Bundle来持久化数据。如下例子:
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
outState.putString("name","lly");
}

当我们这个Activity被销毁而重新创建re-created的时候,通过onCreate(Bundle)中的参数获取到该信息,如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
    if(savedInstanceState != null){
        name = savedInstanceState.getString("name");
}
}

注意:onSaveInstanceState()方法只是在Activity“很容易被销毁的时候调用”,它并不是Activity的生命周期方法,这个调用时机是不确定的,对于点击返回按钮这种主动行为不会去调用这个方法。

此处经网友提醒后修改–原来观点:网上很多说在按下HOME键和旋转屏幕的时候会去调用,但是经过我测试一下,发现不管是我按下HOME键还是旋转屏幕,这个方法都没有被调用。按我理解,这个方法只有在“很容易被销毁的时候调用”,这个尺度应该是系统根据手机具体内存资源情况决定是否调用。】
修改后–在按下HOME键和旋转屏幕的时候会去调用,以及电话来电等突发状况下能够回调该方法。但是由于onSaveInstanceState不是Activity的生命周期方法,当我们的Activity处于onPause或后台时onStop,如果此时Activity被销毁,回到这个Activity的时候会进行re-created,Activity中原来的数据就会丢失。因此,为了更好的用户体验,最好在onPause中进行数据的保存工作。(至于具体是在onPause中保存,还是onSaveInstanceState中保存,我觉得还是看项目需求,大部分情况下,使用onSaveInstanceState能够满足需求)】
因此官方文档中推荐在onPause中进行数据信息的保存操作。(Note that it is important to save persistent data in onPause() instead ofonSaveInstanceState(Bundle) because the latter is not part of the lifecycle callbacks, so will not be called in every situation as described in its documentation.)
ononSaveInstanceState()方法在Activity、Fragment、Service、各类View中都有提供。其实,默认的ononSaveInstanceState、onRestoreInstanceState()方法的默认实现中,就帮我们保存了系统的一些状态信息,如果我们要保存自己的一些状态信息,就需要重写上面的方法。特别地,在View中,使用ononSaveInstanceState时,对应的这个View一定要在布局文件中定义id,否则没有id,是不会进入到ononSaveInstanceState方法中的。

3、如何将一个Activity设置成窗口的样式

1.在res/values文件夹下的style.xml文件中加入如下代码:
  1. <style name“Theme.FloatActivity”  parent“android:style/Theme.Dialog” >
  2.     <!– float_box为我们定义的窗口背景 ,这个不是必须的–>
  3.     <item name=“android:windowBackground” > @drawable /float_box</item>
  4. </style>
2.在res/drawable文件夹下新建float_box.xml文件,代码如下:
  1. <?xml version“1.0”  encoding“utf-8” ?>
  2. <shape xmlns:android=“http://schemas.android.com/apk/res/android” >
  3.     <solid android:color=“#ffffff”  />
  4.     <stroke android:width=“3dp”  android:color“#ff000000”  />
  5.     <corners android:radius=“3dp”  />
  6.     <padding android:left=“10dp”  android:top“10dp”  android:right“10dp”  android:bottom“10dp”  />
  7. </shape>
3.在AndroidMainifest.xml中Activity的声明中加入
android:theme= “@style/Theme.FloatActivity”
效果如图:
图中显示了一个activity启动另一个activity的效果,布局文件是同一个。其中被启动的activity2是以对话框样式显示,不完全覆盖住启动它的activit1,类似alertDialog。
这与普通的activity不同,默认情况下,activity2会完全遮住activity1,启动activity2后,会调用activity1的onStop方法,而这种以对话框样式显示的activity不会,此时调用的是onPause()。(详见Activity的生命周期)

4、启动Activity的四种模式

我们前面说过,android通过Activity栈来管理启动的Activity,当启动一个Activity时就把它压入到栈顶,其实这只是android的一种默认实现,有时候如果栈中存在相同的Activity,通过设置不同的启动模式,就不一定需要新创建一个Activity了。
Activity有下面四种启动模式:
(1)standard
(2)singleTop
(3)singleTask
(4)singleInstance
通过在AndroidManifest.xml中设置android:lanuchMode,来设置不同的启动方式。
standard
默认的一种启动方式,每次通过Intent打开一个Activity,不管栈中是否已有相同的Activity,都会创建一个新的Activity,并放入到栈顶中。
singleTop
每次通过Intent打开一个启动模式为singleTop的Activity,系统会先判断栈顶中是否有该Activity,如果有,就不会创建新的Acitivity;如果栈顶没有,即使栈中的其他位置上有相同的Activity,系统也会创建一个
新的Activity。和standard模式很相似,只是会对栈顶元素进行判断,解决栈顶多个重复相同的Activity的问题。
当栈顶元素有相同的Activity时,再通过Intent打开同一个Activity不会创建新的对象,但是会调用OnNewIntent(Intent)方法,只有在栈顶有相同的Activity时才会调用这个方法,如果没有相同的,就类似于standard,不会调用OnNewIntent(Intent)方法了。
singleTask
如果栈中已经有该Activity的实例了,不管它在栈中什么位置,都会重用该Activity实例,如果它没在栈顶,此时,就会先把它上面的Activity实例先销毁掉,直到它成为栈顶元素。如果栈中不存在该实例,则会创建一个新的Activity实例放入栈中。当重用Activity时,也会调用OnNewIntent(Intent)方法.
singleInstance
系统会创建出一个新的栈,在这个新的栈中创建该Activity实例,并让多个应用共享改栈中的该Activity实例。一旦改模式的Activity的实例存在于某个栈中,任何应用再激活改Activity时都会重用该栈中的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity都会进入同一个应用中。 比如,我们一个应用中打开了百度地图,然后在另一个应用中,也准备打开百度地图,此时,它会直接进入到刚才的地图画面,按返回时返回到自己的界面。

5、完全退出程序的几种方法

方案一:定义Activity栈
根据我们上面讲到的,每个Activity都会放入到栈中管理,那我们也可以仿照这个定义一个类似的Activity栈,每打开一个Activity,就把它放入到我们自定义的Activity中,因此我们写一个Activity管理类来管理这些Activity,代码如下:
  1. /**
  2.  * APP管理类
  3.  *
  4.  */
  5. public class AppManager {
  6.     private static Stack<Activity> activityStack;
  7.     private static AppManager instance;
  8.     private PendingIntent restartIntent;
  9.     private AppManager() {
  10.     }
  11.     /** 
  12.      * 单一实例 
  13.      */
  14.     public static AppManager getAppManager() {
  15.         if (instance == null) {
  16.             instance = new AppManager();
  17.         }
  18.         return instance;
  19.     }
  20.     /** 
  21.      * 添加Activity到堆栈 
  22.      */
  23.     public void addActivity(Activity activity) {
  24.         if (activityStack == null) {
  25.             activityStack = new Stack<Activity>();
  26.         }
  27.         activityStack.add(activity);
  28.     }
  29.     /** 
  30.      * 获取当前Activity(堆栈中最后一个压入的) 
  31.      */
  32.     public Activity currentActivity() {
  33.         Activity activity = activityStack.lastElement();
  34.         return activity;
  35.     }
  36.     /** 
  37.      * 结束当前Activity(堆栈中最后一个压入的) 
  38.      */
  39.     public void finishActivity() {
  40.         Activity activity = activityStack.lastElement();
  41.         finishActivity(activity);
  42.     }
  43.     /** 
  44.      * 结束指定的Activity 
  45.      */
  46.     public void finishActivity(Activity activity) {
  47.         if (activity != null) {
  48.             activityStack.remove(activity);
  49.             activity.finish();
  50.             activity = null;
  51.         }
  52.     }
  53.     /** 
  54.      * 结束指定类名的Activity 
  55.      */
  56.     public void finishActivity(Class<?> cls) {
  57.         for (Activity activity : activityStack) {
  58.             if (activity.getClass().equals(cls)) {
  59.                 finishActivity(activity);
  60.             }
  61.         }
  62.     }
  63.     /** 
  64.      * 结束所有Activity 
  65.      */
  66.     public void finishAllActivity() {
  67.         for (int i = 0, size = activityStack.size(); i < size; i++) {
  68.             if (null != activityStack.get(i)) {
  69.                 activityStack.get(i).finish();
  70.             }
  71.         }
  72.         activityStack.clear();
  73.     }
  74.     /** 
  75.      * 退出应用程序 
  76.      */
  77.     public void exitApp(Context context) {
  78.         try {
  79.             finishAllActivity();
  80.             System.exit(0);
  81.             android.os.Process.killProcess(android.os.Process.myPid());
  82.         } catch (Exception e) {
  83.         }
  84.     }
  85. }
方案二:利用广播的方式

二、Fragment

Android是通过FragmentManager来管理Fragment,每次对Fragment进行添加和移除时需要开启事务,通过事务处理这些相应的操作,然后commit事务。

1、添加、移除Fragment的几种方式

在对Fragment进行管理前,需要开启一个事务,如下:
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction tx = fm.beginTransaction();
FragmentTransaction下管理Fragment的主要方法有add()、remove()、replace()、hide()、show()、detach()、attach()。
添加Fragment方式一:
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction tx = fm.beginTransaction();
        tx.add(R.id.content, new Fragment1(),”Fragment1″);
        tx.commit();
这里是直接通过add将Fragment1绑定到id为content的View上。
添加Fragment方式二:
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction tx = fm.beginTransaction();
        tx.replace(R.id.content, new Fragment1(),”Fragment1″);
        tx.commit();
这里使用replace来添加Fragment,replace的作用相当于是remove() + add() 组合后的作用。即使用replace会先移除掉当前id为content上的Fragment,这个被移除掉的Fragment就会被销毁掉(如果当前事务),然后通过add再把新的Fragment添加到View上。
(1)使用replace方式,相当于在对应id为content的FrameLayout上只有一层,那就是上面的Fragment1,通过replace这种方式,会把Fragment的生命周期再走一遍,如果我们的Fragment中有获取数据的操作的话,会频繁的去拉取数据;使用replace,Fragment绑定的视图一定会销毁,Fragment实例不一定会销毁,主要看有没有添加到回退栈。
(2)而通过add方式,我们可以在id为content的FrameLayout上添加多层,也即可以通过多次add来添加多个Fragment到FrameLayout上。这个时候,我们就可以配合hide()、show()方法来不断切换不同的Fragment。在我们通过add方式添加了Fragment到FrameLayout 的View上之后,通过hide()、show()来切换Fragment还有一个优势就是,当一个Fragment重新show展示出来的时候,它原来的数据还保留在该Fragment上,也就是说hide并不会销毁Fragment,只是单纯的隐藏了而已。
推荐方式:
因此,推荐使用add、hide、show的方式管理Fragment。但是这种方式一些情况下也会有一个缺陷就是:可能会造成Fragment重叠
比如底部有四个Tab:tab1,tab2,tab3,tab4,对应的Fragment引用为tab1Fragment,tab2Fragment,tab3Fragment,tab4Fragment,首先通过add将这四个Fragment添加到FragmentManager后,通过hide和show切换不同TAB都可以处于正常情况,当我们切换到tab1时,假设tab1上的Fragment为 tab1Fragment变量指向的(即tab1Fragment=  new Fragment()),这个时候我们按下HOME键,如果长时间没有回到应用或者内存不足了,系统回收了该引用,此时tab1Fragment= null;但是,tab1的Fragment实例其实还是存在与内存中,只是引用被销毁了,这个时候,我们切换到tab2,这个步骤中,我们会把tab1的fragment隐藏掉,然后显示tab2,即如下操作:
        tx.hide(tab1Fragment);
        tx.show(tab2Fragment);
        tx.commit();
但是,因为此时 tab1Fragment = null,引用变量为空,hide操作无法实现隐藏功能,但是又由于tab1上的Fragment实例还处于内存中,因此此时会造成tab2与tab1重叠的现象。再切换到tab1时,因为tab1Fragment = null,此时会再去创建一个新的Fragment,放入到tab1上,导致原来的tab1上的Fragment一直存在与内存中导致重叠,直至它被回收。
造成上述问题的原因还是因为我们无法找到那个实例对象Fragment,因为引用tab1Fragment已经为空了。这个时候,我们在add的时候可以给Fragment绑定一个tag,用它来标识该Fragment,如果引用为空了,再通过tag来找到该Fragment。如下:
  1. //在添加Fragment时
  2.         FragmentManager fm = getSupportFragmentManager();
  3.         FragmentTransaction tx = fm.beginTransaction();
  4.         tab1Fragment = new Fragment1();
  5.         tx.add(R.id.content, tab1Fragment,“fragment1”);
  6.          tx.commit();
  7.         //在使用时,比如切换到tab2时
  8.         if(tab1Fragment != null){
  9.             tx.hide(tab1Fragment);
  10.             tx.show(tab2Fragment);
  11.             tx.commit();
  12.         }else{
  13.             tab1Fragment = (Fragment1) fm.findFragmentByTag(“fragment1”);
  14.             tx.hide(tab1Fragment);
  15.             tx.show(tab2Fragment);
  16.             tx.commit();
  17.         }
关于上面的缺陷实例,具体可以看这篇文章:http://blog.csdn.net/shimiso/article/details/44677007

2、回退栈

当我们在一个Activity中有多个Fragment进行切换时,我们按下返回键时,会直接退出这个Activity,如果我们想在按下返回键时,退回到上一个显示的Fragment,而不是直接返回,我们就需要使用到回退栈了,类似于系统为每个应用创建的Activity栈一样,每个Activity也维护着一个事务回退栈,在我们通过事务对Fragment进行操作的时候,如果将这个事务添加到回退栈了,这个Fragment的实例就不会被销毁。按返回键时就可以回到这里。如下:
在MainActivity中,
  1. public class MainActivity extends Activity  {
  2.     @Override
  3.     protected void onCreate(Bundle savedInstanceState)  {
  4.         super.onCreate(savedInstanceState);
  5.         setContentView(R.layout.activity_main);
  6.         FragmentManager fm = getSupportFragmentManager();
  7.         FragmentTransaction tx = fm.beginTransaction();
  8.         tx.add(R.id.content, new Fragment1(),“fragment1”);
  9.         tx.commit();
  10.     }
  11. }
进入Activity时初始化加载第一个Fragment1,这里我们并没有把它加入到回退栈中,这是因为当目前显示的是Fragment1时,按下返回键我们就是需要直接退出,因为这是最初的那个Fragment,如果我们加入到了回退栈,那按下返回键后将是一片空白,再按一次返回键后才会退出这个Activity,这是因为第一次按下返回键时,相当于是将Fragment1从栈中弹出,此时被销毁了,Activity的FrameLayout也就没有了Fragment依附,因此一片空白。
在Fragment1中,有了一个按钮,点击后打开第二个Fragment2,
  1. public class Fragment1 extends Fragment implements OnClickListener  {
  2.           private Button mBtn;
  3.         @Override
  4.         public View onCreateView(LayoutInflater inflater, ViewGroup container,
  5.                 Bundle savedInstanceState)  {
  6.             View view = inflater.inflate(R.layout.fragment_one, container, false);
  7.             mBtn = (Button) view.findViewById(R.id.id_fragment_one_btn);
  8.             mBtn.setOnClickListener(this);
  9.             return view;
  10.         }
  11.         @Override
  12.         public void onClick(View v)  {
  13.             Fragment2 fTwo = new Fragment2();
  14.             FragmentManager fm = getFragmentManager();
  15.             FragmentTransaction tx = fm.beginTransaction();
  16.             tx.replace(R.id.content, fTwo, “fragment2”);
  17.             tx.addToBackStack(null);  //将当前事务添加到回退栈
  18.             tx.commit();
  19.         }
  20.     }
在Fragment2中,
  1. public class Fragment2 extends Fragment implements OnClickListener {
  2.         private Button mBtn ;
  3.         @Override
  4.         public View onCreateView(LayoutInflater inflater, ViewGroup container,
  5.                 Bundle savedInstanceState)  {
  6.             View view = inflater.inflate(R.layout.fragment_two, container, false);
  7.             mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);
  8.             mBtn.setOnClickListener(this);
  9.             return view ;
  10.         }
  11.         @Override
  12.         public void onClick(View v)  {  //打开Fragment3
  13.             Fragment3 fThree = new Fragment3();
  14.             FragmentManager fm = getFragmentManager();
  15.             FragmentTransaction tx = fm.beginTransaction();
  16.             tx.hide(this);  //隐藏当前显示的Fragment2
  17.             tx.add(R.id.content , fThree, “fragment3”);  //添加Fragment3
  18.             tx.addToBackStack(null);  //将当前事务添加到回退栈
  19.             tx.commit();
  20.         }
  21.     }
在Fragment3中我们只是打印了一些消息,就不再写了。
当当前显示到Fragment3时,我们按下返回键,将会显示出Fragment2出来,继续按下返回键,显示出Fragment1出来,再按下后,直接退出Activity。
因为我们在Fragment1和Fragment2中,在事务提交之前,即tx.commit()之前,我们把当前的事务(用新的Fragment替换当前显示Fragment或者hide当前Fragment)加入到了回退栈,即tx.addToBackStack(null),点击返回键后,就从回退栈中退出栈顶元素,即上一个加入的事务。
上面我们使用了前面介绍的两种添加Fragment的方式,即replace方式和hide()、add()方式,replace方式,Fragment绑定的视图一定会销毁,如果该事务加入到了回退栈,Fragment实例就不会被销毁,只是视图销毁了;而hide()、add()方式隐藏当前Fragment,加入新的Fragment,隐藏的Fragment绑定的视图也不会被销毁。
这里的视图销不销毁,指的是我们的数据有没有被保存下来,如果视图被销毁了,说明重新回到这个Fragment后,会重新进入onCreate及之后的周期方法区创建一个新的视图,这个时候数据肯定就不在了;
如果视图没有被销毁,在重新回到这个Fragment时,原来的输入数据还在,没有丢失。
当然,在Fragment里面也有onSaveInstanceState(Bundle)方法,可以通过这个来保存数据,然后再onCreate或其他方法里面获取到数据来解决数据丢失的问题。

3、与Activity通信

因为Fragment依附于Activity,Activity与Fragment通信,可以有以下几种办法:

(1)如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法

(2)如果Activity中没有保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getSupportFragmentManager().findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作。

(3)在Fragment中可以通过getActivity得到当前绑定的Activity的实例,然后进行操作。

转载:http://blog.csdn.net/shakespeare001/article/details/51450818

[转载]重新设计一款Android App,我会怎么做?

开发工具的选择

开发工具我将选用 Android Studio,它是Google官方指定的Android开发工具,目前是1.2.2稳定版,1.3的预览版也已经发布了。 Android Studio的优点就不需多说了,GitHub上大部分的Android开源库也都已迁移到Android Studio上来,在未提供 jar文件时,使用Android Studio可以极为方便地集成开源库。最为重要的是Google已宣布将在年底前停止对 Eclipse Android开发工具的一切支持(Google Ends Support for Android Eclipse Tools),因此请早日转移到Android Studio上来。

App设计风格

这一点对于一个开发者来说,貌似没有决定权,最终的决定权在产品部门手里。尽管这样,我还是会尽力说服产品部门将App设计成Material Design风格。这一点说多了都是泪啊,作为一个Android开发者,却整天开发着iOS风格的App,相信很多公司都这样,为了节省成本和时间,Android和iOS共用一套UI。举一个最常见的例子,Android App中每个页面TitleBar的左上角放一个返回按钮,这在iOS里是必须的,但Android有返回键啊,这样设计对于 Android完全是多此一举。真心希望产品设计者尊重每种操作系统的风格及使用习惯,不要再设计不伦不类的产品。Material Design正好提供了一种这样的规范,自MD规范发布以来,其优雅的设计和清新的风格已吸引了大批设计者和开发者,如今MD设计不止在Android上(已有官方类库支持 MD风格),甚至在CSS、HTML、JavaScript网页设计上都越来越火。因此,对于App的设计风格,Material Design当仁不让,也许你曾经错过了Android Design,请不要再错过Material Design。

一些相关的链接:

Material Design官网

Material Design配色模板

MD一个设计案例网站

MD风格的Andorid抽屉源码:Android-MaterialDesign-NavigationDrawer

MD风格的一个App源码(有妹子哦):Android-MaterialDesign-DBMZ

版本支持

对于Android要支持的最低版本,可以参考各个版本的市场占有率,其实最靠谱的是根据自家App的统计数据来决定,目前我们的App最低支持2.2。以个人观点认为,虽然2.x的版本仍然有一部分用户,但其实手机更新换代特别快,为了更好的用户体验,也为了应用更新的API(很多第三方库也都有版本要求),应该提高最低支持的版本,大概3.0 为宜,即API Level为11。

App框架设计

相信大家都有体会,随着功能模块的增加,App越来越大,如果没有良好的架构设计,则代码将会变得臃肿且不易维护,各功能模块的耦合度会越来越高。因此可以把App模块化,将一个完整的App划分成几个相对独立的模块,这样即可以降低模块间的耦合也利于复用。

1.网络模块

已经很少有单机版的App了吧,大部分都需要联网,从服务器请求数据,因此网络模模块必不可少。GitHub上的开源网络框架也特别多,个人认为可以使用开源框架,目前我会选okHttp或者Volley,也许以后会有更好的网络框架出现。注意如果使用开源框架,则必须要阅读其源码,必须能够驾驭它,这样就不至于当bug出现时束手无策。当然还可以自己写网络模块,目前我们的App网络模块就完全是自己写的,这样的好处是自己熟悉所写的代码,当有bug时可以迅速定位问题,同时注意处理一些联网过程中的细节,如:

(1)对HTTPS的支持、HTTPS证书的验证(目前很多做法都是默认允许所有HTTPS证书的,其实这样做是不安全的,应当真正地做证书校验)

(2)支持Wap方式上网,移动、联通、电信代理的设置

(3)支持重定向、数据压缩传输等

(4)其他值得注意的问题

自己写网络框架可以完美地处理这些细节,但时间成本比较大。如果使用开源框架,一般都没有处理这些细节,因此我们可以在第三方框架上做些修改,这样时间成本将会节省很多。

2.图片管理模块

图片也是App中不可少的元素,而且图片是占用内存的大户,因此图片管理框架特别重要,不好的图片框架容易引起内存泄露甚至导致崩溃。当然可以自己实现图片框架(目前我们也是这样做的),实现图片的下载、解码、缓存等关键环节。个人建议可以采用一些比较好的图片库,也许会比我们自己管理图片更完善和高效。我会推荐如下几个图片管理库:

(1)Glide,Google的一些官方App,如Google photos都使用了,还要解释更多吗?

(2)Fresco,FaceBook的开源库,功能超级强大,支持WebP、Gif、JPEG渐进显示,关键是其对图片内存的设计思想,使得图片内存开销大大减少。

(3)Android-Universal-Image-Loader,在出现上述图片库之前,貌似这个最火吧,之前个人的App中也用了它。

(4)Picasso,Square的开源库,据说Glide就是参考Picasso设计的。

3.本地数据库模块

也许你的App需要用到本地数据库,那么建议你采用流行的ORM框架,如ActiveAndroidgreenDAO,使用第三方库会大大方便你对sqlite的操作,个人认为在使用中我们需要注意数据库升级以及多线程并发操作数据库的问题。

4.文件管理模块

一个App,肯定会涉及到一些文件,如配置文件、图片、视频、音频、SharedPreferences文件等。我们可以提供一个全局的文件管理模块,负责文件的增、删、改、查等操作。另外还需支持文件压缩,文件的上传与下载操作,对于下载需要支持多线程并发下载、断点续传等功能。

5.组件内、组件间通信机制

对于一个App,组件通信必不可少,通信类型可以分为点对点和点对面的的通信,点对点即只有唯一的接收者可以响应消息,点对面则类似于消息广播,即所有注册过的都可以响应消息。在Android 中,通常使用消息机制来实现,但消息机制的耦合度比较高。目前也有一些通信框架,如EventBusOtto等事件总线框架,这些框架可以极大地降低组件间的耦合,但无法完美地实现点对点通信,因此建议消息机制和事件总线机制结合使用。

6.数据处理框架

其实还应该有一个数据处理框架,当发出数据请求后(走子线程),经网络模块返回数据(一般为JSON格式),JSON数据一般不能直接交给View层使用,需要解析成对应的Model,同时如有需要,还要缓存数据,因此这些流程可以抽象成一个数据处理的框架。这个框架可以认为接受数据请求的url,并将数据Model返回给Activity或 Fragment。对于JSON数据解析,建议使用fastjson,速度快且稳定,缺省值也比较完善。

7.线程调度模块

其实Android中有很多操作,如请求数据、下载图片、清除缓存等都是需要在子线程中执行的,往往很多时候都是直接起一个Thread来做了,这样做就会很乱而且线程多了将难以管理。因此可以抽象出一个线程调度模块,它维护一个线程池,如果有需要线程的话就通过线程调度模块取线程来做,这样就方便统一管理。当然第三方库中的线程操作我们将无法归到线程调度模块来管理,但其他涉及到线程的操作都应该来统一处理。

8.业务层

业务层大概就是四大组件、Fragment、View了,建议尽可能地使用原生组件,少用自定义组件,因为原生组件性能是最好的。另外建议使用MVC模式就好,只要设计管理好自己的逻辑,至于MVP、MVVM等模式个人认为都有缺陷,总之寻求一个折中吧,有得必有失。

9.APK动态加载机制

随着App的增大,功能的扩展,很多App已经采用了APK动态加载的机制,也可以叫做插件化。由于本人没有在实际的App中应用过,所以不便发表过多评论。但这种机制个人认为很有前途,这种机制将利于App的解耦、功能扩展和局部升级。具体可以参考一个商用的解决方案:ApkPlug-移动应用模块化解决方案和一个开源的APK动态加载框架

10.App的安全性考虑

Android App的安全问题很少有人重视,但这的确是一个很严重的问题,一些好的App经常被人破解。建议将一些核心算法等写成.so库,重要的逻辑放在服务器端,数据请求采用加密等,另外打包APK时至少要混淆代码,还可以采用APK加壳机制,总之这类的防范措施永远不嫌多。

一口气漫无逻辑地写了这么多,可能会有遗漏的内容,后续会补充完善。我想如果按照上述原则,至少可以开发出一款不错的App。

转载:http://blog.csdn.net/ahence/article/details/47154419

[转载]Android中startService的使用及Service生命周期

Android中有两种主要方式使用Service,通过调用Context的startService方法或调用Context的bindService方法,本文只探讨startService的使用,不涉及任何bindService方法调用的情况。

当我们通过调用了Context的startService方法后,我们便启动了Service,通过startService方法启动的Service会一直无限期地运行下去,只有在外部调用Context的stopService或Service内部调用Service的stopSelf方法时,该Service才会停止运行并销毁。

要想使用Service,首先我们要继承自Service,然后重写如下方法:

onCreate, onStartCommand, onBind 和 onDestroy。

这几个方法都是回调方法,都是由Android操作系统在合适的时机调用的,并且需要注意的是这几个回调方法都是在主线程中被调用的。

onCreate: 执行startService方法时,如果Service没有运行的时候会创建该Service并执行Service的onCreate回调方法;如果Service已经处于运行中,那么执行startService方法不会执行Service的onCreate方法。也就是说如果多次执行了Context的startService方法启动Service,Service方法的onCreate方法只会在第一次创建Service的时候调用一次,以后均不会再次调用。我们可以在onCreate方法中完成一些Service初始化相关的操作。

onStartCommand: 在执行了startService方法之后,有可能会调用Service的onCreate方法,在这之后一定会执行Service的onStartCommand回调方法。也就是说,如果多次执行了Context的startService方法,那么Service的onStartCommand方法也会相应的多次调用。onStartCommand方法很重要,我们在该方法中根据传入的Intent参数进行实际的操作,比如会在此处创建一个线程用于下载数据或播放音乐等。

onBind: Service中的onBind方法是抽象方法,所以Service类本身就是抽象类,也就是onBind方法是必须重写的,即使我们用不到。在通过startService使用Service时,我们在重写onBind方法时,只需要将其返回null即可。onBind方法主要是用于给bindService方法调用Service时才会使用到。

onDestroy: 通过startService方法启动的Service会无限期运行,只有当调用了Context的stopService或在Service内部调用stopSelf方法时,Service才会停止运行并销毁,在销毁的时候会执行Service回调函数。

我们为了探究通过startService方法启动的Service的生命周期以验证上面对各个回调函数方法的描述,写了如下的一个测试案例。
首先创建一个服务类TestService,该类继承自Service,代码如下:

package com.ispring.startservicedemo;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class TestService extends Service {

    @Override
    public void onCreate() {
        Log.i("DemoLog","TestService -> onCreate, Thread ID: " + Thread.currentThread().getId());
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("DemoLog", "TestService -> onStartCommand, startId: " + startId + ", Thread ID: " + Thread.currentThread().getId());
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i("DemoLog", "TestService -> onBind, Thread ID: " + Thread.currentThread().getId());
        return null;
    }

    @Override
    public void onDestroy() {
        Log.i("DemoLog", "TestService -> onDestroy, Thread ID: " + Thread.currentThread().getId());
        super.onDestroy();
    }
}

我们在TestService的各个回调方法中只是简单打印出了相应的信息,并没有做很多复杂的处理操作。

然后我们在Activity中调用该Serivce,Activity中相应的代码如下:

package com.ispring.startservicedemo;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity {

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

        Log.i("DemoLog", "Thread ID: " + Thread.currentThread().getId());

        Log.i("DemoLog", "before test startService");

        //连续启动Service
        Intent intent1 = new Intent(this, TestService.class);
        startService(intent1);
        Intent intent2 = new Intent(this, TestService.class);
        startService(intent2);
        Intent intent3 = new Intent(this, TestService.class);
        startService(intent3);

        //停止Service
        Intent intent4 = new Intent(this, TestService.class);
        stopService(intent4);

        //再次启动Service
        Intent intent5 = new Intent(this, TestService.class);
        startService(intent5);

        Log.i("DemoLog", "after test startService");
    }
}

我们在Activity中,首先连续三次调用了Activity的startService方法以启动Service,然后调用Activity的stopService方法停止Service,然后又通过调用Activity的startService方法启动Service。

运行程序的输出结果如下:

这里写图片描述

我们分析一下上面的输出结果,首先打印出了主线程的ID是1, 然后我们发现后面所有在回调函数中打印出的执行线程的ID也就是1,这就说明了Service中的各个回调方法是运行在主线程中的。其次我们可以发现在我们连续调用了三次startService方法之后,只触发了一次onCreate回调方法,触发了三次onStartCommand方法,在onStartCommand中我们可以读取到通过startService方法传入的Intent对象,并且这三次的startId都不同,分别是1,2,3,每次调用startService都会自动分配一个startId,startId可以用来区分不同的startService的调用,一般情况下startId都是从1开始计数,以后每次调用startService之后startId自动加一递增。

之后我们又调用了Activity的stopService(intent4)方法用于停止Service,通过输出结果我们发现Service执行了onDestroy方法,一般情况下我们可以在onDestroy方法中执行一些资源释放的操作。执行完onDestroy之后该Service的实例就销毁了。虽然我们之前调用了三次startService方法,但是只要调用一次stopService就可以让运行中的Service停止运行并销毁。

最后我们再次通过startService(intent5)启动Service时,通过输出结果我们发现再次执行了Service的onCreate方法,这说明Service在通过stopService销毁之后重新创建了,并随之再次调用onStartCommand回调方法,并且startId再次从1开始计数。

我们用一张图来概括一下通过startService启动的Service的生命周期:

这里写图片描述

当Android面临内存匮乏的时候,可能会销毁掉你当前运行的Service,然后待内存充足的时候可以重新创建Service,Service被Android系统强制销毁并再次重建的行为依赖于Service中onStartCommand方法的返回值。我们常用的返回值有三种值,START_NOT_STICKY、START_STICKY和START_REDELIVER_INTENT,这三个值都是Service中的静态常量。

START_NOT_STICKY: 如果返回START_NOT_STICKY,表示当Service运行的进程被Android系统强制杀掉之后,不会重新创建该Service,当然如果在其被杀掉之后一段时间又调用了startService,那么该Service又将被实例化。那什么情境下返回该值比较恰当呢?如果我们某个Service执行的工作被中断几次无关紧要或者对Android内存紧张的情况下需要被杀掉且不会立即重新创建这种行为也可接受,那么我们便可将 onStartCommand的返回值设置为START_NOT_STICKY。举个例子,某个Service需要定时从服务器获取最新数据:通过一个定时器每隔指定的N分钟让定时器启动Service去获取服务端的最新数据。当执行到Service的onStartCommand时,在该方法内再规划一个N分钟后的定时器用于再次启动该Service并开辟一个新的线程去执行网络操作。假设Service在从服务器获取最新数据的过程中被Android系统强制杀掉,Service不会再重新创建,这也没关系,因为再过N分钟定时器就会再次启动该Service并重新获取数据。

START_STICKY: 如果返回START_STICKY,表示Service运行的进程被Android系统强制杀掉之后,Android系统会将该Service依然设置为started状态(即运行状态),但是不再保存onStartCommand方法传入的intent对象,然后Android系统会尝试再次重新创建该Service,并执行onStartCommand回调方法,但是onStartCommand回调方法的Intent参数为null,也就是onStartCommand方法虽然会执行但是获取不到intent信息。如果你的Service可以在任意时刻运行或结束都没什么问题,而且不需要intent信息,那么就可以在onStartCommand方法中返回START_STICKY,比如一个用来播放背景音乐功能的Service就适合返回该值。

START_REDELIVER_INTENT: 如果返回START_REDELIVER_INTENT,表示Service运行的进程被Android系统强制杀掉之后,与返回START_STICKY的情况类似,Android系统会将再次重新创建该Service,并执行onStartCommand回调方法,但是不同的是,Android系统会再次将Service在被杀掉之前最后一次传入onStartCommand方法中的Intent再次保留下来并再次传入到重新创建后的Service的onStartCommand方法中,这样我们就能读取到intent参数。只要返回START_REDELIVER_INTENT,那么onStartCommand重的intent一定不是null。如果我们的Service需要依赖具体的Intent才能运行(需要从Intent中读取相关数据信息等),并且在强制销毁后有必要重新创建运行,那么这样的Service就适合返回START_REDELIVER_INTENT。

转载:http://www.codeceo.com/article/android-startservice-service.html

[转载]自学Android资料大全

转自一个大牛:

http://bbs.jiandaima.com/thread-306-1-1.html

看到很多人提问非科班该如何学习编程,其实科班也基本靠自学。有句话叫“师傅领进门修行靠个人”,再厉害的老师 能教你的东西都是很有限的,真正的修行还是要靠自己。我本科是学数学的,虽然研究生是计算机专业,但研究生往往是做研究工作,并不会接触编程这么基本的东 西,关于编程相关我都是靠自学。对于Android这一块,是参加工作还开始接触,开始自己学习的。

学习级别,很多人都往往划分成入门、初级、中间..骨灰级等。这里就简单地划分为两级:基础篇和进阶篇。另外,本文涉及到的所有书籍都是在学习过程中所读过的比较经典的一些书籍,才推荐给大家。

一、基础篇

看书的姿态:学习过程往往大家都需要看书,网上一搜,往往会有一大推的书推荐给大家去阅读,面对这么多书,该如何选择,如何阅读的呢,对于同一个层级的书籍选择一本精读,其余的粗读、略读即可,大同小异,对于精读的书籍需要反复的阅读。

1.1 Java篇

Java是Android的基础,建议初学者一定要先学习Java基本知识,进而再学习Android,循序渐进,切莫心急,只有扎实的基础才能建造牢固的上层建筑。

Java书籍

  • Thinking in Java》: 中文版《Java编程思想 》,这是一本非常经典的Java书籍,很多人都说这个书不适合初学者,我记得自己当初看的第一本Java书便是这本书。看完第一遍对Java有了整体的理 解,但很多细节没有完全理解,查了资源又看了第二遍,对Java有了更深地理解。再后来一段时间后,能力也有所提升,再拿起这本书又看了第三遍,发现对面 向对象有了更深一步的理解,这本书就是适合反复的阅读。
  • Effective Java中文版》:Java进阶书,这本书采用“条目”的方式来展开的,总提出了78条Java具体的建议,对Java平台精妙之处的独到见解,还提供优秀的代码范例。作为Java进阶之书,对Java水平的提升大有裨益。
  • Java并发编程实战(Java concurrency in Practice)》:中文版《Java并发编程实战》,本书采用循序渐进的讲解方式,从并发编程的基本理论讲起,再讲述了结构化并发应用,性能与测试,最后将显式锁、原子变量、非阻塞算法这些高级主题。对于Java并发这一块算得上是一本很棒的书。
  • Java性能优化权威指南(Java Performance)》:中文版《Java性能优化权威指南》,Java之父James Gosling推荐的一本Java应用性能优化的经典之作,包含算法结构、内存、I/O、磁盘使用方式,内容通俗易懂,还介绍了大量的监控和测量工具。关 于优化都是属于较深的领域,对Java有一定基础后,很有必要了解看看。

Java虚拟机,这是作为进阶Java高手必需有所了解:

本文的重点是讲如何学习Android,所以姑且把Java基础与进阶的书都放到Android学习的基础篇里。作为Android开发者来说,完 全没有必要一开始都对Java理解得那么深,只有要看一两本Java基本书,掌握Java面向对象的思想的核心要义即万物皆为对象,掌握Java基本语 法,基本就可以开启Android的学习之路。在后续对Android也有一定理解后,再慢慢不断提升Java和Android水平。

有朋友私信我觉着这个java书难度有点高,可能是本人在看Java书籍之前,还看过些许C和C++的入门书的缘故,所以看的第一本书《Java编程思想》。如果你真的是零基础,第一次接触编程,想以Java作为自己的入门语言,那么你可以先看看《Java语言程序设计》(基础篇) 或者《Java从入门到精通》,作为初学者险掌握Java基本语法,平时遇到不熟悉的方法,多查看API文档即可,慢慢地就熟悉了。

1.2 Android基础篇

有了一定的Java基础(不需要精通Java),就可以开始入门Android。建议初学Android者,一定要先搭建自己的开发环境,先准备jdk和Android Studio环境。再看书的过程,一边看知识点一边写示例程序,一来加深印象,二来提高动手能力。

  • 疯狂Android讲义》:作者李刚,这是我看过的第一个Android书籍,目前有第三版了,我当时看的是第二版基于Android 4.2,书中有大量的实例,记得当时每看完一个实例就跟着敲了一遍,大概花了一周时间把这本书看完并把大部分的实例代码都亲手敲了一遍。
  • 第一行代码》:作者郭霖,网上有不少人都推荐这本书作为Android入门书,但我当时没有看过。这是图灵系列图书,前段时间图灵的编辑看 到我的博客gityuan.com,于是联系到我问是否有兴趣出书,便提到郭霖的《第一行代码》也是他们出版社推出的,然后就给我邮寄了一本。我大概扫了 一扫这本书,内容的确比较基础,作者文笔不错,书中还穿插了不少打怪涨经验升级的片段,比较风趣,初学者可以看看。

Android的基本书籍,只需一两本即可,没有必要看太多基础书籍,不同能力就该有不同的追求,这里就不再介绍其他基础书籍。 另外,Android开发过程中总是需要各种开发环境、工具的下载,再这里推荐一个不错的网站 AndroidDevTools.cn,收集整理了 Android开发、设计等相关的各种工具大集合,非常全面,而且速度也不错哦,最重要的不用翻墙就可下载到最新的工具。

有朋友好奇私信我是否即将要出书了,目前没有相关计划,能力尚不及很多前辈,还需加深内功修为,自己的所学所悟写成博客,还请大家多多指点!

1.3 Android一手资料

何为Android一手资料?那就是Google官方给出的资料,这里往往是英文版的,营养价值极高。其实你只 要英文还凑合+翻墙工具,强烈建议你直接看Android官网的资料,千万别被英语所吓倒,因为很多专业名称,大家一看就明白比如 Activity/Service等这些代码名称本身就是英语,剩下地都就非常基础语法,不懂可以随时翻译,我一般都是用Chrome浏览器+Google翻译插件,哪里不会点哪里,妈妈再也不用担心我的英语了。

言归正传,如果你能看完并理解下列的内容,那么你完全可以没有必要再看前面介绍的书籍,并且对于Android已有相当熟悉了。

1.4 Android资源整理

到这里,那么你已经具备开发App的本领。平时需要自己动手多写写App,另外就是看看别人优秀的App是如何写的,下面列举一些开源库、工具以及App:

当然还有很多优秀的博客和网站值得推荐… //TODO

二、进阶篇

作为程序员,不去阅读源码,仅仅看API文档,只是浮于表象,这是远远不够的。.真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读Andoid系统源码,也包括阅读各种优秀的开源库。

2.1 阅读源码的重要性

借用Linux之父Linus Torvalds的一句名言:Read the fucking source code。不管是阅读Andoid系统源码还是优秀的开源框架,对能力那都会有一个巨大的提升;首先,能学习到优秀的代码风格和设计思想;能真正做到“知其然,还需知其所以然”;能指导自己更加灵活的使用API,能更加快速地找到系统bug的根源。

2.2 阅读源码的准备

  • Java基础:上层framework以及App层都是采用Java语法;
  • C/C++基础:Android的jni/native层代码采用C++,Linux 采用C;
  • Linux:Android内核基于Linux的,了解Linux相关知识对深入掌握Android还是很有必要。
  • Git:Android源码采用git和repo进行管理;
  • Make:Android源码采用Make系统编译,源码系统中会看到很多Android.mk之类的文件;
  • Source Insight:这绝对是看源码的神器;可以在Java、C++、C代码之间无缝衔接;
  • Eclipse:熟悉常用快捷键,工欲善其事必先利其器;虽然Source Insight很方便,但由于对Eclipse的熟悉感,对于framework Java层面的代码,我还是更习惯用Eclipse来看,对于Native代码以及linux代码则采用Source Insight来看;
  • Android Studio:这是Google官方支持的App开发环境,关于Android Studiod使用教程;
  • Google Drawings:这是画图工具,Gityuan博客中的文章都是采用Google Drawing完成,比如Binder开篇文中的图。
  • StarUML:这是类图,Gityuan博客文章的类图和流程图都是采用StarUML完成,比如理解Android进程创建流程文中时序图。

2.3 阅读源码的姿态

阅读源码绝不是从源码工程按顺序一个个的文件,从首行看到尾行。正确而高效地阅读源码的姿态应该是以某一个主线为起点,从上层往底层,不断地追溯, 在各个模块、文件、方法之间来回跳转,反复地阅读,理清整个流程的逻辑。同时带着思考去看源码,尝试去揣测作者的用意,去理解代码的精妙之处,去思考代码 可能存在的缺陷,去总结优秀的代码设计思想。下面说说我在阅读Android源码过程常涉及的库。

阅读Android源码:

下面是我以Android开机过程为主线,展开一系列的文章 Android开篇中的一副流程图,在公司内部分享时我曾多次以下图为流程整个Android架构,如下图(无图):

Android系统源码

android.googlesource.com:Google官方源码,国内无法直接访问,需要翻墙,对于一个程序员来说具备翻墙的能力是非常有必要的。Android源码中包含的库非常之多,下面列举我在看Android源码过程中涉及较多,也是比较常看的一些库:

2.4 优秀资源

牛顿曾说过:“如果我看得更远一点的话,是因为我站在巨人的肩膀上”,这句话很具有实用价值,看完前面的介绍,你千万不要一上来就一头扎进源码的世界,小心你会进入二次元世界,处于混沌状态,最后崩溃乃至放弃求知之路,一定要合理利用现有的优秀资源。

Android 系统源码分析

  • Innost的专栏
    • 邓凡平前辈所写博客,条例有序,覆盖了Android系统大部分内容;
    • 深入理解Android》 (卷I,卷II,卷III)
  • 老罗的Android之旅
  • Gityuan源码分析
    • 对于邓凡平和罗升阳两位前辈的博客基于Android 2.x或4.x,目前Android已发展到Android 6.0。不管Android如何变化,其核心思维变化并没有很大,所以两位前辈的博客还是很有值得学习和参考的地方。话又说回来,Android经过了几 个大版本的迭代,无论是从代码结构还是整体逻辑仍有不少变化。故博主计划写一关于Android 6.0源码系列的博文。
    • Gityuan作为Android界新秀,能力尚不及很多前辈,但有一颗乐于分享的心,有一份痴于Android的品质,有一种坚持的态度,已经并一直还在努力奋斗的道路上…

2.5 进阶书籍

前4本书都是关于Linux,如果你不是需要从事Linux相关开发,只想提升对Android整体的理解,那么只需看一到两本,对Linux的进 程、内存、IO以及驱动有所了解,对CPU调度、进程间通信有所熟悉就基本可以。另外,优秀的书还有很多,这里只介绍/列举我看过的书,目前还在看一些优 秀的书,后续再更新。

三、其他

最后,再说说关于学习编程的番外篇:

  • 好奇心比雄心走得更远:很多人对未来空有满腔的雄心壮志,往往不如对技术要有一份好奇心,一份探索欲,再加上一份执着的人。
  • 要有open的心态:曾经的我也只是把自己的所思所得都放入自己的云笔记,很少整理,这其实不利于技术发展,有空应该多整理自己零散的知识点,觉得不错的点可以拿出来写成博客,那是对能力的又一层提升。另外,在低头做技术的同时,还应该有空抬头看世界,不能闭门造车。
  • 天道酬勤:学历只能代表过去,能力代表现在,潜力代表未来! 你不把自己逼一把,你压根不知道自己有多优秀,只要努力去学习,去挖掘潜力,进而提升自我技术修为,未来不再是梦!共勉之!
  • 解决问题的方式:遇到问题,一定要先尝试自己解决,解决不了再请教他人。这是对自己的一个锻炼,也是对他人的一个尊重,可以有多种途径自行搜索:
    • 百度一下,很多时候还是能有所帮助的,不要过分强调google,完全抛弃百度,毕竟中文看起来比较快;
    • 先中文关键词google一下;再英文关键词google一下;
    • stackoverflow.com知乎等技术问答网站内直接搜索;
    • 查看官方文档;
    • 如果有源码,尝试直接看源码,看能否解决;
  • 有空可以多逛逛github,多看看Google官方文档,多关注社区,定会收获不少;
  • 当然,最最重要的是能静得下心,持之以恒地专研技术。

[转载]很少有人会告诉你的 Android 开发基本常识

本文介绍Android开发过程中的一些基本常识,大多是一些流程、专业术语和解决问题的方法等。

软件开发流程

一个完整的软件开发流程离不开策划、交互、视觉、软件、测试、维护和运营这七个环节,这七个环节并不是孤立的,它们是开发一款成功产品的前提,但每一项也都可以形成一个学科,是一个独立的岗位,随着敏捷开发的流行,以及来到了体验为王的时代,现代软件开发更多的是注重效率和敏捷,而不是循规蹈矩的遵循这些开发流程,比如软件开发的岗位不再仅仅是个技术岗位,它需要去参与前期的设计和评审、可以在视觉和交互方面提出自己的见解,在开发的过程中需要自测程序尽快解决现存问题,运营和维护的过程中也需要软件的帮助。可见现代软件开发对开发者的综合素质(这并不是facebook所讲的全栈工程师)越来越高,自称为码农或者程序猿显然是不合理的,因为这个过程是脑力劳动和体力脑动并存,称呼自己为工程师显得更为合理。

  • 策划:需求收集(通过用户调研、灰度发布、大数据分析、竞品分析、领导拍脑袋等方式获取需求)、需求整理(将需求归类、划分优先级等)、将需求转换成解决方案(输出设计文档);
  • 交互:从心理学(利用人性的弱点)、人性化(心智)、个性化的角度将解决方案转换成可交互的功能和界面(需要输出交互文档),比如加载等待、消息提示、页面布局、页面内和页面间的交互逻辑、页面切换动画等等,这个过程中一般会使用Axure或者PowerPoint来制作交互文档;
  • 视觉:根据交互图,使用PhotoShop来做视觉效果,在Android上的图片格式大多是png和jpg,对于需要屏幕适配,程序又适合做屏幕适配的地方可以使用九图,格式为*.9.png。
  • 软件:根据视觉和交互效果将需求转化为具体的实现,在实现的过程中可能会因为需求、交互或者视觉的变动导致软件实现的变动,因为策划、交互、视觉这每一个环节都可能会有信息失真的现象,或者是由于市场环境的变化、获取信息不够准确、领导拍脑袋等等情况导致软件始终处于被动状态,所以现在会提倡敏捷开发、结对编程、程序设计、同行评审、单元测试来提高程序的灵活性和稳定性;
  • 测试:软件达到可交互的标准后,需要将可交互的程序提供测试,其中灰度发布(用户测试)、自测(开发自测)、SQA(品质保证)都算是测试;
  • 维护和运营:通过测试程序达到稳定标准后,软件就可以上线了,软件上线后,需要去维护,用户反馈的问题要及时解决、用户有疑问要及时解答;根据后台统计信息、抓住可运营的节日、民族文化需要做运营来提高用户使用产品的粘度,让更多的用户知道、使用产品都是运营应该做的。

注:

提问的智慧

大多数工作都是以结果为导向的,特别是软件开发这个职业,绩效考核、KPI这些都是在考核你工作的成果,所以工作更多地是需要你解决问题的能力,至于学习这个事情,还是在工作之外的时间去做吧。对于提高解决问题能力我有两个建议:

  • 学会学习和思考:学习的过程中要广度和深度并存,Android应用开发本身对技术功底的要求不高(因为很多底层的东西都被google、框架、开源代码给封装起来了,多数时候你只需要看ReadMe或者API知道怎么用就可以了),更多地是在你遇到问题的时候知道这个问题能够通过什么方法和方式来解决。书要看,但多逛逛论坛、QQ群、Github、StackOverflow、CSDN博客专栏对自己都是有益的。
  • 学会提问:你身边有很多资源,比如同事、StackOverflow、QQ技术交流群、搜索引擎,当你遇到问题的时候完全可以利用身边的资源来解决遇到的问题,如果一个问题在一个小时之内自己都不能够解决它,我就会通过搜索引擎、Github、QQ技术交流群、同事、StackOverflow(以上排序是按优先级排列的)来解决它。如果你需要好的答案你就需要有好的提问,特别是在QQ群或者论坛,在提问的过程中需要体现出你的思考,能够通过搜索引擎解决的问题坚决不问他人,这是对别人的尊重,在这里推荐几个链接,认真看会对你有莫大的帮助:

如何用好 Google 等搜索引擎?

程序员应该如何提问?

提问的智慧

Smart Questions

解决bug的方法

为了写这一项我专门在知乎上提过一个问题:

你有哪些解决bug的技巧?

在知道如何快速解决bug之前,你需要知道什么是bug。没有完成策划、交互、视觉要求的功能,这不叫bug,这叫功能缺陷;一个功能完成后不能正常使用也不叫bug,因为它根本还没达到可测试的标准。我认为当你的程序达到可测试标准之后发现的问题才叫bug。综合我自己解决bug的经验和知乎上的回答,总结常见的解决bug的方法有(你想要高效解决bug的前提是你能够快速定位到缺陷所在的位置,所以以下方法多数讲的是如何快速定位问题,至于真正解决bug,需要你自己修改程序才行):

  • 断点调试:

以Eclipse为例:

1、打断点:

(1)打断点:

很少有人会告诉你的Android开发基本常识

打断点

(2)清除断点:

很少有人会告诉你的Android开发基本常识

清除断点

2、启动调试模式的两种方式:

(1)通过debug as启动调试程序:右键工程名–>Debug AS –>Android Application –>模拟器或者真机会弹出……watching for the debugger……的提示框,不要点击等待其自动消失 –> 此时已经进入调试模式,操作程序到达打断点的地方。

(2)在程序运行过程中,在DDMS视图下选中要调试的程序,启动调试模式:

很少有人会告诉你的Android开发基本常识

DDMS视图进入调试模式

3、调试:请自行尝试F5、F6、F7、F8这几个调试的快捷键;

4、watch成员变量:在调试的过程中,比如在执行for、while、do while循环、递归、系统回调等程序时可以通过watch来观察成员变量或者方法返回值的变化情况,watch的方法:

很少有人会告诉你的Android开发基本常识

watch

注:更多关于在Eclipse IDE中调试Android程序的知识请参见:Android eclipse中程序调试

打印:

打印调试的方法对于循环、异步加载、递归、JNI等代码段非常有用,特别是在循环中,在循环次数非常大时,通过打断点调试显然是一件费力的事情,这时候打印就显得更“智能”了,我通常会通过下面封装的打印调试类来输出打印信息,这个类可以打印print、log、行号、文件名、StrictMode等信息,当不需要打印信息时,只需要将DEBUG_MODE改为false就可以了:

import android.content.Context;
    import android.os.StrictMode;
    import android.util.Log;
    import android.widget.Toast;

    /**
     * 调试打印类
     * 
     * */
    public class DebugUtils{
        private DebugUtils( ){

        }

        public static void println( String printInfo ){
            if( Debug.DEBUG_MODE && null != printInfo ){
                System.out.println( printInfo );
            }
        }

        public static void print( String printInfo ){
            if( Debug.DEBUG_MODE && null != printInfo ){
                System.out.print( printInfo );
            }
        }

        public static void printLogI( String logInfo ){
            printLogI( TAG, logInfo );
        }

        public static void printLogI( String tag, String logInfo ){
            if( Debug.DEBUG_MODE && null != tag && null != logInfo ){
                Log.i( tag, logInfo );
            }
        }

        public static void printLogE( String logInfo ){
            printLogE( TAG, logInfo );
        }

        public static void printLogE( String tag, String logInfo ){
            if( Debug.DEBUG_MODE && null != tag && null != logInfo ){
                Log.e( tag, logInfo );
            }
        }

        public static void printLogW( String logInfo ){
            printLogW( TAG, logInfo );
        }

        public static void printLogW( String tag, String logInfo ){
            if( Debug.DEBUG_MODE && null != tag && null != logInfo ){
                Log.w( tag, logInfo );
            }
        }

        public static void printLogD( String logInfo ){
            printLogD( TAG, logInfo );
        }

        public static void printLogD( String tag, String logInfo ){
            if( Debug.DEBUG_MODE && null != tag && null != logInfo ){
                Log.d( tag, logInfo );
            }
        }

        public static void printLogV( String logInfo ){
            printLogV( TAG, logInfo );
        }

        public static void printLogV( String tag, String logInfo ){
            if( Debug.DEBUG_MODE && null != tag || null != logInfo ){
                Log.v( tag, logInfo );
            }
        }

        public static void printLogWtf( String logInfo ){
            printLogWtf( TAG, logInfo );
        }

        public static void printLogWtf( String tag, String logInfo ){
            if( Debug.DEBUG_MODE && null != tag && null != logInfo ){
                Log.wtf( tag, logInfo );
            }
        }

        public static void showToast( Context context, String toastInfo ){
            if( null != context && null != toastInfo ){
                Toast.makeText( context, toastInfo, Toast.LENGTH_LONG ).show( );
            }
        }

        public static void showToast( Context context, String toastInfo, int timeLen ){
            if( null != context && null != toastInfo && ( timeLen > 0 ) ){
                Toast.makeText( context, toastInfo, timeLen ).show( );
            }
        }

        public static void printBaseInfo( ){
            if( Debug.DEBUG_MODE ){
                StringBuffer strBuffer = new StringBuffer( );
                StackTraceElement[ ] stackTrace = new Throwable( ).getStackTrace( );

                strBuffer.append( "; class:" ).append( stackTrace[ 1 ].getClassName( ) )
                        .append( "; method:" ).append( stackTrace[ 1 ].getMethodName( ) )
                        .append( "; number:" ).append( stackTrace[ 1 ].getLineNumber( ) )
                        .append( "; fileName:" ).append( stackTrace[ 1 ].getFileName( ) );

                println( strBuffer.toString( ) );
            }
        }

        public static void printFileNameAndLinerNumber( ){
            if( Debug.DEBUG_MODE ){
                StringBuffer strBuffer = new StringBuffer( );
                StackTraceElement[ ] stackTrace = new Throwable( ).getStackTrace( );

                strBuffer.append( "; fileName:" ).append( stackTrace[ 1 ].getFileName( ) )
                        .append( "; number:" ).append( stackTrace[ 1 ].getLineNumber( ) );

                println( strBuffer.toString( ) );
            }
        }

        public static int printLineNumber( ){
            if( Debug.DEBUG_MODE ){
                StringBuffer strBuffer = new StringBuffer( );
                StackTraceElement[ ] stackTrace = new Throwable( ).getStackTrace( );

                strBuffer.append( "; number:" ).append( stackTrace[ 1 ].getLineNumber( ) );

                println( strBuffer.toString( ) );
                return stackTrace[ 1 ].getLineNumber( );
            }else{
                return 0;
            }
        }

        public static void printMethod( ){
            if( Debug.DEBUG_MODE ){
                StringBuffer strBuffer = new StringBuffer( );
                StackTraceElement[ ] stackTrace = new Throwable( ).getStackTrace( );

                strBuffer.append( "; number:" ).append( stackTrace[ 1 ].getMethodName( ) );

                println( strBuffer.toString( ) );
            }
        }

        public static void printFileNameAndLinerNumber( String printInfo ){
            if( null == printInfo || !Debug.DEBUG_MODE ){
                return;
            }
            StringBuffer strBuffer = new StringBuffer( );
            StackTraceElement[ ] stackTrace = new Throwable( ).getStackTrace( );

            strBuffer.append( "; fileName:" ).append( stackTrace[ 1 ].getFileName( ) )
                    .append( "; number:" ).append( stackTrace[ 1 ].getLineNumber( ) ).append( "/n" )
                    .append( ( null != printInfo ) ? printInfo : "" );

            println( strBuffer.toString( ) );
        }

        public static void showStrictMode( ) {
            if (DebugUtils.Debug.DEBUG_MODE) {
                StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                        .detectDiskReads().detectDiskWrites().detectNetwork().penaltyLog().build());
                StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                        .detectLeakedSqlLiteObjects().detectLeakedClosableObjects().penaltyLog().penaltyDeath().build());
            }
        }

        public static void d(String tag, String msg){
            if(DebugUtils.Debug.DEBUG_MODE){
                Log.d(tag, msg);
            }
        }

        public class Debug{
            public static final boolean DEBUG_MODE = true;
        }

        public static final String TAG = "Debug";
    }

目视法:

这适合于code review,但是不太靠谱,因为人的精力毕竟有限,有时候你多敲一个分号,缩进不对都有可能导致程序出现问题,但在代码量较少时是一个高效率的方法。

自动化测试:

Android的自动化测试(分白盒测试和黑盒测试)工具有:monkey、Robotium、Appium、云端测试(比如testin),具体用法可参见:

android实用测试方法之Monkey与MonkeyRunner

Robotium

Testin

Appium中文教程

排除法:

调试、打印、目视这三种方法适合于可以复现的问题,对于随机问题(实际上不存在随机问题,只是问题不那么容易复现而已),比如在线程、音频播放、AnsynTask、Timer切换或者结束时刚好做了相应地人为操作导致出现灵异现象。这时候可以通过排除法来排查问题,具体的方法是首先大概定位到出现问题的位置,然后将代码一段一段地注释,观察程序现象,逐步缩小出现问题的范围。

版本管理介绍

在较大的软件开发过程中,可能有多个软件工程师同时开发一个项目的情况,比如有负责读取数据、获取网络数据等API封装的,有负责程序架构的,有负责上层界面实现的,为了能够最终编译一个完成的程序出来,需要将代码整合,这个时候最方便的方法就是使用版本管理工具,固定时间上传(比如每天、没改动一个功能等等),这样能够实时保证服务器上的代码是最完整、最新的,也可以避免由于自然灾害、电脑异常导致本地电脑挂掉损失掉代码的问题。

常见的版本管理工具有SVN和Git,我也使用过CVS,关于版本管理工具的介绍参见:

版本控制

版本控制系统的选择之路

git教程

git简易指南

注:对于windows用户来说,建议使用乌龟壳系列的版本控制客户端,使用github的朋友可以使用github for windows客户端:

tortoisegit

tortoisecvs

tortoisesvn

github for windows

编译

通常我们用Eclipse或者Android Studio开发android程序时,只需要运行程序就可以在模拟器或者机器上运行程序了,但为了保证代码的完整性、能够在服务器上编译,需要通过编译工具将代码编译成apk,常见的编译工具有:antgradle,但这两种编译工具都是需要通过手动敲命令来完成编译功能(当然你也可以自己写脚本来实现编译自动化),jenkins是一个持续集成的工具,通过它可以代码克隆、编译以及程序加密自动化,其实它也是通过批处理来实现的,ant、gradle和jenkins的具体用法自行谷歌,使用起来很简单,目前android studio和github上很多功能都是通过gradle来编译的。

专业术语介绍

以下解释完全是本人的理解,详细解释可自行谷歌。

  • 版本迭代:按照需求优先级,在保证基本功能OK后持续开发和升级,这样能够降低软件开发的风险,并且能够及时解决用户反馈的问题,船小好掉头嘛;
  • 敏捷开发:小步快跑,大概意思就是不要过于注重文档,要注重当面交流,能够在实现时高保真的还原用户的需求场景,并且能够快速地解决用户的需求。
  • 单元测试:白盒测试的一种,对核心方法通过写程序来测试自己的程序,单元测试的目的是让你有意识地降低程序间的耦合,保证每一个方法都是最小单元,但这对于测试程序逻辑是没有帮助,这是我自己的理解。。。
  • 灰度发布:先找一部分用户来使用即将发布的程序(这部分用户可以是随机抽取、制定年龄段、指定地区或者通过某种方式知道他是活跃用户),在测试的过程中给与用户一点好处让用户写用户体验报告、反馈问题等方式来发现程序存在的问题和缺陷;
  • DA统计:也叫后台统计,通过在程序中埋点的方式,在有网络的情况下将用户的操作行为和数据上传到后台,将每个用户的信息都上传回来就叫大数据,通过建模对这些数据分析就叫大数据分析。
  • 开放平台:比如分享到QQ空间、分享到微信、讯飞语音、友盟的后台统计、天气、地图等等都叫做开放平台,它提供了一些开放的接口给开发者,方便开发者使用它的服务,开放平台多数服务都是免费的,但有时候也可能不稳定,比如用的人少它自然就活不下去了,然后就没有然后了。
  • 同行评审:你的同行和你一起看看你的代码,发现是否有问题;
  • 结对编程:在写代码的过程中,有个人坐在你旁边或者你坐在别人旁边,编写边讨论,降低程序出现逻辑和低级错误的概率。

Android开发资源

参见我的另一篇文章:Android开发者网址导航

建议

  • 尽量阅读官方文档,这才是原汁原味、不失真的开发指导;
  • 即使你认为设计程序是浪费时间,你只是喜欢写程序,至少你也得用思维导图理清思路,思维导图对于帮助你理解设计文档、理清思路有很大的帮助;
  • 不要用Intent传递大量的数据,这有可能导致ANR或者报异常;
  • 在退出页面后,系统不一定会及时执行onDestory方法,如果你在onDestory方法里做关闭文件、释放内存的操作可能出现退出程序又立即进入时,由于需要重新初始化这些信息导致代码重入的异常;
  • 在改动JNI后,运行程序之前记得卸载掉已经安装在模拟器或者真机上的该程序,如果直接运行,android不会load最新编译的so,也就不能立即看到修改后的效果;
  • 代码至少每天备份一次,或者是完善一个功能就备份一次,不要堆积之后一次性备份,因为在你的代码出问题需要回溯代码时你需要从服务器上重新取代码,同时也可以避免代码不是最新导致最后和其他人合并时不知道改了哪些地方;
  • 将打印信息封装成一个方法,用一个标志位控制这个这个方法的方法体是否需要执行,这样在由debug版释放到release版本时,不需要傻傻地一行一行地去掉代码,你只需要改变标志位的值就可以了;
  • 对于有返回值的JNI函数,即使你不返回任何值,用NDK编译JNI的时候也不会报错,所以在写JNI代码的时候,一定要仔细检查代码;
  • JNI频繁读写文件操作会影响程序的运行性能,可以考虑一次性在内存中申请一块大内存作为缓存空间,用这种空间换时间的方式可以大大提高程序的运行效率;
  • 不要指望类的finalize方法去处理需要回收和销毁的工作,因为finalize是系统回调的方法,调用时机不可预见,切记;
  • 使用文件流、Cursor时,使用结束后记得一定要关闭,否则可能导致内存泄漏,严重的情况可能引发程序崩溃;
  • 优先使用Google搜索引擎(少用百度),如果不能正常使用Google搜索引擎建议通过代理、VPN、修改hosts文件等方式搭建梯子。这里提供一个免费的谷歌搜索引擎
  • 对于不需要使用硬件加速的activity(没有动画效果、视频播放以及各种多媒体文件的操作都可以关掉硬件加速),在AndroidManifest.xml文件中通过“android:hardwareAccelerated=”false””关掉硬件加速可节省应用内存;
  • 对于需要横竖屏转换的应用,又不想在横竖屏切换的时候重新跑onCreate方法,可以在AndroidManifest.xml文件中对应的Activity标签下调用“android:configChanges=”screenSize|orientation””;
  • 为了减轻应用程序主进程的内存压力,对于耗内存比较多的界面(比如视频播放界面、flash播放界面等),可以在AndroidManifest.xml文件中对应的Activity标签下调用“android:process=”.processname””单开一个进程,但在退出这个界面的时候一定要在该界面的onDestory方法中调用System的kill方法来杀掉该进程;
  • 在res/values/arrays.xml文件中定义的单个数组的元素个数不宜过大,过大会导致加载数据时非常慢,有时候你需要使用数组资源时数据有可能还没加载完成;
  • 一个Activity中最耗费内存的是activity的背景(多数情况如此,特别是对于分辨率很大的机器,一个界面的背景算下来都需要好几兆内存),所以在程序界面较多时,可以考虑将图片转换成静态的drawable,然后多个activity共用这一张背景图;
  • 可以通过为application、activity自定义主题的方式来关掉多点触摸功能,只需要在自定义的主题下添加这两个标签:
    <item name="android:windowEnableSplitTouch">false</item>
      <item name="android:splitMotionEvents">false</item>
  • 很多游戏进入时,播放的片头动画多数是一个视频文件;
  • Android单个dex文件的方法数不能超过65536个,android使用多个dex能否避开65536方法数限制?
  • 使用模拟器genymotion代替android自带模拟器(它需要虚拟机vituralbox的支持,不过官网已经提供了一个集成虚拟机的安装包了,直接下载下来安装即可),可以大大提高使用模拟器的体验(流畅、快),它也可以以插件的形式集成在Eclipse中,这是视频教程
  • 给Application或者activity设置自定义主题时,最好不要设置为全透明,否则在activity按Home键回退到桌面的时候效果很渣;
  • 如果你需要取消toast显示的功能,在一个类中你只需要实例化该类一次(也就是说将Toast定义成一个全局的成员变量),这样你就可以调用mToast.cancel()了,我把它写成了一个静态类:
    public class ToastUtils {
          private ToastUtils( ){
    
          }
    
          public static void showToast( Context context, String toast ){
              if( null == mToast ){
                  mToast = Toast.makeText( context, toast, Toast.LENGTH_LONG );
              }else{
                  mToast.setText( toast );
              }
    
              mToast.show( );
          }
    
          public static void cancel( ){
              if( null != mToast ){
                  mToast.cancel( );
              }
          }
    
          public static Toast mToast = null;
      }
  • 你可以定义一个静态类来实现防止按钮被重复点击导致重复执行一段代码的问题:
    /**
       * 按钮重复点击
       * 
       * */
      public class BtnClickUtils {
          private BtnClickUtils( ){
    
          }            
    
          public static boolean isFastDoubleClick() {
              long time = System.currentTimeMillis();
              long timeD = time - mLastClickTime;
              if ( 0 < timeD && timeD < 1000) {   
                  return true;   
              }
    
              mLastClickTime = time;
    
              return false;   
          }
    
          private static long mLastClickTime = 0;
      }
  • 放在apk的assets或者raw目录下的数据文件最好做加密处理,在需要使用的时候才解密,这样可以避免在apk被他人破解时数据也被破解的问题;
  • 最好不要再activity的onCreate方法里面调用popupwindow的show方法,有可能由于activity没有完全初始化导致程序异常(android.view.WindowManager$BadTokenException: Unable to add window — token null is not valid),如果非要在一进activity就显示popupwindow,建议用handler.post、View.postDelay来处理;
  • 对于自定义View,在构造方法里面是获取不到视图的宽高的(此时获取长宽都为0),需要在onMeasure方法中或者跑了onMeasure方法后才能够获取到视图的宽高,不过你可以通过在构造方法里面强制测量视图的宽高来实现在构造方法里获取视图的宽高信息,具体见MeasureSpec介绍及使用详解
  • 如果你觉得在安装Eclipse后还需要配置android开发环境很麻烦,你可以直接使用ADT Bundle,它是一个懒人套餐,下载下来就可以用了,可以在这里下载。
  • 有时间看看阿里技术嘉年华InfoQ演讲与访谈Google IO视频,可以学习到一些解决问题、做大项目的经验。
  • 当应用中动画比较多,并且动画都是通过图片来切换的时候,可以考虑借用Cocos的精灵表单思想,这样就可以避免图片命名的烦恼。

工具推荐

Android应用开发第三方解决方案

下图为Android应用开发第三方解决方案汇总,有些可以借助第三方平台搞定的就尽量不要自己搞,一是可以节省成本,二是你没人家专业,原文链接:Android应用开发第三方解决方案

很少有人会告诉你的Android开发基本常识

转载:http://www.jianshu.com/p/d6611c8bd45c