标签归档:转载

Android应用程序窗口设计框架介绍

 

Android系统中,一个Activity对应一个应用程序窗口,任何一个Activity的启动都是由AMS服务和应用程序进程相互配合来完成的。AMS服务统一调度系统中所有进程的Activity启动,而每个Activity的启动过程则由其所属进程来完成。AMS服务通过realStartActivityLocked函数来通知应用程序进程启动某个Activity:

frameworksbaseservicesjavacomandroidserveram ActivityStack.java

  1. final boolean realStartActivityLocked(ActivityRecord r,
  2.         ProcessRecord app, boolean andResume, boolean checkConfig)
  3.         throws RemoteException {
  4.     …
  5.     //系统参数发送变化,通知Activity
  6.     if (checkConfig) {
  7.         ①Configuration config = mService.mWindowManager.updateOrientationFromAppTokens(mService.mConfiguration,
  8.                 r.mayFreezeScreenLocked(app) ? r.appToken : null);
  9.         mService.updateConfigurationLocked(config, r, falsefalse);
  10.     }
  11.     //将进程描述符设置到启动的Activity描述符中
  12.     r.app = app;
  13.     app.waitingToKill = null;
  14.     //将启动的Activity添加到进程启动的Activity列表中
  15.     int idx = app.activities.indexOf(r);
  16.     if (idx < 0) {
  17.         app.activities.add(r);
  18.     }
  19.     mService.updateLruProcessLocked(app, truetrue);
  20.     try {
  21.         …
  22.         //通知应用程序进程加载Activity
  23.         ②app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
  24.                 System.identityHashCode(r), r.info,
  25.                 new Configuration(mService.mConfiguration),
  26.                 r.compat, r.icicle, results, newIntents, !andResume,
  27.                 mService.isNextTransitionForward(), profileFile, profileFd,
  28.                 profileAutoStop);
  29.         …
  30.     } catch (RemoteException e) {
  31.         …
  32.     }
  33.     if (mMainStack) {
  34.         mService.startSetupActivityLocked();
  35.     }
  36.     return true;
  37. }

AMS通过realStartActivityLocked函数来调度应用程序进程启动一个Activity,参数r为即将启动的Activity在AMS服务中的描述符,参数app为Activity运行所在的应用程序进程在AMS服务中的描述符。函数通过IApplicationThread代理对象ApplicationThreadProxy通知应用程序进程启动r对应的Activity,应用程序进程完成Activity的加载等准备工作后,AMS最后启动该Activity。启动Activity的创建等工作是在应用程序进程中完成的,AMS是通过IApplicationThread接口和应用程序进程通信的。r.appToken
在AMS服务端的类型为Token,是IApplicationToken的Binder本地对象。

frameworksbasecorejavaandroidapp ActivityThread.java

  1. public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
  2.         ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
  3.         Bundle state, List<ResultInfo> pendingResults,
  4.         List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
  5.         String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) {
  6.     //将AMS服务传过来的参数封装为ActivityClientRecord对象
  7.     ActivityClientRecord r = new ActivityClientRecord();
  8.     r.token = token;
  9.     r.ident = ident;
  10.     r.intent = intent;
  11.     r.activityInfo = info;
  12.     r.compatInfo = compatInfo;
  13.     r.state = state;
  14.     r.pendingResults = pendingResults;
  15.     r.pendingIntents = pendingNewIntents;
  16.     r.startsNotResumed = notResumed;
  17.     r.isForward = isForward;
  18.     r.profileFile = profileName;
  19.     r.profileFd = profileFd;
  20.     r.autoStopProfiler = autoStopProfiler;
  21.     updatePendingConfiguration(curConfig);
  22.     //使用异步消息方式实现Activity的启动
  23.     queueOrSendMessage(H.LAUNCH_ACTIVITY, r);
  24. }

参数token从AMS服务端经过Binder传输到应用程序进程后,变为IApplicationToken的Binder代理对象,类型为IApplicationToken.Proxy,这是因为AMS和应用程序运行在不同的进程中。

通过queueOrSendMessage函数将Binder跨进程调用转换为应用程序进程中的异步消息处理

frameworksbasecorejavaandroidapp ActivityThread.java

  1. private class H extends Handler {
  2.  public void handleMessage(Message msg) {
  3.     switch (msg.what) {
  4.             case LAUNCH_ACTIVITY: {
  5.                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “activityStart”);
  6.                 ActivityClientRecord r = (ActivityClientRecord)msg.obj;
  7.                 r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
  8.                 handleLaunchActivity(r, null);
  9.                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  10.             } break;
  11.         }
  12.     }
  13. }

LAUNCH_ACTIVITY消息在应用程序主线程消息循环中得到处理,应用程序通过handleLaunchActivity函数来启动Activity。到此AMS服务就完成了Activity的调度任务,将Activity的启动过程完全交给了应用程序进程来完成。

frameworksbasecorejavaandroidapp ActivityThread.java

  1. private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  2.     //主线程空闲时会定时执行垃圾回收,主线程当前要完成启动Activity的任务,因此这里先暂停GC
  3.     unscheduleGcIdler();
  4.     if (r.profileFd != null) {
  5.         mProfiler.setProfiler(r.profileFile, r.profileFd);
  6.         mProfiler.startProfiling();
  7.         mProfiler.autoStopProfiler = r.autoStopProfiler;
  8.     }
  9.     // Make sure we are running with the most recent config.
  10.     ①handleConfigurationChanged(nullnull);
  11.     //创建Activity
  12.     ②Activity a = performLaunchActivity(r, customIntent);
  13.     if (a != null) {
  14.         r.createdConfig = new Configuration(mConfiguration);
  15.         Bundle oldState = r.state;
  16.         //启动Activity
  17.         ③handleResumeActivity(r.token, false, r.isForward);
  18.         …
  19.     }else{
  20.         …
  21.     }
  22. }

performLaunchActivity

应用程序进程通过performLaunchActivity函数将即将要启动的Activity加载到当前进程空间来,同时为启动Activity做准备。

frameworksbasecorejavaandroidapp ActivityThread.java

  1. private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  2.     ActivityInfo aInfo = r.activityInfo;
  3.     if (r.packageInfo == null) {
  4.         //通过Activity所在的应用程序信息及该Activity对应的CompatibilityInfo信息从PMS服务中查询当前Activity的包信息
  5.         r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,Context.CONTEXT_INCLUDE_CODE);
  6.     }
  7.     //获取当前Activity的组件信息
  8.     ComponentName component = r.intent.getComponent();
  9.     if (component == null) {
  10.         component = r.intent.resolveActivity(mInitialApplication.getPackageManager());
  11.         r.intent.setComponent(component);
  12.     }
  13.     if (r.activityInfo.targetActivity != null) {
  14.         //packageName为启动Activity的包名,targetActivity为Activity的类名
  15.         component = new ComponentName(r.activityInfo.packageName,
  16.                 r.activityInfo.targetActivity);
  17.     }
  18.     //通过类反射方式加载即将启动的Activity
  19.     Activity activity = null;
  20.     try {
  21.         java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
  22.         ①activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
  23.         StrictMode.incrementExpectedActivityCount(activity.getClass());
  24.         r.intent.setExtrasClassLoader(cl);
  25.         if (r.state != null) {
  26.             r.state.setClassLoader(cl);
  27.         }
  28.     } catch (Exception e) {
  29.         …
  30.     }
  31.     try {
  32.         //通过单例模式为应用程序进程创建Application对象
  33.         ②Application app = r.packageInfo.makeApplication(false, mInstrumentation);
  34.         if (activity != null) {
  35.             //为当前Activity创建上下文对象ContextImpl
  36.             ContextImpl appContext = new ContextImpl();
  37.             //上下文初始化
  38.             ③appContext.init(r.packageInfo, r.token, this);
  39.             appContext.setOuterContext(activity);
  40.             CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
  41.             …
  42.             Configuration config = new Configuration(mCompatConfiguration);
  43.             //将当前启动的Activity和上下文ContextImpl、Application绑定
  44.             ④activity.attach(appContext, this, getInstrumentation(), r.token,
  45.                     r.ident, app, r.intent, r.activityInfo, title, r.parent,
  46.                     r.embeddedID, r.lastNonConfigurationInstances, config);
  47.             …
  48.             //调用Activity的OnCreate函数
  49.             ⑤mInstrumentation.callActivityOnCreate(activity, r.state);
  50.             …
  51.             //将Activity保存到ActivityClientRecord中,ActivityClientRecord为Activity在应用程序进程中的描述符
  52.             r.activity = activity;
  53.             …
  54.         }
  55.         r.paused = true;
  56.         //ActivityThread的成员变量mActivities保存了当前应用程序进程中的所有Activity的描述符
  57.         mActivities.put(r.token, r);
  58.     } catch (SuperNotCalledException e) {
  59.         …
  60.     }
  61.     return activity;
  62. }

在该函数中,首先通过PMS服务查找到即将启动的Activity的包名信息,然后通过类反射方式创建一个该Activity实例,同时为应用程序启动的每一个Activity创建一个LoadedApk实例对象,应用程序进程中创建的所有LoadedApk对象保存在ActivityThread的成员变量mPackages中。接着通过LoadedApk对象的makeApplication函数,使用单例模式创建Application对象,因此在android应用程序进程中有且只有一个Application实例。然后为当前启动的Activity创建一个ContextImpl上下文对象,并初始化该上下文,到此我们可以知道,启动一个Activity需要以下对象:

1)      XXActivity对象,需要启动的Activity;

2)      LoadedApk对象,每个启动的Activity都拥有属于自身的LoadedApk对象;

3)      ContextImpl对象,每个启动的Activity都拥有属于自身的ContextImpl对象;

4)      Application对象,应用程序进程中有且只有一个实例,和Activity是一对多的关系;

加载Activity类

  1. public Activity newActivity(ClassLoader cl, String className,
  2.         Intent intent)
  3.         throws InstantiationException, IllegalAccessException,
  4.         ClassNotFoundException {
  5.     return (Activity)cl.loadClass(className).newInstance();
  6. }

这里通过类反射的方式来加载要启动的Activity实例对象。

LoadedApk构造过程

首先介绍一下LoadedApk对象的构造过程:

frameworksbasecorejavaandroidapp ActivityThread.java

  1. public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
  2.         int flags) {
  3.     synchronized (mPackages) {
  4.         //通过Activity的包名从对应的成员变量中查找LoadedApk对象
  5.         WeakReference<LoadedApk> ref;
  6.         if ((flags&Context.CONTEXT_INCLUDE_CODE) != 0) {
  7.             ref = mPackages.get(packageName);
  8.         } else {
  9.             ref = mResourcePackages.get(packageName);
  10.         }
  11.         LoadedApk packageInfo = ref != null ? ref.get() : null;
  12.         if (packageInfo != null && (packageInfo.mResources == null
  13.                 || packageInfo.mResources.getAssets().isUpToDate())) {
  14.             …
  15.             return packageInfo;
  16.         }
  17.     }
  18.     //如果没有,则为当前Activity创建对应的LoadedApk对象
  19.     ApplicationInfo ai = null;
  20.     try {
  21.         //通过包名在PMS服务中查找应用程序信息
  22.         ai = getPackageManager().getApplicationInfo(packageName,
  23.                 PackageManager.GET_SHARED_LIBRARY_FILES, UserId.myUserId());
  24.     } catch (RemoteException e) {
  25.         // Ignore
  26.     }
  27.     //使用另一个重载函数创建LoadedApk对象
  28.     if (ai != null) {
  29.         return getPackageInfo(ai, compatInfo, flags);
  30.     }
  31.     return null;
  32. }

 

  1. public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo,
  2.         int flags) {
  3.     boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0;
  4.     boolean securityViolation = includeCode && ai.uid != 0
  5.             && ai.uid != Process.SYSTEM_UID && (mBoundApplication != null
  6.                     ? !UserId.isSameApp(ai.uid, mBoundApplication.appInfo.uid)
  7.                     : true);
  8.     if ((flags&(Context.CONTEXT_INCLUDE_CODE|Context.CONTEXT_IGNORE_SECURITY))
  9.             == Context.CONTEXT_INCLUDE_CODE) {
  10.         …
  11.     }
  12.     return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode);
  13. }

 

  1. private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
  2.         ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {
  3.     //再次从对应的成员变量中查找LoadedApk实例
  4.     synchronized (mPackages) {
  5.         WeakReference<LoadedApk> ref;
  6.         if (includeCode) {
  7.             ref = mPackages.get(aInfo.packageName);
  8.         } else {
  9.             ref = mResourcePackages.get(aInfo.packageName);
  10.         }
  11.         LoadedApk packageInfo = ref != null ? ref.get() : null;
  12.         if (packageInfo == null || (packageInfo.mResources != null
  13.                 && !packageInfo.mResources.getAssets().isUpToDate())) {
  14.             …
  15.             //构造一个LoadedApk对象
  16.             packageInfo =new LoadedApk(this, aInfo, compatInfo, this, baseLoader,
  17.                         securityViolation, includeCode &&
  18.                         (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);
  19.             //保存LoadedApk实例到ActivityThread的相应成员变量中
  20.             if (includeCode) {
  21.                 mPackages.put(aInfo.packageName,
  22.                         new WeakReference<LoadedApk>(packageInfo));
  23.             } else {
  24.                 mResourcePackages.put(aInfo.packageName,
  25.                         new WeakReference<LoadedApk>(packageInfo));
  26.             }
  27.         }
  28.         return packageInfo;
  29.     }
  30. }

 

frameworksbasecorejavaandroidappLoadedApk.java

  1. public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
  2.         CompatibilityInfo compatInfo,
  3.         ActivityThread mainThread, ClassLoader baseLoader,
  4.         boolean securityViolation, boolean includeCode) {
  5.     mActivityThread = activityThread;
  6.     mApplicationInfo = aInfo;
  7.     mPackageName = aInfo.packageName;
  8.     mAppDir = aInfo.sourceDir;
  9.     final int myUid = Process.myUid();
  10.     mResDir = aInfo.uid == myUid ? aInfo.sourceDir
  11.             : aInfo.publicSourceDir;
  12.     if (!UserId.isSameUser(aInfo.uid, myUid) && !Process.isIsolated()) {
  13.         aInfo.dataDir = PackageManager.getDataDirForUser(UserId.getUserId(myUid),
  14.                 mPackageName);
  15.     }
  16.     mSharedLibraries = aInfo.sharedLibraryFiles;
  17.     mDataDir = aInfo.dataDir;
  18.     mDataDirFile = mDataDir != null ? new File(mDataDir) : null;
  19.     mLibDir = aInfo.nativeLibraryDir;
  20.     mBaseClassLoader = baseLoader;
  21.     mSecurityViolation = securityViolation;
  22.     mIncludeCode = includeCode;
  23.     mCompatibilityInfo.set(compatInfo);
  24.     if (mAppDir == null) {
  25.         //为应用程序进程创建一个ContextImpl上下文
  26.         if (ActivityThread.mSystemContext == null) {
  27.             ActivityThread.mSystemContext =
  28.                 ContextImpl.createSystemContext(mainThread);
  29.             ActivityThread.mSystemContext.getResources().updateConfiguration(
  30.                      mainThread.getConfiguration(),
  31.                      mainThread.getDisplayMetricsLocked(compatInfo, false),
  32.                      compatInfo);
  33.         }
  34.         mClassLoader = ActivityThread.mSystemContext.getClassLoader();
  35.         mResources = ActivityThread.mSystemContext.getResources();
  36.     }
  37. }

从以上LoadedApk的构造函数可以看出,LoadedApk类记录了Activity运行所在的ActivityThread、Activity所在的应用程序信息、Activity的包名、Activity的资源路径、Activity的库路径、Activity的数据存储路径、类加载器和应用程序所使用的资源等信息。

Application构造过程

当Activity为应用程序进程启动的第一个Activity,因此需要构造一个Application对象

frameworksbasecorejavaandroidappLoadedApk.java

  1. public Application makeApplication(boolean forceDefaultAppClass,
  2.         Instrumentation instrumentation) {
  3.     //在应用程序进程空间以单例模式创建Application对象
  4.     if (mApplication != null) {
  5.         return mApplication;
  6.     }
  7.     Application app = null;
  8.     //得到应用程序的Application类名
  9.     String appClass = mApplicationInfo.className;
  10.     //如果应用程序没用重写Application,则使用Android默认的Application类
  11.     if (forceDefaultAppClass || (appClass == null)) {
  12.         appClass = “android.app.Application”;
  13.     }
  14.     try {
  15.         java.lang.ClassLoader cl = getClassLoader();
  16.         //为Application实例创建一个上下文对象ContextImpl
  17.         ①ContextImpl appContext = new ContextImpl();
  18.         //初始化上下文
  19.         ②appContext.init(thisnull, mActivityThread);
  20.         //创建Application实例对象
  21.         ③app = mActivityThread.mInstrumentation.newApplication(
  22.                 cl, appClass, appContext);
  23.         appContext.setOuterContext(app);
  24.     } catch (Exception e) {
  25.         …
  26.     }
  27.     mActivityThread.mAllApplications.add(app);
  28.     mApplication = app;
  29.     if (instrumentation != null) {
  30.         try {
  31.             //调用Application的OnCreate函数
  32.             ④instrumentation.callApplicationOnCreate(app);
  33.         } catch (Exception e) {
  34.             …
  35.         }
  36.     }
  37.     return app;
  38. }

在应用程序开发过程中,当我们重写了Application类后,应用程序加载运行的是我们定义的Application类,否则就加载运行默认的Application类。从Application对象的构造过程就可以解释为什么应用程序启动后首先执行的是Application的OnCreate函数。在实例化Application对象时,同样创建并初始化了一个ContextImpl上下文对象。

ContextImpl构造过程

前面我们介绍了,每一个Activity拥有一个上下文对象ContextImpl,每一个Application对象也拥有一个ContextImpl上下文对象,那么ContextImpl对象又是如何构造的呢?

frameworksbasecorejavaandroidapp ContextImpl.java

  1. ContextImpl() {
  2.     mOuterContext = this;
  3. }

ContextImpl的构造过程什么也没干,通过调用ContextImpl的init函数进行初始化

  1. final void init(LoadedApk packageInfo,IBinder activityToken, ActivityThread mainThread) {
  2.     init(packageInfo, activityToken, mainThread, nullnull);
  3. }

 

  1. final void init(LoadedApk packageInfo,IBinder activityToken, ActivityThread mainThread,
  2.             Resources container, String basePackageName) {
  3.     mPackageInfo = packageInfo;
  4.     mBasePackageName = basePackageName != null ? basePackageName : packageInfo.mPackageName;
  5.     mResources = mPackageInfo.getResources(mainThread);
  6.     if (mResources != null && container != null
  7.             && container.getCompatibilityInfo().applicationScale !=
  8.                     mResources.getCompatibilityInfo().applicationScale) {
  9.         mResources = mainThread.getTopLevelResources(
  10.                 mPackageInfo.getResDir(), container.getCompatibilityInfo());
  11.     }
  12.     mMainThread = mainThread;
  13.     mContentResolver = new ApplicationContentResolver(this, mainThread);
  14.     setActivityToken(activityToken);
  15. }

从ContextImpl的初始化函数中可以知道,ContextImpl记录了应用程序的包名信息、应用程序的资源信息、应用程序的主线程、ContentResolver及Activity对应的IApplicationToken.Proxy,当然对应Application对象所拥有的ContextImpl上下文就没有对应的Token了。通过前面的分析我们可以知道各个对象之间的关系:

对象Attach过程

Activity所需要的对象都创建好了,就需要将Activity和Application对象、ContextImpl对象绑定在一起。

frameworksbasecorejavaandroidapp Activity.java

  1. final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token,
  2.         Application application, Intent intent, ActivityInfo info, CharSequence title,
  3.         Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances,
  4.         Configuration config) {
  5.     attach(context, aThread, instr, token, 0, application, intent, info, title, parent, id,
  6.         lastNonConfigurationInstances, config);
  7. }

context:Activity的上下文对象,就是前面创建的ContextImpl对象;

aThread:Activity运行所在的主线程描述符ActivityThread;

instr:用于监控Activity运行状态的Instrumentation对象;

token:用于和AMS服务通信的IApplicationToken.Proxy代理对象;

application:Activity运行所在进程的Application对象;

parent:启动当前Activity的Activity;

  1. final void attach(Context context, ActivityThread aThread,
  2.         Instrumentation instr, IBinder token, int ident,
  3.         Application application, Intent intent, ActivityInfo info,
  4.         CharSequence title, Activity parent, String id,
  5.         NonConfigurationInstances lastNonConfigurationInstances,
  6.         Configuration config) {
  7.     //将上下文对象ContextImpl保存到Activity的成员变量中
  8.     attachBaseContext(context);
  9.     //每个Activity都拥有一个FragmentManager,这里就是将当前Activity设置到FragmentManager中管理
  10.     mFragments.attachActivity(this);
  11.     //创建窗口对象
  12.     ①mWindow = PolicyManager.makeNewWindow(this);
  13.     mWindow.setCallback(this);
  14.     mWindow.getLayoutInflater().setPrivateFactory(this);
  15.     if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
  16.         mWindow.setSoftInputMode(info.softInputMode);
  17.     }
  18.     if (info.uiOptions != 0) {
  19.         mWindow.setUiOptions(info.uiOptions);
  20.     }
  21.     //记录应用程序的UI线程
  22.     mUiThread = Thread.currentThread();
  23.     //记录应用程序的ActivityThread对象
  24.     mMainThread = aThread;
  25.     mInstrumentation = instr;
  26.     mToken = token;
  27.     mIdent = ident;
  28.     mApplication = application;
  29.     mIntent = intent;
  30.     mComponent = intent.getComponent();
  31.     mActivityInfo = info;
  32.     mTitle = title;
  33.     mParent = parent;
  34.     mEmbeddedID = id;
  35.     mLastNonConfigurationInstances = lastNonConfigurationInstances;
  36.     //为Activity所在的窗口创建窗口管理器
  37.     ②mWindow.setWindowManager(null, mToken, mComponent.flattenToString(),
  38.             (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
  39.     if (mParent != null) {
  40.         mWindow.setContainer(mParent.getWindow());
  41.     }
  42.     mWindowManager = mWindow.getWindowManager();
  43.     mCurrentConfig = config;
  44. }

在该attach函数中主要做了以下几件事:

1)        将Activity设置到FragmentManager中;

2)        根据参数初始化Activity的成员变量;

3)        为Activity创建窗口Window对象;

4)        为Window创建窗口管理器;

到此为止应用程序进程为启动的Activity对象创建了以下不同的实例对象,它们之间的关系如下:

应用程序窗口创建过程

frameworksbasecorejavacomandroidinternalpolicy PolicyManager.java

  1. public static Window makeNewWindow(Context context) {
  2.     return sPolicy.makeNewWindow(context);
  3. }

通过Policy类的makeNewWindow函数来创建一个应用程序窗口

  1. private static final String POLICY_IMPL_CLASS_NAME =
  2.         “com.android.internal.policy.impl.Policy”;
  3. private static final IPolicy sPolicy;
  4. static {
  5.     try {
  6.         Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
  7.         sPolicy = (IPolicy)policyClass.newInstance();
  8.     } catch (ClassNotFoundException ex) {
  9.         …
  10.     }
  11. }

frameworksbasepolicysrccomandroidinternalpolicyimpl Policy.java

  1. public Window makeNewWindow(Context context) {
  2.     return new PhoneWindow(context);
  3. }

应用程序窗口的创建过程其实就是构造一个PhoneWindow对象。PhoneWindow类是通过静态方式加载到应用程序进程空间的。

  1. private static final String[] preload_classes = {
  2.     “com.android.internal.policy.impl.PhoneLayoutInflater”,
  3.     “com.android.internal.policy.impl.PhoneWindow”,
  4.     “com.android.internal.policy.impl.PhoneWindow$1”,
  5.     “com.android.internal.policy.impl.PhoneWindow$ContextMenuCallback”,
  6.     “com.android.internal.policy.impl.PhoneWindow$DecorView”,
  7.     “com.android.internal.policy.impl.PhoneWindow$PanelFeatureState”,
  8.     “com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState”,
  9. };
  1. static {
  2.     for (String s : preload_classes) {
  3.         try {
  4.             Class.forName(s);
  5.         } catch (ClassNotFoundException ex) {
  6.             Log.e(TAG, “Could not preload class for phone policy: “ + s);
  7.         }
  8.     }
  9. }

PhoneWindow的构造过程

  1. public PhoneWindow(Context context) {
  2.     super(context);
  3.     mAlternativePanelStyle=getContext().getResources().getBoolean(com.android.internal.R.bool.config_alternativePanelStyle);
  4.     mLayoutInflater = LayoutInflater.from(context);
  5. }

构造过程比较简单,只是得到布局加载服务对象。

窗口管理器创建过程

通过前面的分析我们可以知道,在Activity启动过程中,会为Activity创建一个窗口对象PhoneWindow,应用程序有了窗口那就需要有一个窗口管理器来管理这些窗口,因此在Activity启动过程中还会创建一个WindowManager对象。

frameworksbasecorejavaandroidview Window.java

  1. public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
  2.         boolean hardwareAccelerated) {
  3.     mAppToken = appToken;// IApplicationToken.Proxy代理对象
  4.     mAppName = appName;
  5.     //得到WindowManagerImpl实例,
  6.     if (wm == null) {
  7.         wm = WindowManagerImpl.getDefault();
  8.     }
  9.     //为每个启动的Activity创建一个轻量级的窗口管理器LocalWindowManager
  10.     mWindowManager = new LocalWindowManager(wm, hardwareAccelerated);
  11. }

WindowManagerImpl为重量级的窗口管理器,应用程序进程中有且只有一个WindowManagerImpl实例,它管理了应用程序进程中创建的所有PhoneWindow窗口。Activity并没有直接引用WindowManagerImpl实例,Android系统为每一个启动的Activity创建了一个轻量级的窗口管理器LocalWindowManager,每个Activity通过LocalWindowManager来访问WindowManagerImpl,它们三者之间的关系如下图所示:

WindowManagerImpl以单例模式创建,应用程序进程中有且只有一个WindowManagerImpl实例

frameworksbasecorejavaandroidview WindowManagerImpl.java

  1. private final static WindowManagerImpl sWindowManager = new WindowManagerImpl();
  2. public static WindowManagerImpl getDefault() {
  3.     return sWindowManager;
  4. }

应用程序进程会为每一个Activity创建一个LocalWindowManager实例对象

frameworksbasecorejavaandroidview Window.java

  1. LocalWindowManager(WindowManager wm, boolean hardwareAccelerated) {
  2.     super(wm, getCompatInfo(mContext));
  3.     mHardwareAccelerated = hardwareAccelerated ||
  4.             SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
  5. }

frameworksbasecorejavaandroidview WindowManagerImpl.java

  1. CompatModeWrapper(WindowManager wm, CompatibilityInfoHolder ci) {
  2.     mWindowManager = wm instanceof CompatModeWrapper
  3.             ? ((CompatModeWrapper)wm).mWindowManager : (WindowManagerImpl)wm;
  4.     if (ci == null) {
  5.         mDefaultDisplay = mWindowManager.getDefaultDisplay();
  6.     } else {
  7.         mDefaultDisplay = Display.createCompatibleDisplay(
  8.                 mWindowManager.getDefaultDisplay().getDisplayId(), ci);
  9.     }
  10.     mCompatibilityInfo = ci;
  11. }

  1. public Display getDefaultDisplay() {
  2.     return new Display(Display.DEFAULT_DISPLAY, null);
  3. }

frameworksbasecorejavaandroidviewDisplay.java

  1. Display(int display, CompatibilityInfoHolder compatInfo) {
  2.     synchronized (sStaticInit) {
  3.         if (!sInitialized) {
  4.             nativeClassInit();
  5.             sInitialized = true;
  6.         }
  7.     }
  8.     mCompatibilityInfo = compatInfo != null ? compatInfo : new CompatibilityInfoHolder();
  9.     mDisplay = display;
  10.     init(display);
  11. }

构造Display对象时需要初始化该对象。

frameworksbasecorejniandroid_view_Display.cpp

  1. static void android_view_Display_init(
  2.         JNIEnv* env, jobject clazz, jint dpy)
  3. {
  4.     DisplayInfo info;
  5.     if (headless) {
  6.         // initialize dummy display with reasonable values
  7.         info.pixelFormatInfo.format = 1// RGB_8888
  8.         info.fps = 60;
  9.         info.density = 160;
  10.         info.xdpi = 160;
  11.         info.ydpi = 160;
  12.     } else {
  13.         status_t err = SurfaceComposerClient::getDisplayInfo(DisplayID(dpy), &info);
  14.         if (err < 0) {
  15.             jniThrowException(env, “java/lang/IllegalArgumentException”, NULL);
  16.             return;
  17.         }
  18.     }
  19.     env->SetIntField(clazz, offsets.pixelFormat,info.pixelFormatInfo.format);
  20.     env->SetFloatField(clazz, offsets.fps,      info.fps);
  21.     env->SetFloatField(clazz, offsets.density,  info.density);
  22.     env->SetFloatField(clazz, offsets.xdpi,     info.xdpi);
  23.     env->SetFloatField(clazz, offsets.ydpi,     info.ydpi);
  24. }

Display的初始化过程很简单,就是通过SurfaceComposerClient请求SurfaceFlinger得到显示屏的基本信息。

frameworksnativelibsgui SurfaceComposerClient.cpp

  1. status_t SurfaceComposerClient::getDisplayInfo(
  2.         DisplayID dpy, DisplayInfo* info)
  3. {
  4.     if (uint32_t(dpy)>=NUM_DISPLAY_MAX)
  5.         return BAD_VALUE;
  6.     volatile surface_flinger_cblk_t const * cblk = get_cblk();
  7.     volatile display_cblk_t const * dcblk = cblk->displays + dpy;
  8.     info->w              = dcblk->w;
  9.     info->h              = dcblk->h;
  10.     info->orientation      = dcblk->orientation;
  11.     info->xdpi           = dcblk->xdpi;
  12.     info->ydpi           = dcblk->ydpi;
  13.     info->fps            = dcblk->fps;
  14.     info->density        = dcblk->density;
  15.     return getPixelFormatInfo(dcblk->format, &(info->pixelFormatInfo));
  16. }

我们知道在SurfaceFlinger启动过程中,创建了一块匿名共享内存来保存显示屏的基本信息,这里就是通过访问这块匿名共享内存来读取显示屏信息。到此一个Activity所需要的窗口对象就创建完成了,在应用程序窗口的创建过程中一共创建了以下几个对象:

Activity视图对象的创建过程

在Activity的attach函数中完成应用程序窗口的创建后,通过Instrumentation回调Activity的OnCreate函数来为当前Activity加载布局文件,进一步创建视图对象。

frameworksbasecorejavaandroidappInstrumentation.java

  1. public void callActivityOnCreate(Activity activity, Bundle icicle) {
  2.     …
  3.     activity.performCreate(icicle);
  4.     …
  5. }

frameworksbasecorejavaandroidappActivity.java

  1. final void performCreate(Bundle icicle) {
  2.     onCreate(icicle);
  3.     mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
  4.             com.android.internal.R.styleable.Window_windowNoDisplay, false);
  5.     mFragments.dispatchActivityCreated();
  6. }

我们知道在应用程序开发中,需要重写Activity的OnCreate函数:

Packagesappsxxxsrccomxxx xxxActivity.java

  1. public void onCreate(Bundle savedInstanceState) {
  2.     super.onCreate(savedInstanceState);
  3.     setContentView(R.layout.main_activity);
  4.     …
  5. }

在OnCreate函数中通过setContentView来设置Activity的布局文件,就是生成该Activity的所有视图对象。

frameworksbasecorejavaandroidappActivity.java

  1. public void setContentView(View view, ViewGroup.LayoutParams params) {
  2.     getWindow().setContentView(view, params);
  3.     //初始化动作条
  4.     initActionBar();
  5. }

getWindow()函数得到前面创建的窗口对象PhoneWindow,通过PhoneWindow来设置Activity的视图。

frameworksbasepolicysrccomandroidinternalpolicyimplPhoneWindow.java

  1. public void setContentView(int layoutResID) {
  2.     //如果窗口顶级视图对象为空,则创建窗口视图对象
  3.     if (mContentParent == null) {
  4.         installDecor();
  5.     } else {//否则只是移除该视图对象中的其他视图
  6.         mContentParent.removeAllViews();
  7.     }
  8.     //加载布局文件,并将布局文件中的所有视图对象添加到mContentParent容器中
  9.     mLayoutInflater.inflate(layoutResID, mContentParent);
  10.     final Callback cb = getCallback();
  11.     if (cb != null && !isDestroyed()) {
  12.         cb.onContentChanged();
  13.     }
  14. }

PhoneWindow的成员变量mContentParent的类型为ViewGroup,是窗口内容存放的地方

frameworksbasepolicysrccomandroidinternalpolicyimplPhoneWindow.java

  1. private void installDecor() {
  2.     if (mDecor == null) {
  3.         ①mDecor = generateDecor();
  4.         mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
  5.         mDecor.setIsRootNamespace(true);
  6.         if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
  7.             mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
  8.         }
  9.     }
  10.     if (mContentParent == null) {
  11.         ②mContentParent = generateLayout(mDecor);
  12.         mDecor.makeOptionalFitsSystemWindows();
  13.         //应用程序窗口标题栏
  14.         mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
  15.         if (mTitleView != null) {
  16.             …
  17.         } else {
  18.             //应用程序窗口动作条
  19.             mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
  20.             if (mActionBar != null) {
  21.                 …
  22.             }
  23.         }
  24.     }
  25. }

通过函数generateDecor()来创建一个DecorView对象

  1. protected DecorView generateDecor() {
  2.     return new DecorView(getContext(), –1);
  3. }

接着通过generateLayout(mDecor)来创建视图对象容器mContentParent

  1. protected ViewGroup generateLayout(DecorView decor) {
  2.     //通过读取属性配置文件设置窗口风格
  3.     if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBarOverlay, false)) {
  4.         requestFeature(FEATURE_ACTION_BAR_OVERLAY);
  5.     }
  6.     …
  7.     //通过读取属性配置文件设置窗口标志
  8.     if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
  9.     setFlags(FLAG_FULLSCREEN,FLAG_FULLSCREEN&(~getForcedWindowFlags()));
  10.     }
  11.     …
  12.     WindowManager.LayoutParams params = getAttributes();
  13.     …
  14.     mDecor.startChanging();
  15.     //根据窗口主题风格选择不同的布局文件layoutResource
  16.     …
  17.     //加载布局文件
  18.     ①View in = mLayoutInflater.inflate(layoutResource, null);
  19.     //添加到DecorView中
  20.     ②decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
  21.     //从窗口视图中找出窗口内容视图对象
  22.     ③ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
  23.     …
  24.     mDecor.finishChanging();
  25.     return contentParent;
  26. }

到此Activity的所有视图对象都已经创建完毕,DecorView是Activity的顶级视图,由窗口PhoneWindow对象持有,在DecorView视图对象中添加了一个ViewGroup容器组件contentParent,所有用户定义视图组件将被添加到该容器中。

handleResumeActivity

performLaunchActivity函数完成了两件事:

1)        Activity窗口对象的创建,通过attach函数来完成;

2)        Activity视图对象的创建,通过setContentView函数来完成;

这些准备工作完成后,就可以显示该Activity了,应用程序进程通过调用handleResumeActivity函数来启动Activity的显示过程。

frameworksbasecorejavaandroidapp ActivityThread.java

  1. final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
  2.     unscheduleGcIdler();
  3.     ActivityClientRecord r;
  4.     try {
  5.         ①r = performResumeActivity(token, clearHide);
  6.     } catch (Exception e) {
  7.         …
  8.     }
  9.     if (r != null) {
  10.         final Activity a = r.activity;
  11.         …
  12.         if (r.window == null && !a.mFinished && willBeVisible) {
  13.             //获得为当前Activity创建的窗口PhoneWindow对象
  14.             r.window = r.activity.getWindow();
  15.             //获取为窗口创建的视图DecorView对象
  16.             View decor = r.window.getDecorView();
  17.             decor.setVisibility(View.INVISIBLE);
  18.             //在attach函数中就为当前Activity创建了WindowManager对象
  19.             ViewManager wm = a.getWindowManager();
  20.             //得到该视图对象的布局参数
  21.             ②WindowManager.LayoutParams l = r.window.getAttributes();
  22.             //将视图对象保存到Activity的成员变量mDecor中
  23.             a.mDecor = decor;
  24.             l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
  25.             if (r.intent.hasCategory(Intent.CATEGORY_HOME)) {
  26.                 l.idleScreenAvailable = true;
  27.             } else {
  28.                 l.idleScreenAvailable = false;
  29.             }
  30.             l.softInputMode |= forwardBit;
  31.             if (a.mVisibleFromClient) {
  32.                 a.mWindowAdded = true;
  33.                 //将创建的视图对象DecorView添加到Activity的窗口管理器中
  34.                 ③wm.addView(decor, l);
  35.             }
  36.         } else if (!willBeVisible) {
  37.             …
  38.         }
  39.         …
  40.         if (!r.onlyLocalRequest) {
  41.             r.nextIdle = mNewActivities;
  42.             mNewActivities = r;
  43.             Looper.myQueue().addIdleHandler(new Idler());
  44.         }
  45.         …
  46.     } else {
  47.         …
  48.     }
  49. }

我们知道,在前面的performLaunchActivity函数中完成Activity的创建后,会将当前当前创建的Activity在应用程序进程端的描述符ActivityClientRecord以键值对的形式保存到ActivityThread的成员变量mActivities中:mActivities.put(r.token, r),r.token就是Activity的身份证,即是IApplicationToken.Proxy代理对象,也用于与AMS通信。上面的函数首先通过performResumeActivity从mActivities变量中取出Activity的应用程序端描述符ActivityClientRecord,然后取出前面为Activity创建的视图对象DecorView和窗口管理器WindowManager,最后将视图对象添加到窗口管理器中。

我们知道Activity引用的其实是轻量级的窗口管理器LocalWindowManager

frameworksbasecorejavaandroidview Window.java

  1. public final void addView(View view, ViewGroup.LayoutParams params) {
  2.     WindowManager.LayoutParams wp = (WindowManager.LayoutParams)params;
  3.     CharSequence curTitle = wp.getTitle();
  4.     //应用程序窗口
  5.     if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
  6.         wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
  7.         if (wp.token == null) {
  8.             View decor = peekDecorView();
  9.             if (decor != null) {
  10.                 // LayoutParams 的token设置为W本地Binder对象
  11.                 wp.token = decor.getWindowToken();
  12.             }
  13.         }
  14.         if (curTitle == null || curTitle.length() == 0) {
  15.             //根据窗口类型设置不同的标题
  16.             …
  17.             if (mAppName != null) {
  18.                 title += “:” + mAppName;
  19.             }
  20.             wp.setTitle(title);
  21.         }
  22.     } else {//系统窗口
  23.         if (wp.token == null) {
  24.             wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
  25.         }
  26.         if ((curTitle == null || curTitle.length() == 0)
  27.                 && mAppName != null) {
  28.             wp.setTitle(mAppName);
  29.         }
  30.     }
  31.     if (wp.packageName == null) {
  32.         wp.packageName = mContext.getPackageName();
  33.     }
  34.     if (mHardwareAccelerated) {
  35.         wp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
  36.     }
  37.     super.addView(view, params);
  38. }

LocalWindowManager的addView函数对不同类型窗口的布局参数进行相应的设置,比如布局参数中的token设置,如果是应用程序窗口,则设置token为W本地Binder对象。如果不是应用程序窗口,同时当前窗口没有父窗口,则设置token为当前窗口的IApplicationToken.Proxy代理对象,否则设置为父窗口的IApplicationToken.Proxy代理对象。最后视图组件的添加工作交给其父类来完成。LocalWindowManager继承于CompatModeWrapper,是WindowManagerImpl的内部类。

frameworksbasecorejavaandroidviewWindowManagerImpl.java

  1. public void addView(View view, android.view.ViewGroup.LayoutParams params) {
  2.     mWindowManager.addView(view, params, mCompatibilityInfo);
  3. }

前面我们介绍了,每一个Activity拥有一个轻量级窗口管理器,通过轻量级窗口管理器LocalWindowManager来访问重量级窗口管理器WindowManagerImpl,因此视图组件的添加过程又转交给了WindowManagerImpl来实现。

  1. public void addView(View view, ViewGroup.LayoutParams params, CompatibilityInfoHolder cih) {
  2.     addView(view, params, cih, false);
  3. }

该函数又调用WindowManagerImpl的另一个重载函数来添加视图组件

  1. private void addView(View view, ViewGroup.LayoutParams params,
  2.         CompatibilityInfoHolder cih, boolean nest) {
  3.     …
  4.     final WindowManager.LayoutParams wparams= (WindowManager.LayoutParams)params;
  5.     ViewRootImpl root;
  6.     View panelParentView = null;
  7.     synchronized (this) {
  8.         …
  9.         //从mViews中查找当前添加的View
  10.         int index = findViewLocked(view, false);
  11.         //如果已经存在,直接返回
  12.         if (index >= 0) {
  13.             …
  14.             return;
  15.         }
  16.         //尚未添加当前View
  17.         if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
  18.                 wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
  19.             final int count = mViews != null ? mViews.length : 0;
  20.             for (int i=0; i<count; i++) {
  21.                 if (mRoots[i].mWindow.asBinder() == wparams.token) {
  22.                     panelParentView = mViews[i];
  23.                 }
  24.             }
  25.         }
  26.         //为Activity创建一个ViewRootImpl对象
  27.         ①root = new ViewRootImpl(view.getContext());
  28.         …
  29.         //设置视图组件的布局参数
  30.         view.setLayoutParams(wparams);
  31.         if (mViews == null) {
  32.             index = 1;
  33.             mViews = new View[1];
  34.             mRoots = new ViewRootImpl[1];
  35.             mParams = new WindowManager.LayoutParams[1];
  36.         } else {
  37.             //动态增加mViews数组长度
  38.             index = mViews.length + 1;
  39.             Object[] old = mViews;
  40.             mViews = new View[index];
  41.             System.arraycopy(old, 0, mViews, 0, index-1);
  42.             //动态增加mRoots数组长度
  43.             old = mRoots;
  44.             mRoots = new ViewRootImpl[index];
  45.             System.arraycopy(old, 0, mRoots, 0, index-1);
  46.             //动态增加mParams数组长度
  47.             old = mParams;
  48.             mParams = new WindowManager.LayoutParams[index];
  49.             System.arraycopy(old, 0, mParams, 0, index-1);
  50.         }
  51.         index–;
  52.         ②mViews[index] = view;
  53.         mRoots[index] = root;
  54.         mParams[index] = wparams;
  55.     }
  56.     try {
  57.         ③root.setView(view, wparams, panelParentView);
  58.     } catch (RuntimeException e) {
  59.         …
  60.     }
  61. }

到此我们知道,当应用程序向窗口管理器中添加一个视图对象时,首先会为该视图对象创建一个ViewRootImpl对象,并且将视图对象、ViewRootImpl对象、视图布局参数分别保存到窗口管理器WindowManagerImpl得mViews、mRoots、mParams数组中,如下图所示:

最后通过ViewRootImpl对象来完成视图的显示过程。

ViewRootImpl构造过程

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. public ViewRootImpl(Context context) {
  2.     …
  3.     ①getWindowSession(context.getMainLooper());
  4.     mThread = Thread.currentThread();
  5.     mLocation = new WindowLeaked(null);
  6.     mLocation.fillInStackTrace();
  7.     mWidth = –1;
  8.     mHeight = –1;
  9.     mDirty = new Rect();
  10.     mTempRect = new Rect();
  11.     mVisRect = new Rect();
  12.     mWinFrame = new Rect();
  13.     ②mWindow = new W(this);
  14.     mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
  15.     mInputMethodCallback = new InputMethodCallback(this);
  16.     mViewVisibility = View.GONE;
  17.     mTransparentRegion = new Region();
  18.     mPreviousTransparentRegion = new Region();
  19.     mFirst = true// true for the first time the view is added
  20.     mAdded = false;
  21.     mAccessibilityManager = AccessibilityManager.getInstance(context);
  22.     mAccessibilityInteractionConnectionManager =
  23.         new AccessibilityInteractionConnectionManager();
  24.     mAccessibilityManager.addAccessibilityStateChangeListener(
  25.             mAccessibilityInteractionConnectionManager);
  26.     ③mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, mHandler, this);
  27.     mViewConfiguration = ViewConfiguration.get(context);
  28.     mDensity = context.getResources().getDisplayMetrics().densityDpi;
  29.     mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context);
  30.     mProfileRendering = Boolean.parseBoolean(
  31.             SystemProperties.get(PROPERTY_PROFILE_RENDERING, “false”));
  32.     ④mChoreographer = Choreographer.getInstance();
  33.     PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
  34.     mAttachInfo.mScreenOn = powerManager.isScreenOn();
  35.     loadSystemProperties();
  36. }

在ViewRootImpl的构造函数中初始化了一些成员变量,ViewRootImpl创建了以下几个主要对象:

1)        通过getWindowSession(context.getMainLooper())得到IWindowSession的代理对象,该对象用于和WMS通信。

2)        创建了一个W本地Binder对象,用于WMS通知应用程序进程。

3)        采用单例模式创建了一个Choreographer对象,用于统一调度窗口绘图。

4)        创建ViewRootHandler对象,用于处理当前视图消息。

5)        构造一个AttachInfo对象;

6)        创建Surface对象,用于绘制当前视图,当然该Surface对象的真正创建是由WMS来完成的,只不过是WMS传递给应用程序进程的。

  1. private final Surface mSurface = new Surface();
  2. final ViewRootHandler mHandler = new ViewRootHandler();

IWindowSession代理获取过程

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. public static IWindowSession getWindowSession(Looper mainLooper) {
  2.     synchronized (mStaticInit) {
  3.         if (!mInitialized) {
  4.             try {
  5.                 //获取输入法管理器
  6.                 InputMethodManager imm = InputMethodManager.getInstance(mainLooper);
  7.                 //获取窗口管理器
  8.                 IWindowManager windowManager = Display.getWindowManager();
  9.                 //得到IWindowSession代理对象
  10.                 sWindowSession = windowManager.openSession(imm.getClient(), imm.getInputContext());
  11.                 float animatorScale = windowManager.getAnimationScale(2);
  12.                 ValueAnimator.setDurationScale(animatorScale);
  13.                 mInitialized = true;
  14.             } catch (RemoteException e) {
  15.             }
  16.         }
  17.         return sWindowSession;
  18.     }
  19. }

以上函数通过WMS的openSession函数创建应用程序与WMS之间的连接通道,即获取IWindowSession代理对象,并将该代理对象保存到ViewRootImpl的静态成员变量sWindowSession中

  1. static IWindowSession sWindowSession;

因此在应用程序进程中有且只有一个IWindowSession代理对象。

frameworksbaseservicesjavacomandroidserverwmWindowManagerService.java

  1. public IWindowSession openSession(IInputMethodClient client,
  2.         IInputContext inputContext) {
  3.     if (client == nullthrow new IllegalArgumentException(“null client”);
  4.     if (inputContext == nullthrow new IllegalArgumentException(“null inputContext”);
  5.     Session session = new Session(this, client, inputContext);
  6.     return session;
  7. }

在WMS服务端构造了一个Session实例对象。

AttachInfo构造过程

frameworksbasecorejavaandroidview View.java

  1. AttachInfo(IWindowSession session, IWindow window,
  2.         ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
  3.     mSession = session;//IWindowSession代理对象,用于与WMS通信
  4.     mWindow = window;//W对象
  5.     mWindowToken = window.asBinder();//W本地Binder对象
  6.     mViewRootImpl = viewRootImpl;//ViewRootImpl实例
  7.     mHandler = handler;//ViewRootHandler对象
  8.     mRootCallbacks = effectPlayer;//ViewRootImpl实例
  9. }

 

创建Choreographer对象

Android Project Butter分析中介绍了Android4.1引入VSYNC、Triple Buffer和Choreographer来改善Android先天存在的UI流畅性差问题,有关Choreographer的实现过程请参看Android系统Choreographer机制实现过程

视图View添加过程

窗口管理器WindowManagerImpl为当前添加的窗口创建好各种对象后,调用ViewRootImpl的setView函数向WMS服务添加一个窗口对象。

  1. public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
  2.     synchronized (this) {
  3.         if (mView == null) {
  4.             //将DecorView保存到ViewRootImpl的成员变量mView中
  5.             mView = view;
  6.             mFallbackEventHandler.setView(view);
  7.             mWindowAttributes.copyFrom(attrs);
  8.             attrs = mWindowAttributes;
  9.             mClientWindowLayoutFlags = attrs.flags;
  10.             setAccessibilityFocus(nullnull);
  11.             //DecorView实现了RootViewSurfaceTaker接口
  12.             if (view instanceof RootViewSurfaceTaker) {
  13.                 mSurfaceHolderCallback =
  14.                         ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
  15.                 if (mSurfaceHolderCallback != null) {
  16.                     mSurfaceHolder = new TakenSurfaceHolder();
  17.                     mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
  18.                 }
  19.             }
  20.             …
  21.             //同时将DecorView保存到mAttachInfo中
  22.             mAttachInfo.mRootView = view;
  23.             mAttachInfo.mScalingRequired = mTranslator != null;
  24.             mAttachInfo.mApplicationScale = mTranslator == null ? 1.0f : mTranslator.applicationScale;
  25.             if (panelParentView != null) {
  26.                 mAttachInfo.mPanelParentWindowToken
  27.                         = panelParentView.getApplicationWindowToken();
  28.             }
  29.             …
  30.             //在添加窗口前进行UI布局
  31.             ①requestLayout();
  32.             if ((mWindowAttributes.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
  33.                 mInputChannel = new InputChannel();
  34.             }
  35.             try {
  36.                 mOrigWindowType = mWindowAttributes.type;
  37.                 mAttachInfo.mRecomputeGlobalAttributes = true;
  38.                 collectViewAttributes();
  39.                 //将窗口添加到WMS服务中,mWindow为W本地Binder对象,通过Binder传输到WMS服务端后,变为IWindow代理对象
  40.                 ②res = sWindowSession.add(mWindow, mSeq, mWindowAttributes,
  41.                         getHostVisibility(), mAttachInfo.mContentInsets,
  42.                         mInputChannel);
  43.             } catch (RemoteException e) {
  44.                 …
  45.             }
  46.             …
  47.             //建立窗口消息通道
  48.             if (view instanceof RootViewSurfaceTaker) {
  49.                 mInputQueueCallback =
  50.                     ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
  51.             }
  52.             if (mInputChannel != null) {
  53.                 if (mInputQueueCallback != null) {
  54.                     mInputQueue = new InputQueue(mInputChannel);
  55.                     mInputQueueCallback.onInputQueueCreated(mInputQueue);
  56.                 } else {
  57.                     mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,Looper.myLooper());
  58.                 }
  59.             }
  60.             …
  61.         }
  62.     }
  63. }

通过前面的分析可以知道,用户自定义的UI作为一个子View被添加到DecorView中,然后将顶级视图DecorView添加到应用程序进程的窗口管理器中,窗口管理器首先为当前添加的View创建一个ViewRootImpl对象、一个布局参数对象ViewGroup.LayoutParams,然后将这三个对象分别保存到当前应用程序进程的窗口管理器WindowManagerImpl中,最后通过ViewRootImpl对象将当前视图对象注册到WMS服务中。

ViewRootImpl的setView函数向WMS服务添加一个窗口对象过程:

1)         requestLayout()在应用程序进程中进行窗口UI布局;

2)         WindowSession.add()向WMS服务注册一个窗口对象;

3)         注册应用程序进程端的消息接收通道;

窗口UI布局过程

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. public void requestLayout() {
  2.     //检查当前线程是否是UI线程
  3.     checkThread();
  4.     //标识当前正在请求UI布局
  5.     mLayoutRequested = true;
  6.     scheduleTraversals();
  7. }

窗口布局过程必须在UI线程中进行,因此该函数首先检查调用requestLayout()函数的线程是否为创建ViewRootImpl对象的线程。然后调用scheduleTraversals()函数启动Choreographer的Callback遍历过程。

  1. void scheduleTraversals() {
  2.     if (!mTraversalScheduled) {
  3.         mTraversalScheduled = true;
  4.         //暂停UI线程消息队列对同步消息的处理
  5.         mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
  6.         //向Choreographer注册一个类型为CALLBACK_TRAVERSAL的回调,用于处理UI绘制
  7.         mChoreographer.postCallback(
  8.                 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
  9.         //向Choreographer注册一个类型为CALLBACK_INPUT的回调,用于处理输入事件
  10.         scheduleConsumeBatchedInput();
  11.     }
  12. }

关于Choreographer的postCallback()用法在前面进行了详细的介绍,当Vsync事件到来时,mTraversalRunnable对象的run()函数将被调用。

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  2. final class TraversalRunnable implements Runnable {
  3.         @Override
  4.         public void run() {
  5.             doTraversal();
  6.         }
  7. }

mTraversalRunnable对象的类型为TraversalRunnable,该类实现了Runnable接口,在其run()函数中调用了doTraversal()函数来完成窗口布局。

  1. void doTraversal() {
  2.     if (mTraversalScheduled) {
  3.         mTraversalScheduled = false;
  4.         mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
  5.         if (mProfile) {
  6.             Debug.startMethodTracing(“ViewAncestor”);
  7.         }
  8.         Trace.traceBegin(Trace.TRACE_TAG_VIEW, “performTraversals”);
  9.         try {
  10.             performTraversals();
  11.         } finally {
  12.             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  13.         }
  14.         if (mProfile) {
  15.             Debug.stopMethodTracing();
  16.             mProfile = false;
  17.         }
  18.     }
  19. }

performTraversals函数相当复杂,其主要实现以下几个重要步骤:

1.执行窗口测量;

2.执行窗口注册;

3.执行窗口布局;

4.执行窗口绘图;

  1. private void performTraversals() {
  2.     // cache mView since it is used so much below…
  3.     final View host = mView;
  4.     if (host == null || !mAdded)
  5.         return;
  6.     mWillDrawSoon = true;
  7.     boolean windowSizeMayChange = false;
  8.     boolean newSurface = false;
  9.     boolean surfaceChanged = false;
  10.     WindowManager.LayoutParams lp = mWindowAttributes;
  11.     int desiredWindowWidth;
  12.     int desiredWindowHeight;
  13.     final View.AttachInfo attachInfo = mAttachInfo;
  14.     final int viewVisibility = getHostVisibility();
  15.     boolean viewVisibilityChanged = mViewVisibility != viewVisibility
  16.             || mNewSurfaceNeeded;
  17.     WindowManager.LayoutParams params = null;
  18.     if (mWindowAttributesChanged) {
  19.         mWindowAttributesChanged = false;
  20.         surfaceChanged = true;
  21.         params = lp;
  22.     }
  23.     …
  24.     /****************执行窗口测量******************/
  25.     boolean layoutRequested = mLayoutRequested && !mStopped;
  26.     if (layoutRequested) {
  27.         …
  28.         // Ask host how big it wants to be
  29.         windowSizeMayChange |= measureHierarchy(host, lp, res,
  30.                 desiredWindowWidth, desiredWindowHeight);
  31.     }
  32.     …
  33.     /****************向WMS服务添加窗口******************/
  34.     if (mFirst || windowShouldResize || insetsChanged ||
  35.             viewVisibilityChanged || params != null) {
  36.         …
  37.         try {
  38.             final int surfaceGenerationId = mSurface.getGenerationId();
  39.             relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
  40.             …
  41.         } catch (RemoteException e) {
  42.         }
  43.         …
  44.         if (!mStopped) {
  45.             boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
  46.                     (relayoutResult&WindowManagerImpl.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
  47.             if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
  48.                     || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
  49.                 …
  50.                  // Ask host how big it wants to be
  51.                 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  52.                 …
  53.             }
  54.         }
  55.     }
  56.     /****************执行窗口布局******************/
  57.     final boolean didLayout = layoutRequested && !mStopped;
  58.     boolean triggerGlobalLayoutListener = didLayout
  59.             || attachInfo.mRecomputeGlobalAttributes;
  60.     if (didLayout) {
  61.         performLayout();
  62.         …
  63.     }
  64.     …
  65.     /****************查找窗口焦点******************/
  66.     boolean skipDraw = false;
  67.     if (mFirst) {
  68.         // handle first focus request
  69.         if (DEBUG_INPUT_RESIZE) Log.v(TAG, “First: mView.hasFocus()=”
  70.                 + mView.hasFocus());
  71.         if (mView != null) {
  72.             if (!mView.hasFocus()) {
  73.                 mView.requestFocus(View.FOCUS_FORWARD);
  74.                 mFocusedView = mRealFocusedView = mView.findFocus();
  75.                 if (DEBUG_INPUT_RESIZE) Log.v(TAG, “First: requested focused view=”
  76.                         + mFocusedView);
  77.             } else {
  78.                 mRealFocusedView = mView.findFocus();
  79.                 if (DEBUG_INPUT_RESIZE) Log.v(TAG, “First: existing focused view=”
  80.                         + mRealFocusedView);
  81.             }
  82.         }
  83.         if ((relayoutResult&WindowManagerImpl.RELAYOUT_RES_ANIMATING) != 0) {
  84.             // The first time we relayout the window, if the system is
  85.             // doing window animations, we want to hold of on any future
  86.             // draws until the animation is done.
  87.             mWindowsAnimating = true;
  88.         }
  89.     } else if (mWindowsAnimating) {
  90.         skipDraw = true;
  91.     }
  92.     /****************执行窗口绘制******************/
  93.     mFirst = false;
  94.     mWillDrawSoon = false;
  95.     mNewSurfaceNeeded = false;
  96.     mViewVisibility = viewVisibility;
  97.     …
  98.     boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||
  99.             viewVisibility != View.VISIBLE;
  100.     if (!cancelDraw && !newSurface) {
  101.         if (!skipDraw || mReportNextDraw) {
  102.             if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
  103.                 for (int i = 0; i < mPendingTransitions.size(); ++i) {
  104.                     mPendingTransitions.get(i).startChangingAnimations();
  105.                 }
  106.                 mPendingTransitions.clear();
  107.             }
  108.             performDraw();
  109.         }
  110.     } else {
  111.         if (viewVisibility == View.VISIBLE) {
  112.             // Try again
  113.             scheduleTraversals();
  114.         } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
  115.             for (int i = 0; i < mPendingTransitions.size(); ++i) {
  116.                 mPendingTransitions.get(i).endChangingAnimations();
  117.             }
  118.             mPendingTransitions.clear();
  119.         }
  120.     }
  121. }
performMeasure

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
  2.  Trace.traceBegin(Trace.TRACE_TAG_VIEW, “measure”);
  3.  try {
  4.   mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  5.  } finally {
  6.   Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  7.  }
  8. }
relayoutWindow

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
  2.         boolean insetsPending) throws RemoteException {
  3.     …
  4.     int relayoutResult = sWindowSession.relayout(
  5.             mWindow, mSeq, params,
  6.             (int) (mView.getMeasuredWidth() * appScale + 0.5f),
  7.             (int) (mView.getMeasuredHeight() * appScale + 0.5f),
  8.             viewVisibility, insetsPending ? WindowManagerImpl.RELAYOUT_INSETS_PENDING : 0,
  9.             mWinFrame, mPendingContentInsets, mPendingVisibleInsets,
  10.             mPendingConfiguration, mSurface);
  11.     …
  12.     return relayoutResult;
  13. }

这里通过前面获取的IWindowSession代理对象请求WMS服务执行窗口布局,mSurface是ViewRootImpl的成员变量

  1. private final Surface mSurface = new Surface();

frameworksbasecorejavaandroidview Surface.java

  1. public Surface() {
  2.     checkHeadless();
  3.     if (DEBUG_RELEASE) {
  4.         mCreationStack = new Exception();
  5.     }
  6.     mCanvas = new CompatibleCanvas();
  7. }

该Surface构造函数仅仅创建了一个CompatibleCanvas对象,并没有对该Surface进程native层的初始化,到此我们知道应用程序进程为每个窗口对象都创建了一个Surface对象。并且将该Surface通过跨进程方式传输给WMS服务进程,我们知道,在Android系统中,如果一个对象需要在不同进程间传输,必须实现Parcelable接口,Surface类正好实现了Parcelable接口。ViewRootImpl通过IWindowSession接口请求WMS的完整过程如下:

frameworksbasecorejavaandroidviewIWindowSession.java$ Proxy

  1. public int relayout(android.view.IWindow window, int seq,
  2.         android.view.WindowManager.LayoutParams attrs, int requestedWidth,
  3.         int requestedHeight, int viewVisibility, int flags,
  4.         android.graphics.Rect outFrame,
  5.         android.graphics.Rect outOverscanInsets,
  6.         android.graphics.Rect outContentInsets,
  7.         android.graphics.Rect outVisibleInsets,
  8.         android.content.res.Configuration outConfig,
  9.         android.view.Surface outSurface) throws android.os.RemoteException {
  10.     android.os.Parcel _data = android.os.Parcel.obtain();
  11.     android.os.Parcel _reply = android.os.Parcel.obtain();
  12.     int _result;
  13.     try {
  14.         _data.writeInterfaceToken(DESCRIPTOR);
  15.         _data.writeStrongBinder((((window != null)) ? (window.asBinder()): (null)));
  16.         _data.writeInt(seq);
  17.         if ((attrs != null)) {
  18.             _data.writeInt(1);
  19.             attrs.writeToParcel(_data, 0);
  20.         } else {
  21.             _data.writeInt(0);
  22.         }
  23.         _data.writeInt(requestedWidth);
  24.         _data.writeInt(requestedHeight);
  25.         _data.writeInt(viewVisibility);
  26.         _data.writeInt(flags);
  27.         mRemote.transact(Stub.TRANSACTION_relayout, _data, _reply, 0);
  28.         _reply.readException();
  29.         _result = _reply.readInt();
  30.         if ((0 != _reply.readInt())) {
  31.             outFrame.readFromParcel(_reply);
  32.         }
  33.         if ((0 != _reply.readInt())) {
  34.             outOverscanInsets.readFromParcel(_reply);
  35.         }
  36.         if ((0 != _reply.readInt())) {
  37.             outContentInsets.readFromParcel(_reply);
  38.         }
  39.         if ((0 != _reply.readInt())) {
  40.             outVisibleInsets.readFromParcel(_reply);
  41.         }
  42.         if ((0 != _reply.readInt())) {
  43.             outConfig.readFromParcel(_reply);
  44.         }
  45.         if ((0 != _reply.readInt())) {
  46.             outSurface.readFromParcel(_reply);
  47.         }
  48.     } finally {
  49.         _reply.recycle();
  50.         _data.recycle();
  51.     }
  52.     return _result;
  53. }

从该函数的实现可以看出,应用程序进程中创建的Surface对象并没有传递到WMS服务进程,只是读取WMS服务进程返回来的Surface。那么WMS服务进程是如何响应应用程序进程布局请求的呢?
frameworksbasecorejavaandroidviewIWindowSession.java$ Stub

  1. public boolean onTransact(int code, android.os.Parcel data,
  2.         android.os.Parcel reply, int flags)throws android.os.RemoteException {
  3.     switch (code) {
  4.     case TRANSACTION_relayout: {
  5.         data.enforceInterface(DESCRIPTOR);
  6.         android.view.IWindow _arg0;
  7.         _arg0 = android.view.IWindow.Stub.asInterface(data.readStrongBinder());
  8.         int _arg1;
  9.         _arg1 = data.readInt();
  10.         android.view.WindowManager.LayoutParams _arg2;
  11.         if ((0 != data.readInt())) {
  12.             _arg2 = android.view.WindowManager.LayoutParams.CREATOR
  13.                     .createFromParcel(data);
  14.         } else {
  15.             _arg2 = null;
  16.         }
  17.         int _arg3;
  18.         _arg3 = data.readInt();
  19.         int _arg4;
  20.         _arg4 = data.readInt();
  21.         int _arg5;
  22.         _arg5 = data.readInt();
  23.         int _arg6;
  24.         _arg6 = data.readInt();
  25.         android.graphics.Rect _arg7;
  26.         _arg7 = new android.graphics.Rect();
  27.         android.graphics.Rect _arg8;
  28.         _arg8 = new android.graphics.Rect();
  29.         android.graphics.Rect _arg9;
  30.         _arg9 = new android.graphics.Rect();
  31.         android.graphics.Rect _arg10;
  32.         _arg10 = new android.graphics.Rect();
  33.         android.content.res.Configuration _arg11;
  34.         _arg11 = new android.content.res.Configuration();
  35.         android.view.Surface _arg12;
  36.         _arg12 = new android.view.Surface();
  37.         int _result = this.relayout(_arg0, _arg1, _arg2, _arg3, _arg4,
  38.                 _arg5, _arg6, _arg7, _arg8, _arg9, _arg10, _arg11, _arg12);
  39.         reply.writeNoException();
  40.         reply.writeInt(_result);
  41.         if ((_arg7 != null)) {
  42.             reply.writeInt(1);
  43.             _arg7.writeToParcel(reply,
  44.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  45.         } else {
  46.             reply.writeInt(0);
  47.         }
  48.         if ((_arg8 != null)) {
  49.             reply.writeInt(1);
  50.             _arg8.writeToParcel(reply,
  51.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  52.         } else {
  53.             reply.writeInt(0);
  54.         }
  55.         if ((_arg9 != null)) {
  56.             reply.writeInt(1);
  57.             _arg9.writeToParcel(reply,
  58.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  59.         } else {
  60.             reply.writeInt(0);
  61.         }
  62.         if ((_arg10 != null)) {
  63.             reply.writeInt(1);
  64.             _arg10.writeToParcel(reply,
  65.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  66.         } else {
  67.             reply.writeInt(0);
  68.         }
  69.         if ((_arg11 != null)) {
  70.             reply.writeInt(1);
  71.             _arg11.writeToParcel(reply,
  72.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  73.         } else {
  74.             reply.writeInt(0);
  75.         }
  76.         if ((_arg12 != null)) {
  77.             reply.writeInt(1);
  78.             _arg12.writeToParcel(reply,
  79.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  80.         } else {
  81.             reply.writeInt(0);
  82.         }
  83.         return true;
  84.     }
  85.     }
  86. }

该函数可以看出,WMS服务在响应应用程序进程请求添加窗口时,首先在当前进程空间创建一个Surface对象,然后调用Session的relayout()函数进一步完成窗口添加过程,最后将WMS服务中创建的Surface返回给应用程序进程。

到目前为止,在应用程序进程和WMS服务进程分别创建了一个Surface对象,但是他们调用的都是Surface的无参构造函数,在该构造函数中并未真正初始化native层的Surface,那native层的Surface是在那里创建的呢?
frameworksbaseservicesjavacomandroidserverwm Session.java

  1. public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
  2.         int requestedWidth, int requestedHeight, int viewFlags,
  3.         int flags, Rect outFrame, Rect outContentInsets,
  4.         Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
  5.     int res = mService.relayoutWindow(this, window, seq, attrs,
  6.             requestedWidth, requestedHeight, viewFlags, flags,
  7.             outFrame, outContentInsets, outVisibleInsets,
  8.             outConfig, outSurface);
  9.     return res;
  10. }

frameworksbaseservicesjavacomandroidserverwm WindowManagerService.java

  1. public int relayoutWindow(Session session, IWindow client, int seq,
  2.         WindowManager.LayoutParams attrs, int requestedWidth,
  3.         int requestedHeight, int viewVisibility, int flags,
  4.         Rect outFrame, Rect outContentInsets,
  5.         Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
  6.     …
  7.     synchronized(mWindowMap) {
  8.         // TODO(cmautner): synchronize on mAnimator or win.mWinAnimator.
  9.         WindowState win = windowForClientLocked(session, client, false);
  10.         if (win == null) {
  11.             return 0;
  12.         }
  13.         …
  14.         if (viewVisibility == View.VISIBLE &&
  15.                 (win.mAppToken == null || !win.mAppToken.clientHidden)) {
  16.             …
  17.             try {
  18.                 if (!win.mHasSurface) {
  19.                     surfaceChanged = true;
  20.                 }
  21.                 //创建Surface
  22.                 Surface surface = winAnimator.createSurfaceLocked();
  23.                 if (surface != null) {
  24.                     outSurface.copyFrom(surface);
  25.                 } else {
  26.                     outSurface.release();
  27.                 }
  28.             } catch (Exception e) {
  29.                 …
  30.             }
  31.             …
  32.         }
  33.         …
  34.     }
  35.     …
  36. }

frameworksbaseservicesjavacomandroidserverwmWindowStateAnimator.java

  1. Surface createSurfaceLocked() {
  2.     if (mSurface == null) {
  3.         …
  4.         try {
  5.             …
  6.             if (DEBUG_SURFACE_TRACE) {
  7.                 mSurface = new SurfaceTrace(
  8.                         mSession.mSurfaceSession, mSession.mPid,
  9.                         attrs.getTitle().toString(),
  10.                         0, w, h, format, flags);
  11.             } else {
  12.                 mSurface = new Surface(
  13.                     mSession.mSurfaceSession, mSession.mPid,
  14.                     attrs.getTitle().toString(),
  15.                     0, w, h, format, flags);
  16.             }
  17.             mWin.mHasSurface = true;
  18.         } catch (Surface.OutOfResourcesException e) {
  19.             …
  20.         }
  21.         Surface.openTransaction();
  22.         …
  23.     }
  24.     return mSurface;
  25. }

Surface创建过程
frameworksbasecorejavaandroidviewSurface.java

  1. public Surface(SurfaceSession s,int pid, String name, int display, int w, int h, int format, int flags)
  2.     throws OutOfResourcesException {
  3.     checkHeadless();
  4.     if (DEBUG_RELEASE) {
  5.         mCreationStack = new Exception();
  6.     }
  7.     mCanvas = new CompatibleCanvas();
  8.     init(s,pid,name,display,w,h,format,flags);
  9.     mName = name;
  10. }

frameworksbasecorejni android_view_Surface.cpp

  1. static void Surface_init(
  2.         JNIEnv* env, jobject clazz,
  3.         jobject session,
  4.         jint, jstring jname, jint dpy, jint w, jint h, jint format, jint flags)
  5. {
  6.     if (session == NULL) {
  7.         doThrowNPE(env);
  8.         return;
  9.     }
  10.     SurfaceComposerClient* client =
  11.             (SurfaceComposerClient*)env->GetIntField(session, sso.client);
  12.     sp<SurfaceControl> surface;
  13.     if (jname == NULL) {
  14.         surface = client->createSurface(dpy, w, h, format, flags);
  15.     } else {
  16.         const jchar* str = env->GetStringCritical(jname, 0);
  17.         const String8 name(str, env->GetStringLength(jname));
  18.         env->ReleaseStringCritical(jname, str);
  19.         surface = client->createSurface(name, dpy, w, h, format, flags);
  20.     }
  21.     if (surface == 0) {
  22.         jniThrowException(env, OutOfResourcesException, NULL);
  23.         return;
  24.     }
  25.     setSurfaceControl(env, clazz, surface);
  26. }

到此才算真正创建了一个可用于绘图的Surface,从上面的分析我们可以看出,在WMS服务进程端,其实创建了两个Java层的Surface对象,第一个Surface使用了无参构造函数,仅仅构造一个Surface对象而已,而第二个Surface却使用了有参构造函数,参数指定了图象宽高等信息,这个Java层Surface对象还会在native层请求SurfaceFlinger创建一个真正能用于绘制图象的native层Surface。最后通过浅拷贝的方式将第二个Surface复制到第一个Surface中,最后通过writeToParcel方式写回到应用程序进程。

到目前为止,应用程序和WMS一共创建了3个Java层Surface对象,如上图所示,而真正能用于绘图的Surface只有3号,那么3号Surface与2号Surface之间是什么关系呢?outSurface.copyFrom(surface)
frameworksbasecorejni android_view_Surface.cpp

  1. static void Surface_copyFrom(JNIEnv* env, jobject clazz, jobject other)
  2. {
  3.     if (clazz == other)
  4.         return;
  5.     if (other == NULL) {
  6.         doThrowNPE(env);
  7.         return;
  8.     }
  9.     //得到当前Surface所引用的SurfaceControl对象
  10.     const sp<SurfaceControl>& surface = getSurfaceControl(env, clazz);
  11.     //得到源Surface所引用的SurfaceControl对象
  12.     const sp<SurfaceControl>& rhs = getSurfaceControl(env, other);
  13.     //如果它们引用的不是同一个SurfaceControl对象
  14.     if (!SurfaceControl::isSameSurface(surface, rhs)) {
  15.         setSurfaceControl(env, clazz, rhs);
  16.     }
  17. }

2号Surface引用到了3号Surface的SurfaceControl对象后,通过writeToParcel()函数写会到应用程序进程。
frameworksbasecorejni android_view_Surface.cpp

  1. static void Surface_writeToParcel(
  2.         JNIEnv* env, jobject clazz, jobject argParcel, jint flags)
  3. {
  4.     Parcel* parcel = (Parcel*)env->GetIntField(
  5.             argParcel, no.native_parcel);
  6.     if (parcel == NULL) {
  7.         doThrowNPE(env);
  8.         return;
  9.     }
  10.     const sp<SurfaceControl>& control(getSurfaceControl(env, clazz));
  11.     if (control != NULL) {
  12.         SurfaceControl::writeSurfaceToParcel(control, parcel);
  13.     } else {
  14.         sp<Surface> surface(Surface_getSurface(env, clazz));
  15.         if (surface != NULL) {
  16.             Surface::writeToParcel(surface, parcel);
  17.         } else {
  18.             SurfaceControl::writeSurfaceToParcel(NULL, parcel);
  19.         }
  20.     }
  21.     if (flags & PARCELABLE_WRITE_RETURN_VALUE) {
  22.         setSurfaceControl(env, clazz, NULL);
  23.         setSurface(env, clazz, NULL);
  24.     }
  25. }

由于2号Surface引用的SurfaceControl对象不为空,因此这里就将SurfaceControl对象写会给应用程序进程
frameworksnativelibsgui Surface.cpp

  1. status_t SurfaceControl::writeSurfaceToParcel(
  2.         const sp<SurfaceControl>& control, Parcel* parcel)
  3. {
  4.     sp<ISurface> sur;
  5.     uint32_t identity = 0;
  6.     if (SurfaceControl::isValid(control)) {
  7.         sur = control->mSurface;
  8.         identity = control->mIdentity;
  9.     }
  10.     parcel->writeStrongBinder(sur!=0 ? sur->asBinder() : NULL);
  11.     parcel->writeStrongBinder(NULL);  // NULL ISurfaceTexture in this case.
  12.     parcel->writeInt32(identity);
  13.     return NO_ERROR;
  14. }

写入Parcel包裹的对象顺序如下:

应用程序进程中的1号Surface通过readFromParcel()函数读取从WMS服务进程写回的Binder对象。
frameworksbasecorejni android_view_Surface.cpp

  1. static void Surface_readFromParcel(
  2.         JNIEnv* env, jobject clazz, jobject argParcel)
  3. {
  4.     Parcel* parcel = (Parcel*)env->GetIntField( argParcel, no.native_parcel);
  5.     if (parcel == NULL) {
  6.         doThrowNPE(env);
  7.         return;
  8.     }
  9.     sp<Surface> sur(Surface::readFromParcel(*parcel));
  10.     setSurface(env, clazz, sur);
  11. }

frameworksnativelibsgui Surface.cpp

  1. sp<Surface> Surface::readFromParcel(const Parcel& data) {
  2.     Mutex::Autolock _l(sCachedSurfacesLock);
  3.     sp<IBinder> binder(data.readStrongBinder());
  4.     sp<Surface> surface = sCachedSurfaces.valueFor(binder).promote();
  5.     if (surface == 0) {
  6.        surface = new Surface(data, binder);
  7.        sCachedSurfaces.add(binder, surface);
  8.     } else {
  9.         // The Surface was found in the cache, but we still should clear any
  10.         // remaining data from the parcel.
  11.         data.readStrongBinder();  // ISurfaceTexture
  12.         data.readInt32();         // identity
  13.     }
  14.     if (surface->mSurface == NULL && surface->getISurfaceTexture() == NULL) {
  15.         surface = 0;
  16.     }
  17.     cleanCachedSurfacesLocked();
  18.     return surface;
  19. }

应用程序进程中的1号Surface按相反顺序读取WMS服务端返回过来的Binder对象等数据,并构造一个native层的Surface对象。

  1. Surface::Surface(const Parcel& parcel, const sp<IBinder>& ref)
  2.     : SurfaceTextureClient()
  3. {
  4.     mSurface = interface_cast<ISurface>(ref);
  5.     sp<IBinder> st_binder(parcel.readStrongBinder());
  6.     sp<ISurfaceTexture> st;
  7.     if (st_binder != NULL) {
  8.         st = interface_cast<ISurfaceTexture>(st_binder);
  9.     } else if (mSurface != NULL) {
  10.         st = mSurface->getSurfaceTexture();
  11.     }
  12.     mIdentity   = parcel.readInt32();
  13.     init(st);
  14. }

每个Activity可以有一个或多个Surface,默认情况下一个Activity只有一个Surface,当Activity中使用SurfaceView时,就存在多个Surface。Activity默认surface是在relayoutWindow过程中由WMS服务创建的,然后回传给应用程序进程,我们知道一个Surface其实就是应用程序端的本地窗口,关于Surface的初始化过程这里就不在介绍。

performLayout

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. private void performLayout() {
  2.     mLayoutRequested = false;
  3.     mScrollMayChange = true;
  4.     final View host = mView;
  5.     if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
  6.         Log.v(TAG, “Laying out “ + host + ” to (“ +
  7.                 host.getMeasuredWidth() + “, “ + host.getMeasuredHeight() + “)”);
  8.     }
  9.     Trace.traceBegin(Trace.TRACE_TAG_VIEW, “layout”);
  10.     try {
  11.         host.layout(00, host.getMeasuredWidth(), host.getMeasuredHeight());
  12.     } finally {
  13.         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  14.     }
  15. }

performDraw

frameworksbasecorejavaandroidview ViewRootImpl.java

  1. private void performDraw() {
  2.     if (!mAttachInfo.mScreenOn && !mReportNextDraw) {
  3.         return;
  4.     }
  5.     final boolean fullRedrawNeeded = mFullRedrawNeeded;
  6.     mFullRedrawNeeded = false;
  7.     mIsDrawing = true;
  8.     Trace.traceBegin(Trace.TRACE_TAG_VIEW, “draw”);
  9.     try {
  10.         draw(fullRedrawNeeded);
  11.     } finally {
  12.         mIsDrawing = false;
  13.         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  14.     }
  15.     …
  16. }

 

  1. private void draw(boolean fullRedrawNeeded) {
  2.     Surface surface = mSurface;
  3.     if (surface == null || !surface.isValid()) {
  4.         return;
  5.     }
  6.     …
  7.     if (!dirty.isEmpty() || mIsAnimating) {
  8.         //使用硬件渲染
  9.         if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
  10.             // Draw with hardware renderer.
  11.             mIsAnimating = false;
  12.             mHardwareYOffset = yoff;
  13.             mResizeAlpha = resizeAlpha;
  14.             mCurrentDirty.set(dirty);
  15.             mCurrentDirty.union(mPreviousDirty);
  16.             mPreviousDirty.set(dirty);
  17.             dirty.setEmpty();
  18.             if (attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
  19.                     animating ? null : mCurrentDirty)) {
  20.                 mPreviousDirty.set(00, mWidth, mHeight);
  21.             }
  22.         //使用软件渲染
  23.         } else if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
  24.             return;
  25.         }
  26.     }
  27.     …
  28. }

窗口添加过程

frameworksbaseservicesjavacomandroidserverwmSession.java

  1. public int add(IWindow window, int seq, WindowManager.LayoutParams attrs,
  2.         int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) {
  3.     return mService.addWindow(this, window, seq, attrs, viewVisibility, outContentInsets,
  4.             outInputChannel);
  5. }

frameworksbaseservicesjavacomandroidserverwmWindowManagerService.java

  1. public int addWindow(Session session, IWindow client, int seq,
  2.         WindowManager.LayoutParams attrs, int viewVisibility,
  3.         Rect outContentInsets, InputChannel outInputChannel) {
  4.     //client为IWindow的代理对象,是Activity在WMS服务中的唯一标示
  5.     int res = mPolicy.checkAddPermission(attrs);
  6.     if (res != WindowManagerImpl.ADD_OKAY) {
  7.         return res;
  8.     }
  9.     boolean reportNewConfig = false;
  10.     WindowState attachedWindow = null;
  11.     WindowState win = null;
  12.     long origId;
  13.     synchronized(mWindowMap) {
  14.         if (mDisplay == null) {
  15.             throw new IllegalStateException(“Display has not been initialialized”);
  16.         }
  17.         //判断窗口是否已经存在
  18.         if (mWindowMap.containsKey(client.asBinder())) {
  19.             Slog.w(TAG, “Window “ + client + ” is already added”);
  20.             return WindowManagerImpl.ADD_DUPLICATE_ADD;
  21.         }
  22.         //如果添加的是应用程序窗口
  23.         if (attrs.type >= FIRST_SUB_WINDOW && attrs.type <= LAST_SUB_WINDOW) {
  24.             //根据attrs.token从mWindowMap中取出应用程序窗口在WMS服务中的描述符WindowState
  25.             attachedWindow = windowForClientLocked(null, attrs.token, false);
  26.             if (attachedWindow == null) {
  27.                 Slog.w(TAG, “Attempted to add window with token that is not a window: “
  28.                       + attrs.token + “.  Aborting.”);
  29.                 return WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN;
  30.             }
  31.             if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
  32.                     && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
  33.                 Slog.w(TAG, “Attempted to add window with token that is a sub-window: “
  34.                         + attrs.token + “.  Aborting.”);
  35.                 return WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN;
  36.             }
  37.         }
  38.         boolean addToken = false;
  39.         //根据attrs.token从mWindowMap中取出应用程序窗口在WMS服务中的描述符WindowState
  40.         WindowToken token = mTokenMap.get(attrs.token);
  41.         if (token == null) {
  42.             …
  43.             ①token = new WindowToken(this, attrs.token, –1false);
  44.             addToken = true;
  45.         }
  46.         //应用程序窗口
  47.         else if (attrs.type >= FIRST_APPLICATION_WINDOW
  48.                 && attrs.type <= LAST_APPLICATION_WINDOW) {
  49.             AppWindowToken atoken = token.appWindowToken;
  50.             …
  51.         }
  52.         //输入法窗口
  53.         else if (attrs.type == TYPE_INPUT_METHOD) {
  54.             …
  55.         }
  56.         //墙纸窗口
  57.         else if (attrs.type == TYPE_WALLPAPER) {
  58.             …
  59.         }
  60.         //Dream窗口
  61.         else if (attrs.type == TYPE_DREAM) {
  62.             …
  63.         }
  64.         //为Activity窗口创建WindowState对象
  65.         ②win = new WindowState(this, session, client, token,
  66.                 attachedWindow, seq, attrs, viewVisibility);
  67.         …
  68.         if (outInputChannel != null && (attrs.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
  69.             String name = win.makeInputChannelName();
  70.             InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
  71.             win.setInputChannel(inputChannels[0]);
  72.             inputChannels[1].transferTo(outInputChannel);
  73.             mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
  74.         }
  75.         …
  76.         //以键值对<IWindow.Proxy/Token,WindowToken>形式保存到mTokenMap表中
  77.         if (addToken) {
  78.             ③mTokenMap.put(attrs.token, token);
  79.         }
  80.         ④win.attach();
  81.         //以键值对<IWindow的代理对象,WindowState>形式保存到mWindowMap表中
  82.         ⑤mWindowMap.put(client.asBinder(), win);
  83.         …
  84.     }
  85.     …
  86.     return res;
  87. }

我们知道当应用程序进程添加一个DecorView到窗口管理器时,会为当前添加的窗口创建ViewRootImpl对象,同时构造了一个W本地Binder对象,无论是窗口视图对象DecorView还是ViewRootImpl对象,都只是存在于应用程序进程中,在添加窗口过程中仅仅将该窗口的W对象传递给WMS服务,经过Binder传输后,到达WMS服务端进程后变为IWindow.Proxy代理对象,因此该函数的参数client的类型为IWindow.Proxy。参数attrs的类型为WindowManager.LayoutParams,在应用程序进程启动Activity时,handleResumeActivity()函数通过WindowManager.LayoutParams
l = r.window.getAttributes();来得到应用程序窗口布局参数,由于WindowManager.LayoutParams实现了Parcelable接口,因此WindowManager.LayoutParams对象可以跨进程传输,WMS服务的addWindow函数中的attrs参数就是应用程序进程发送过来的窗口布局参数。在LocalWindowManager的addView函数中为窗口布局参数设置了相应的token,如果是应用程序窗口,则布局参数的token设为W本地Binder对象。如果不是应用程序窗口,同时当前窗口没有父窗口,则设置token为当前窗口的IApplicationToken.Proxy代理对象,否则设置为父窗口的IApplicationToken.Proxy代理对象,由于应用程序和WMS分属于两个不同的进程空间,因此经过Binder传输后,布局参数的令牌attrs.token就转变为IWindow.Proxy或者Token。以上函数首先根据布局参数的token等信息构造一个WindowToken对象,然后在构造一个WindowState对象,并将添加的窗口信息记录到mTokenMap和mWindowMap哈希表中。


在WMS服务端创建了所需对象后,接着调用了WindowState的attach()来进一步完成窗口添加。
frameworksbaseservicesjavacomandroidserverwmWindowState.java

  1. void attach() {
  2.     if (WindowManagerService.localLOGV) Slog.v(
  3.         TAG, “Attaching “ + this + ” token=” + mToken
  4.         + “, list=” + mToken.windows);
  5.     mSession.windowAddedLocked();
  6. }

frameworksbaseservicesjavacomandroidserverwmSession.java

  1. void windowAddedLocked() {
  2.     if (mSurfaceSession == null) {
  3.         mSurfaceSession = new SurfaceSession();
  4.         if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
  5.                 WindowManagerService.TAG, ”  NEW SURFACE SESSION “ + mSurfaceSession);
  6.         mService.mSessions.add(this);
  7.     }
  8.     mNumWindow++;//记录对应的某个应用程序添加的窗口数量
  9. }

到此一个新的应用程序窗口就添加完成了。总结一下:

应用程序通过IWindowSession接口请求WMS服务添加一个应用程序窗口,WMS服务首先在自己服务进程为应用程序创建创建一个对应的WindowState描述符,然后保存到成员变量mWindowMap中。如果还没有为应用程序进程创建连接SurfaceFlinger的会话,就接着创建该会话通道SurfaceSession,我们知道,Activity中的视图所使用的画布Surface是在WMS服务进程中创建的,但是该画布所使用的图形buffer确实在SurfaceFlinger进程中分配管理的,而图形的绘制确是在应用程序进程中完成的,所以Activity的显示过程需要三个进程的配合才能完成。应用程序进程只与WMS服务进程交互,并不直接和SurfaceFlinger进程交互,而是由WMS服务进程同SurfaceFlinger进程配合。前面我们介绍了应用程序进程是通过IWindowSession接口与WMS服务进程通信的,那WMS服务是如何与SurfaceFlinger进程通信的呢,这就是windowAddedLocked函数要完成的工作。

在windowAddedLocked函数中使用单例模式创建一个SurfaceSession对象,在构造该对象时,通过JNI在native层创建一个与SurfaceFlinger进程的连接。

frameworksbasecorejavaandroidviewSurfaceSession.java

  1. public SurfaceSession() {
  2.     init();
  3. }

该init()函数是一个native函数,其JNI实现如下:

frameworksbasecorejni android_view_Surface.cpp

  1. static void SurfaceSession_init(JNIEnv* env, jobject clazz)
  2. {
  3.     sp<SurfaceComposerClient> client = new SurfaceComposerClient;
  4.     client->incStrong(clazz);
  5.     env->SetIntField(clazz, sso.client, (int)client.get());
  6. }

该函数构造了一个SurfaceComposerClient对象,在第一次强引用该对象时,会请求SurfaceFlinger创建一个专门处理当前应用程序进程请求的Client会话。

每个应用程序进程都持有一个与WMS服务会话通道IWindowSession,而服务端的Session有且只有一个SurfaceSession对象。系统中创建的所有IWindowSession都被记录到WMS服务的mSessions成员变量中,这样WMS就可以知道自己正在处理那些应用程序的请求。到此我们来梳理一下在WMS服务端都创建了那些对象:

1)        WindowState对象,是应用程序窗口在WMS服务端的描述符;

2)        Session对象,应用程序进程与WMS服务会话通道;

3)        SurfaceSession对象,应用程序进程与SurfaceFlinger的会话通道;

 

如何开发一个WordPress插件

介绍

WordPress 插件 允许你对 WordPress 博客进行修改、自定义和加强。不必修改 WordPress 的核心程序,直接用插件的形式增加功能。下面是对 WordPress 插件的基本定义:

WordPress 插件:WordPress 插件是用 PHP 语言写成的一只或者一组程序。这些程序可以为 WordPress 增加某些原来没有的功能,这样使用者看起来仿佛就是这个博客固有的功能。 插件 API

想为你的博客添加功能吗?那么最简单的方法就是搜搜看有没有现成的插件。如果很不幸——没有,那么这篇文章会指导你自己开发一个。

本文假设你已经熟悉 WordPress 的基本功能,以及 PHP 编程。

资源

  • 插件资源集合 有各种你可能需要的资源,包括外站关于写插件的文章,以及特定主题的文章。
  • 学习一个叫 Hello Dolly 的插件“范本”可以领你入门。
  • 如果你的插件已经写完了,并自以为写的不错,查看 插件提交以及推广

新建一个插件

这个部分告诉你怎么把开发插件的理想变为现实。

名称,文件和地方

插件名

你得先想一个名字,并且努力让它独一无二。在 Plugins 或者其他宝贝地方——Google或者百度先验证一下这个名字到底是不是独一无二的。另外你的名字得让别人明白你的插件是干什么的。

插件文件

下一步是创建一个PHP文件。按照原文奇怪的逻辑,你得先想好名字。这个名字还得是从插件名衍生过来的(其实是为你自己辨认的)。举个例子吧,比如说你的插件名字叫 “Fabulous Functionality”,你的PHP名字可能是 fabfunc.php。另外不要用汉语拼音(这也是我加的),还要避免重名。人民群众会把你的插件安装到一个你也知道的叫wp-content/plugins/的地方,如果名字冲突岂不要悲剧了。

你也可以选择把插件分割成几个文件。 显而易见一个php文件是必需的,同时还需要图片、CSS、JavaScript、语言(当然也可以没有)。如果有很多文件,命名一个php和一个文件夹,例如 fabfunc and fabfunc.php。把你所有插件文件放到文件夹里,然后让你的用户相信只要把你的整个压缩包解压到 wp-content/plugins/就能正常使用你的劳动成果。

在本文的其余部分,“插件的PHP文件”是指主要插件的PHP文件,无论是在的wp-content/plugins/或子目录。

Readme文件

如果你想将你的插件发布到http://wordpress.org/extend/plugins/, 你必须在插件包中建立一个标准格式readme.txt文件. 文件格式参见http://wordpress.org/extend/plugins/about/readme.txt.

主页

最好为插件建立一个主页,以介绍插件的功能、安装方法、使用说明、适用的WordPress版本、插件更新信息等。

文件Headers

现在开始吧,首先让我们从向PHP主文件中加入一些信息

标准插件信息

插件的主文件顶部必须包括一个标准插件信息头。WordPress通过标准信息头识别插件的存在,并把她加入到控制面板的插件管理页面,这样插件才能激活,载入插件,并运行里面的函数;如果没有信息头,插件将无法激活和使用。标准信息插件头的格式为:

1
2
3
4
5
6
7
8
9
10
11
<?php
/*
Plugin Name: 插件名
Plugin URI: 插件的介绍或更新地址
Description: 插件描述
Version: 插件版本,例如 1.0
Author: 插件作者名称
Author URI: 插件作者的链接
License: A "Slug" license name e.g. GPL2
*/
?>

标准信息头至少要包括插件名称,这样WordPress才能识别你的插件。其他信息将显示在控制面板插件管理页面中。标准插件信息对各行顺序没有要求。

这样的升级机制能够正确地读出你的插件版本,建议你选择一个格式的版本号,不同版本之间,并坚持下去。例如,x.x中或x.x.x或xx.xx.xxx

注意:文件必须是 UTF-8 格式!

版权信息

通常我们还要在标准信息头中加入插件的许可证信息。大多数插件使用GPLGPLCompatibleLicenses许可。如果使用GPL许可,要求插件中包含以下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
/*  Copyright 年份  作者名  (email : 你的邮箱)
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
?>

开始编写插件

现在是时候让你的插件能干些什么了。这部分内容包括插件开发的一般思路,而且介绍了开发插件需要做哪些工作。

WordPress插件钩子

许多WordPress插件通过连接一个或多个WordPress插件钩子来完成他们的功能。插件钩子的运行机理是,当WordPress运行到不同阶段,WordPress会检查当前阶段是否注册了插件函数,如果是,那么函数将被执行。通过执行插件函数改变了WordPress的默认功能。

例如,在WordPress将日志标题和post发送到浏览器前,会检查是否有插件函数注册了名为“the_title”的“filter”钩子。如果是,标题文本将会传送到注册函数中,注册函数返回值将会传送到浏览器。所以,如果要在日志标题中加入一些信息,可以通过这种方式实现。

另一个例子是名为“wp_footer”的“action”钩子。在WordPress的HTML页脚创建之前,会检查是否有插件注册了名为“wp_footer”的“action”钩子,如果是依次执行她们。

Plugin API了解更多如何注册“filter”和“action”类型的钩子函数,及WordPress提供了那些插件钩子。如果你发现WordPress没有提供自己想要的钩子,你可以建议WordPress加入这个钩子,很多建议WordPress都会采纳。具体方法参考Reporting Bugs

模版标签

另一个通过插件加入新功能的方法是建立自定义的模版标签Template Tags。如果有人想用你的插件,可以在他们的主题中添加这些标签,边栏,文章内容段,或者任意的只要是适合这插件的地方。例如,一个给文章添加地理位置的插件可能定义了一个模板标签函数geotag_list_states()放在边栏上,这里列表了所有在文章中关联的州的名称,并且还带有插件提供的到这些州的文档页的链接。

定义一个自定义模板标签,仅需要写一个PHP函数,并且在你插件主页或者插件的主PHP文件中声明一下。声明函数的时候,为这个函数提供一个示例来明确如果想要应用这个函数需要加主题中加些什么文件是个相当棒的主意。

保存插件数据到数据库

大多数WordPress插件需要站点的所有者或者是博客的用户输入信息,然后在对话过程中保存起来,以便过滤器函数(filter)、动作函数(action)或者模板函数(Template)使用。这些信息必须保存在WordPress的数据库中,以便下次使用。这里有两种基本的方法用于保存插件的信息到数据库里面。

  1. 使用WordPress的”option”机制(稍后会有介绍)。这种方法适合于保存一些相对小数量的静态命名类数据–这类数据通常只需要网站的所有者在首次建立插件的时候输入,以后很少改动。
  2. 文章属性post meta(a.k.a. Custom Fields),适用于那些只和个人文章、页面或者附件有关的数据。
  3. 参看post_meta Function Examples, add_post_meta(), 以及与文章相关函数属性post.fuction meta (a.k.a. Custom Fields).
  4. 自定义分类法。对于文章分类或者其他对象,比如用户、评论,或者用户可编辑列表中的数据名称/值,可以考虑使用自定义分类法,尤其是当你要访问的所有的文章/对象与给定的分类法项目相关联的时候。查阅 Custom Taxonomies
  5. 在数据库中创建一个新的自定义数据表。这种方法适合于与个人文章、页面、附件或者评论相关的数据,这类数据会随着时间越来越多,它们也没有专有名称。参看Creating Tables with Plugins来了解更多这类信息处理方法。

WordPress的选项机制

参看 Creating Options Pages 得到更多如何创建会自动保存你选项数据的页面。

WordPress拥有一个机制来保存,更新和检索WordPress数据库中专用,名称类数据(即”options”机制)。选项值可以是字符,数组或PHP对象(他们会被”序列化”,或在存储前转换为字符,并在被检索时解开序列)。选项名称是字符,并且它们必须是独一无二的,这样就不会与其它的WordPress插件相冲突。

它也通常被认为是一个不错的主意,将你的插件使用的选项的数量降到最低。例如,考虑存储序列化数组的10个元素作为一个单一的命名选项,而不是存储10个不同的命名选项。

这里是你的插件应用WordPress option功能的主要函数。

1
add_option($name, $value, $deprecated, $autoload);
建立一个新的option; 如果这个option已经存在则不做动作.
$name
必须 (string). 要添加的option的名称.
$value
可选(string), 默认是空字符. option值会存在这里.
$deprecated
可选 (string), 不再被WordPress使用了,你可以不填或NULL 如果你希望应用后面的$autoload参数.
$autoload
可选, 默认为 ‘yes’ (enum: ‘yes’ or ‘no’). 如果设置为 ‘yes’ 那么这个option会被get_alloptions 函数自动检索.
1
get_option($option);
在数据库中检索option值.
$option
Required (string). 你想返回数值的option名称。你可以Option Reference在找到一个随着WordPress一起安装好的默认option表。
1
update_option($option_name, $newvalue);
更新或创建数据库中的option值(注意 add_option 不是必须被调用,如果你不想作用 $deprecated 或$autoload 参数).
$option_name
必须(string). 要更新的option名.
$newvalue
必须. (string|array|object) option的新值.

管理面板

假定你的插件有一些选项(option)存储于WordPress的数据库中(参看上一节),你可能会想要一个主控面板来允许你的插件用户查看和编辑选项值。实现这一目标的方法阐述于Adding Administration Menus

插件国际化

在你完成了你的插件的编写工作之后,另一个需要考虑的问题(假设你准备跟大家分享你的插件的话)就是将其国际化。国际化就是将你的软件设置成能够本地化的过程;本地化是将软件中显示的语言翻译成其他语言的过程。Wordpress正在被全球的人们使用,所以全球化和本地化是他内在的特性,这其中就包括了插件的本地化。

请注意,插件的语言文件是不会自动加载。将此插件代码,以确保加载的语言文件:

1
load_plugin_textdomain('your-unique-name', false, basename( dirname( __FILE__ ) ) . '/languages' );

要简单地取一个字符串使用 __(‘String name’,’your-unique-name’); 返回翻译或者 _e(‘String name’,’your-unique-name’); 输出翻译。翻译,然后进入你插件的  /languages  文件夹。

我们十分希望你能够将你的插件国际化,这样其他国家的用户就可以在自己的本地使用它了。我们有一个关于国际化的综合说明在I18n for WordPress Developers,这其中就包括了一个描述插件国际化的部分。

更新你的插件

本节介绍将插件托管到 http://wordpress.org/extend/plugins 之后必要的更新步骤。特别列出wordpress.org关于使用 Subversion(SVN)的一些细节。

假设你已经提交你的插件到WordPress的插件库,随着时间的推移,你可能会发现需要将某些功能添加到插件或修正错误。更新代码,并将变化提交到你的插件主干(trunk),这些变化将是公开可见的,但仅限于在技术上志同道合的人通过SVN检查你的插件。其他用户通过网站或自己的WordPress插件管理下载都不会改变。

当你准备发布一个新版本的插件:

  • 确保一切承诺和新版本的实际工作。注意所有版本的WordPress的插件支持,并尝试与他们进行测试。不要只是测试新功能,也确保你不小心打破一些插件的旧功能。
  • 更改主要的PHP文件头注释中的版本号为新的版本号。
  • 更改readme.txt文件的“Stable tag”字段中的版本号。
  • 在readme.txt文件中添加一个新的小节“changelog“,简要介绍与最后一个版本相比,新版本有什么改变。这将列出的插件页面上的“更新日志”选项卡。
  • 提交这些更改。
  • 创建一个新的SVN标记作为副本主干(trunk),遵循 this guide

给系统一个运行两三分钟,然后检查你的插件,看看更新是否一切正常,以及WordPress 是否提示插件有更新(更新检查可能有缓存,比如wordpress.org插件页面或后台安装,所以这可能需要一些时间 —— 尝试访问“可用更新”页面)。

故障排除:

  • wordpress.org插件的页面上仍然列出旧版本。你是否更新了树干文件夹’stable tag’ 字段?只创建一个标签和或更新readme.txt文件是不够的!
  • 插件的页面提供了一个zip文件的新版本,但按钮仍然列出旧的版本号,而且WordPress 没有装更新通知。你是否已修改主要的PHP文件中“Version”版本号?
  • 对于其他问题,请参考: The Plugins directory and readme.txt files

插件开发建议

最后这个部分是关于开发插件的一些建议。

  • WordPress插件的代码应该遵循 WordPress Coding Standards. 另外请同时参考Inline Documentation
  • 你的插件中所有函数的名称都应该与现存的Wordpress Core函数,其他插件或主题的任何名称不同。基于这个原因,我们建议你在你的插件的所有函数的名称之前加上一个你自己选择的前缀,或者把你的插件的函数都写在一个类里面(当然这个类的名字也必须是唯一的)。
  • 请不要把Wordpress数据库表格前缀(通常是“wp_”)直接写在你的插件里,请使用$wpdb->prefix 。
  • 虽然数据库的读取相对便宜,但它的写入是相当昂贵的。数据库十分擅长获取信息并呈现给用户,而且这些操作(通常)是非常迅速的。然而对数据库进行改动就是一个非常复杂的过程了,而且需要使用更长的计算时间。因此,请尽量减少你对数据库进行写入的次数。在你编写程序的时候就做好所有的准备,这样就可以只在必须的时候再进行写入了。
  • 在数据库里只SELECT你需要的东西。尽管数据库的读取十分便捷,我们依然推荐你值查找真正需要的数据,来尽量减少数据库的负载。例如,如果你只想获得表格的行数,不要使用 SELECT * FROM, 因为这样的话每一行中的所有数据都会被读出,导致内存的浪费。同样的,如果在插件中你只想获得post_id和post_author,请只 SELECT 这两项来减少数据库的负载。记住:在某一个操作的同时可能有其他上百个进程需要使用数据库,而数据库和服务器都必须同时满足所有这些进程的需求。学习怎样尽量减少你的插件对数据库的使用可以避免对这些资源的滥用。
  • 不要让你的PHP出错。在你的wp_config.php文件中添加define(‘WP_DEBUG’,true);,对你的所有函数进行测试来确定是否有任何的错误或者警告。有多少,就修复多少,直到再也不出现为止。
  • 尽量不要直接调用<script>和<style>标记 —— 推荐使用 wp_enqueue_style() 和 wp_enqueue_script() 函数。他们帮助消除引用重复的脚本和样式,以及引进依赖的支持。

 

  • 原文:http://codex.wordpress.org/Writing_a_Plugin
  • 编译:倡萌@WordPress大学 (参考官方中文文档)

Android之Wifi学习(2)——连接Wifi

  1. package org.sunchao;
  2. import android.app.Activity;
  3. import android.content.Intent;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.view.View.OnClickListener;
  7. import android.widget.Button;
  8. import android.widget.ScrollView;
  9. import android.widget.TextView;
  10. public class Main extends Activity implements OnClickListener {
  11.     // 右侧滚动条按钮
  12.     private ScrollView sView;
  13.     private Button openNetCard;
  14.     private Button closeNetCard;
  15.     private Button checkNetCardState;
  16.     private Button scan;
  17.     private Button getScanResult;
  18.     private Button connect;
  19.     private Button disconnect;
  20.     private Button checkNetWorkState;
  21.     private TextView scanResult;
  22.     private String mScanResult;
  23.     private WifiAdmin mWifiAdmin;
  24.     /** Called when the activity is first created. */
  25.     @Override
  26.     public void onCreate(Bundle savedInstanceState) {
  27.         super.onCreate(savedInstanceState);
  28.         setContentView(R.layout.main);
  29.         mWifiAdmin = new WifiAdmin(Main.this);
  30.         init();
  31.     }
  32.     /**
  33.      * 按钮等控件的初始化
  34.      */
  35.     public void init() {
  36.         sView = (ScrollView) findViewById(R.id.mScrollView);
  37.         openNetCard = (Button) findViewById(R.id.openNetCard);
  38.         closeNetCard = (Button) findViewById(R.id.closeNetCard);
  39.         checkNetCardState = (Button) findViewById(R.id.checkNetCardState);
  40.         scan = (Button) findViewById(R.id.scan);
  41.         getScanResult = (Button) findViewById(R.id.getScanResult);
  42.         scanResult = (TextView) findViewById(R.id.scanResult);
  43.         connect = (Button) findViewById(R.id.connect);
  44.         disconnect = (Button) findViewById(R.id.disconnect);
  45.         checkNetWorkState = (Button) findViewById(R.id.checkNetWorkState);
  46.         openNetCard.setOnClickListener(Main.this);
  47.         closeNetCard.setOnClickListener(Main.this);
  48.         checkNetCardState.setOnClickListener(Main.this);
  49.         scan.setOnClickListener(Main.this);
  50.         getScanResult.setOnClickListener(Main.this);
  51.         connect.setOnClickListener(Main.this);
  52.         disconnect.setOnClickListener(Main.this);
  53.         checkNetWorkState.setOnClickListener(Main.this);
  54.     }
  55.     /**
  56.      * WIFI_STATE_DISABLING 0 WIFI_STATE_DISABLED 1 WIFI_STATE_ENABLING 2
  57.      * WIFI_STATE_ENABLED 3
  58.      */
  59.     public void openNetCard() {
  60.         mWifiAdmin.openNetCard();
  61.     }
  62.     public void closeNetCard() {
  63.         mWifiAdmin.closeNetCard();
  64.     }
  65.     public void checkNetCardState() {
  66.         mWifiAdmin.checkNetCardState();
  67.     }
  68.     public void scan() {
  69.         mWifiAdmin.scan();
  70.     }
  71.     public void getScanResult() {
  72.         mScanResult = mWifiAdmin.getScanResult();
  73.         scanResult.setText(mScanResult);
  74.     }
  75.     public void connect() {
  76.         mWifiAdmin.connect();
  77. //      startActivityForResult(new Intent(
  78. //              android.provider.Settings.ACTION_WIFI_SETTINGS), 0);
  79.         startActivity(new Intent(android.provider.Settings.ACTION_WIFI_SETTINGS));
  80.     }
  81.     public void disconnect() {
  82.         mWifiAdmin.disconnectWifi();
  83.     }
  84.     public void checkNetWorkState() {
  85.         mWifiAdmin.checkNetWorkState();
  86.     }
  87.     @Override
  88.     public void onClick(View v) {
  89.         switch (v.getId()) {
  90.         case R.id.openNetCard:
  91.             openNetCard();
  92.             break;
  93.         case R.id.closeNetCard:
  94.             closeNetCard();
  95.             break;
  96.         case R.id.checkNetCardState:
  97.             checkNetCardState();
  98.             break;
  99.         case R.id.scan:
  100.             scan();
  101.             break;
  102.         case R.id.getScanResult:
  103.             getScanResult();
  104.             break;
  105.         case R.id.connect:
  106.             connect();
  107.             break;
  108.         case R.id.disconnect:
  109.             disconnect();
  110.             break;
  111.         case R.id.checkNetWorkState:
  112.             checkNetWorkState();
  113.             break;
  114.         default:
  115.             break;
  116.         }
  117.     }
  118. }

Wifi工具类:

  1. package org.sunchao;
  2. import java.util.List;
  3. import android.content.Context;
  4. import android.net.wifi.ScanResult;
  5. import android.net.wifi.WifiConfiguration;
  6. import android.net.wifi.WifiInfo;
  7. import android.net.wifi.WifiManager;
  8. import android.net.wifi.WifiManager.WifiLock;
  9. import android.util.Log;
  10. public class WifiAdmin {
  11.     private final static String TAG = “WifiAdmin”;
  12.     private StringBuffer mStringBuffer = new StringBuffer();
  13.     private List<ScanResult> listResult;
  14.     private ScanResult mScanResult;
  15.     // 定义WifiManager对象
  16.     private WifiManager mWifiManager;
  17.     // 定义WifiInfo对象
  18.     private WifiInfo mWifiInfo;
  19.     // 网络连接列表
  20.     private List<WifiConfiguration> mWifiConfiguration;
  21.     // 定义一个WifiLock
  22.     WifiLock mWifiLock;
  23.     /**
  24.      * 构造方法
  25.      */
  26.     public WifiAdmin(Context context) {
  27.         mWifiManager = (WifiManager) context
  28.                 .getSystemService(Context.WIFI_SERVICE);
  29.         mWifiInfo = mWifiManager.getConnectionInfo();
  30.     }
  31.     /**
  32.      * 打开Wifi网卡
  33.      */
  34.     public void openNetCard() {
  35.         if (!mWifiManager.isWifiEnabled()) {
  36.             mWifiManager.setWifiEnabled(true);
  37.         }
  38.     }
  39.     /**
  40.      * 关闭Wifi网卡
  41.      */
  42.     public void closeNetCard() {
  43.         if (mWifiManager.isWifiEnabled()) {
  44.             mWifiManager.setWifiEnabled(false);
  45.         }
  46.     }
  47.     /**
  48.      * 检查当前Wifi网卡状态
  49.      */
  50.     public void checkNetCardState() {
  51.         if (mWifiManager.getWifiState() == 0) {
  52.             Log.i(TAG, “网卡正在关闭”);
  53.         } else if (mWifiManager.getWifiState() == 1) {
  54.             Log.i(TAG, “网卡已经关闭”);
  55.         } else if (mWifiManager.getWifiState() == 2) {
  56.             Log.i(TAG, “网卡正在打开”);
  57.         } else if (mWifiManager.getWifiState() == 3) {
  58.             Log.i(TAG, “网卡已经打开”);
  59.         } else {
  60.             Log.i(TAG, “—_—晕……没有获取到状态—_—“);
  61.         }
  62.     }
  63.     /**
  64.      * 扫描周边网络
  65.      */
  66.     public void scan() {
  67.         mWifiManager.startScan();
  68.         listResult = mWifiManager.getScanResults();
  69.         if (listResult != null) {
  70.             Log.i(TAG, “当前区域存在无线网络,请查看扫描结果”);
  71.         } else {
  72.             Log.i(TAG, “当前区域没有无线网络”);
  73.         }
  74.     }
  75.     /**
  76.      * 得到扫描结果
  77.      */
  78.     public String getScanResult() {
  79.         // 每次点击扫描之前清空上一次的扫描结果
  80.         if (mStringBuffer != null) {
  81.             mStringBuffer = new StringBuffer();
  82.         }
  83.         // 开始扫描网络
  84.         scan();
  85.         listResult = mWifiManager.getScanResults();
  86.         if (listResult != null) {
  87.             for (int i = 0; i < listResult.size(); i++) {
  88.                 mScanResult = listResult.get(i);
  89.                 mStringBuffer = mStringBuffer.append(“NO.”).append(i + 1)
  90.                         .append(” :”).append(mScanResult.SSID).append(“->”)
  91.                         .append(mScanResult.BSSID).append(“->”)
  92.                         .append(mScanResult.capabilities).append(“->”)
  93.                         .append(mScanResult.frequency).append(“->”)
  94.                         .append(mScanResult.level).append(“->”)
  95.                         .append(mScanResult.describeContents()).append(“nn”);
  96.             }
  97.         }
  98.         Log.i(TAG, mStringBuffer.toString());
  99.         return mStringBuffer.toString();
  100.     }
  101.     /**
  102.      * 连接指定网络
  103.      */
  104.     public void connect() {
  105.         mWifiInfo = mWifiManager.getConnectionInfo();
  106.     }
  107.     /**
  108.      * 断开当前连接的网络
  109.      */
  110.     public void disconnectWifi() {
  111.         int netId = getNetworkId();
  112.         mWifiManager.disableNetwork(netId);
  113.         mWifiManager.disconnect();
  114.         mWifiInfo = null;
  115.     }
  116.     /**
  117.      * 检查当前网络状态
  118.      * 
  119.      * @return String
  120.      */
  121.     public void checkNetWorkState() {
  122.         if (mWifiInfo != null) {
  123.             Log.i(TAG, “网络正常工作”);
  124.         } else {
  125.             Log.i(TAG, “网络已断开”);
  126.         }
  127.     }
  128.     /**
  129.      * 得到连接的ID
  130.      */
  131.     public int getNetworkId() {
  132.         return (mWifiInfo == null) ? 0 : mWifiInfo.getNetworkId();
  133.     }
  134.     /**
  135.      * 得到IP地址
  136.      */
  137.     public int getIPAddress() {
  138.         return (mWifiInfo == null) ? 0 : mWifiInfo.getIpAddress();
  139.     }
  140.     // 锁定WifiLock
  141.     public void acquireWifiLock() {
  142.         mWifiLock.acquire();
  143.     }
  144.     // 解锁WifiLock
  145.     public void releaseWifiLock() {
  146.         // 判断时候锁定
  147.         if (mWifiLock.isHeld()) {
  148.             mWifiLock.acquire();
  149.         }
  150.     }
  151.     // 创建一个WifiLock
  152.     public void creatWifiLock() {
  153.         mWifiLock = mWifiManager.createWifiLock(“Test”);
  154.     }
  155.     // 得到配置好的网络
  156.     public List<WifiConfiguration> getConfiguration() {
  157.         return mWifiConfiguration;
  158.     }
  159.     // 指定配置好的网络进行连接
  160.     public void connectConfiguration(int index) {
  161.         // 索引大于配置好的网络索引返回
  162.         if (index >= mWifiConfiguration.size()) {
  163.             return;
  164.         }
  165.         // 连接配置好的指定ID的网络
  166.         mWifiManager.enableNetwork(mWifiConfiguration.get(index).networkId,
  167.                 true);
  168.     }
  169.     // 得到MAC地址
  170.     public String getMacAddress() {
  171.         return (mWifiInfo == null) ? “NULL” : mWifiInfo.getMacAddress();
  172.     }
  173.     // 得到接入点的BSSID
  174.     public String getBSSID() {
  175.         return (mWifiInfo == null) ? “NULL” : mWifiInfo.getBSSID();
  176.     }
  177.     // 得到WifiInfo的所有信息包
  178.     public String getWifiInfo() {
  179.         return (mWifiInfo == null) ? “NULL” : mWifiInfo.toString();
  180.     }
  181.     // 添加一个网络并连接
  182.     public int addNetwork(WifiConfiguration wcg) {
  183.         int wcgID = mWifiManager.addNetwork(mWifiConfiguration.get(3));
  184.         mWifiManager.enableNetwork(wcgID, true);
  185.         return wcgID;
  186.     }
  187. }

AndroidManifest.xml:(注意需要添加的权限)

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <manifest xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     package=“org.sunchao” android:versionCode=“1” android:versionName=“1.0”>
  4.     <uses-sdk android:minSdkVersion=“8” />
  5.     <application android:icon=“@drawable/icon” android:label=“@string/app_name”>
  6.         <activity android:name=“.Main” android:label=“@string/app_name”>
  7.             <intent-filter>
  8.                 <action android:name=“android.intent.action.MAIN” />
  9.                 <category android:name=“android.intent.category.LAUNCHER” />
  10.             </intent-filter>
  11.         </activity>
  12.     </application>
  13.     <!– 以下是使用wifi访问网络所需的权限 –>
  14.     <uses-permission android:name=“android.permission.CHANGE_NETWORK_STATE”></uses-permission>
  15.     <uses-permission android:name=“android.permission.CHANGE_WIFI_STATE”></uses-permission>
  16.     <uses-permission android:name=“android.permission.ACCESS_NETWORK_STATE”></uses-permission>
  17.     <uses-permission android:name=“android.permission.ACCESS_WIFI_STATE”></uses-permission>
  18. </manifest>

布局文件就不贴了,我想看界面你都可以自己写出来,如果有需要,这里可以下载源代码:http://download.csdn.net/source/3449837

AndroidUI设计之 布局管理器 – 详细解析布局实现

 父容器与本容器属性 : android_layout…属性是本容器的属性, 定义在这个布局管理器的LayoutParams内部类中, 每个布局管理器都有一个LayoutParams内部类, Android:… 是父容器用来控制子组件的属性. 如android:layout_gravity 是控制组件本身的对齐方式, android:gravity是控制本容器子组件的对齐方式;

 

布局管理器都是以ViewGroup为基类派生出来的; 使用布局管理器可以适配不同手机屏幕的分辨率,尺寸大小;

 

布局管理器之间的继承关系

 


 

在上面的UML图中可以看出, 绝对布局 帧布局 网格布局 相对布局 线性布局直接继承ViewGroup,表格布局继承的LinearLayout;

 

一. 线性布局(LinearLayout)

 

1. 线性布局作用 

 

作用 : 线性布局会将容器中的组件一个一个排列起来, LinearLayout可以控制组件 横向 或者 纵向 排列, 通过android:orientation属性控制;

不换行属性 : 线性布局中的组件不会自动换行, 如果组件一个一个排列到尽头之后, 剩下的组件就不会显示出来;

 

2. LinearLayout常用属性

 

(1)基线对齐

 

xml属性 : android:baselineAligned

设置方法 : setBaselineAligned(boolean b)

作用 : 如果该属性为false, 就会阻止该布局管理器与其子元素的基线对齐;

 

(2)设分隔条 

 

xml属性 : android:divider

设置方法 : setDividerDrawable(Drawable)

作用 : 设置垂直布局时两个按钮之间的分隔条;

 

(3)对齐方式(控制内部子元素)  

 

xml属性 : android:gravity

设置方法 : setGravity(int)

作用 : 设置布局管理器内组件(子元素)的对齐方式

支持的属性

top, bottom, left, right, 

center_vertical(垂直方向居中), center_horizontal(水平方向居中),

fill_vertical(垂直方向拉伸), fill_horizontal(水平方向拉伸), 

center, fill, 

clip_vertical, clip_horizontal; 

可以同时指定多种对齐方式 : 如 left|center_vertical 左侧垂直居中;

 

(4)权重最小尺寸 

 

xml属性 : android:measureWithLargestChild

设置方法 : setMeasureWithLargestChildEnable(boolean b);

作用 : 该属性为true的时候, 所有带权重的子元素都会具有最大子元素的最小尺寸;

 

(5) 排列方式

 

xml属性 : android:orientation;

设置方法 : setOrientation(int i);

作用 : 设置布局管理器内组件排列方式, 设置为horizontal(水平),vertical(垂直), 默认为垂直排列;

 

3. LinearLayout子元素控制

 

LinearLayout的子元素, 即LinearLayout中的组件, 都受到LinearLayout.LayoutParams控制, 因此LinearLayout包含的子元素可以执行下面的属性.

 

(1) 对齐方式

 

xml属性 : android:layout_gravity;

作用 : 指定该元素在LinearLayout(父容器)的对齐方式, 也就是该组件本身的对齐方式, 注意要与android:gravity区分, ;

 

(2) 所占权重

 

xml属性 : android:layout_weight;

作用 : 指定该元素在LinearLayout(父容器)中所占的权重, 例如都是1的情况下, 那个方向(LinearLayout的orientation方向)长度都是一样的;

 

4. 控制子元素排列 与 在父元素中排列

 

控制本身元素属性与子元素属性

设备组件本身属性 : 带layout的属性是设置本身组件属性, 例如 android:layout_gravity设置的是本身的对其方式; 

设置子元素属性 : 不带layout的属性是设置其所包含的子元素, 例如android:gravity 设置的是该容器子组件的对齐方式;

LayoutParams属性 : 所有的布局管理器都提供了相应的LayoutParams内部类, 这些内部类用于控制该布局本身, 如 对齐方式 layout_gravity, 所占权重 layout_weight, 这些属性用于设置本元素在父容器中的对齐方式;

容器属性 : 在android:后面没有layout的属性基本都是容器属性, android:gravity作用是指定指定本元素包含的子元素的对齐方式, 只有容器才支持这个属性;

 

5. 常见用法

 

(1) 获取LinearLayout的宽高

 

a. 组件外无法获取组件宽高 

下面的两种情况都是针对 View.getHeight() 和 View.getWidth() 方法

组件外无法获取 : 调用View.getHeight()View.getWidth()方法 是获取不到组件的宽度和高度的, 这两个方法返回的是0, Android的运行机制决定了无法在组件外部使用getHeight()和getWidth()方法获取宽度和高度;

组件内可以获取 : 在自定义的类中可以在View的类中通过调用这两个方法获取该View子类组件的宽和高;

 

b. 组件外部获取View对象宽高方法 

 

外部获取 : 使用View.getMeasuredWidth()View.getMeasuredHeight()方法可以获取组件的宽和高, 在调用这个方法之前, 必须先调用View.measure()方法, 才可以, 否则也获取不到组件的宽高;

注意(特例) : 如果组件宽度或高度设置为 fill_parent, 使用 getMeasuredHeight() 等方法获取宽度和高度的时候, 并且组件中含有子元素时, 所获取的实际值是这些组件所占的最小宽度和最小高度.(没看懂)

 

示例:

 

  1. View view = getLayoutInflater().inflate(R.layout.main, null);
  2. LinearLayout layout = (LinearLayout) view.findViewById(R.id.linearlayout);
  3. //调用测量方法, 调用了该方法之后才能通过getMeasuredHeight()等方法获取宽高
  4. layout.measure(00);
  5. //获取宽度
  6. int width = layout.getMeasuredWidth();
  7. //获取高度
  8. int height = layout.getMeasuredHeight();

 

 

c. 获取布局文件中组件的宽高 

 

从LayoutParams中获取 : 调用View.getLayoutParams().width 和 View.getLayoutParams().height 获取宽高, 如果宽高被设定为 fill_parent, match_parent, warp_content 时, 这两个两边直接回返回 FILL_PARENT, MATCH_PARENT, WARP_CONTENT常量值;

规律 : 从View.getLayoutParams()中获取 width, height 值, 在布局xml文件中设置的是什么, 获取的时候就得到的是什么;

 

(2) 在LinearLayout中添加分隔线

 

a. 使用ImageView添加(低版本3.0以下)

 

垂直布局 横向宽度填满 : 如果布局是vertical, 那么设置一个ImageView宽度fill_parent, 高度2dp, 设置一个背景色;

水平布局 纵向高度填满 : 如果布局时horizontal, 那么设置一个ImageView宽度2dp, 高度fill_parent, 设置一个背景色;

 

 

  1. <ImageView
  2.     android:layout_width=“fill_parent”
  3.     android:layout_height=“2dp”
  4.     android:background=“#F00”/>

 

b. 使用xml属性添加(3.0以上版本)

 

设置LinearLayout标签的 android:showDividers属性, 该属性有四个值 : 

none :不显示分隔线;

beginning : 在LinearLayout开始处显示分隔线;

middle : 在LinearLayout中每两个组件之间显示分隔线;

end : 在LinearLayout结尾处显示分隔线;

 

设置android:divider属性, 这个属性的值是一个Drawable的id;

 

c. 使用代码添加(3.0以上版本)

 

设置显示分隔线样式 : linearLayout.setShowDividers(), 设置android:showDividers属性;

设置分隔线图片 : linearLayout.setDividerDrawable(), 设置android:divider属性;

 

6. 实际案例

 

(1) 按钮排列 

 

       

 

要点

底部 + 水平居中 对齐属性 : 左边的LinearLayout的android:gravity 属性为bottom|center_horizontal

右部 + 垂直居中 对齐属性 : 右边的LinearLayout的android:gravity 属性为right|center_vertical;

 

代码

 

 

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     android:layout_width=“fill_parent”
  4.     android:layout_height=“fill_parent”
  5.     android:orientation=“vertical”
  6.     android:gravity=“bottom|center_horizontal”>
  7.     <Button
  8.         android:layout_width=“wrap_content”
  9.         android:layout_height=“wrap_content”
  10.         android:text=“按钮1”/>
  11.     <Button
  12.         android:layout_width=“wrap_content”
  13.         android:layout_height=“wrap_content”
  14.         android:text=“测试按钮2”/>
  15.     <Button
  16.         android:layout_width=“wrap_content”
  17.         android:layout_height=“wrap_content”
  18.         android:text=“按钮3”/>
  19.     <Button
  20.         android:layout_width=“wrap_content”
  21.         android:layout_height=“wrap_content”
  22.         android:text=“测试按钮4”/>
  23.     <Button
  24.         android:layout_width=“wrap_content”
  25.         android:layout_height=“wrap_content”
  26.         android:text=“按钮5”/>
  27. </LinearLayout>

子元素对齐 : 通过修改 android:gravity 属性来控制LinearLayout中子元素的排列情况;

左边的图的属性为 bottom|center_horizontal , 右边的android:gravity的属性值为 right|center_vertical;

 

(2) 三个按钮各自对齐

 

三个水平方向的按钮, 分别左对齐, 居中对齐, 右对齐 :

 


 

要点

水平线性布局 : 最顶层的LinearLayout的orientation是horizontal水平的;

等分三个线性布局 : 第二层的LinearLayout的orientation是vertical垂直的, 并且宽度是fill_parent , 依靠权重分配宽度;

设置按钮对齐方式 : 按钮的android:layout_gravity属性根据需求 left, center, right, 默认为left;

 

代码 : 

 

 

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     android:layout_width=“fill_parent”
  4.     android:layout_height=“fill_parent”
  5.     android:orientation=“horizontal”  >
  6.     <LinearLayout
  7.         android:layout_width=“fill_parent”
  8.         android:layout_weight=“1”
  9.         android:layout_height=“wrap_content”
  10.         android:orientation=“vertical”
  11.         android:background=“#f00”>
  12.         <Button android:layout_width=“wrap_content”
  13.             android:layout_height=“wrap_content”
  14.             android:text=“按钮1”/>
  15.     </LinearLayout>
  16.     <LinearLayout
  17.         android:layout_width=“fill_parent”
  18.         android:layout_weight=“1”
  19.         android:layout_height=“wrap_content”
  20.         android:orientation=“vertical”
  21.         android:background=“#0f0”>
  22.         <Button android:layout_width=“wrap_content”
  23.             android:layout_height=“wrap_content”
  24.             android:text=“按钮2”
  25.             android:layout_gravity=“center”/>
  26.     </LinearLayout>
  27.     <LinearLayout
  28.         android:layout_width=“fill_parent”
  29.         android:layout_weight=“1”
  30.         android:layout_height=“wrap_content”
  31.         android:orientation=“vertical”
  32.         android:background=“#00f”>
  33.         <Button android:layout_width=“wrap_content”
  34.             android:layout_height=“wrap_content”
  35.             android:text=“按钮3”
  36.             android:layout_gravity=“right”/>
  37.     </LinearLayout>
  38. </LinearLayout>

 

二. 相对布局RelativeLayout

 

相对布局容器中, 子组件的位置总是相对兄弟组件,父容器来决定的;

 

1. RelativeLayout支持的属性

 

(1) 对齐方式

 

xml属性 : android:gravity;

设置方法 : setGravity(int);

作用 : 设置布局容器内子元素的对齐方式, 注意与android:layout_gravity区分, 后者是设置组件本身元素对齐方式;

 

(2) 忽略对齐方式

 

xml属性 : android:ignoreGravity;

设置方法 : setIgnoreGravity(int);

作用 : 设置该组件不受gravity属性影响, 因为gravity属性影响容器内所有的组件的对齐方式, 设置了之后, 该组件就可以例外;

 

2. LayoutParams属性

 

(1) 只能设置boolean值的属性

 

这些属性都是相对父容器的, 确定是否在父容器中居中(水平, 垂直), 是否位于父容器的 上下左右 端;

 

是否水平居中 : android:layout_centerHorizontal;

是否垂直居中 : android:layout_centerVertical;

是否位于中央 : android:layout_centerInParent;

 

是否底端对齐 : android:layout_alignParentBottom;

是否顶端对齐 : android:layout_alignParentTop;

是否左边对齐 : android:layout_alignParentLeft;

是否右边对齐 : android:layout_alignParentRight;

 

(2) 只能设置其它组件id的属性

 

位于所给id组件左侧 : android:layout_toLeftOf;

位于所给id组件右侧 : android:layout_toRightOf;

位于所给id组件的上边 : android:layout_above;

位于所给id组件的下方 : android:layout_below;

 

与所给id组件顶部对齐 : android:layout_alignTop;

与所给id组件底部对齐 : android:layout_alignBottom;

与所给id组件左边对齐 : android:layout_alignLeft;

与所给id组件右边对齐 : android:layout_alignRight;

 

3. 梅花布局效果 

 

五个按钮排成梅花形状, 梅花处于正中心, 效果图如下 :

 

 

 

两个按钮, 如果只有 android:layout_above=”@+id/bt1″ 会是这种情况 : 


加上 android:layout_alignLeft=”@+id/bt1″就会成为这种情况 : 


 

 

要点

注意每个组件的属性, 先要确定方位, 在进行对齐, 组件左边界对齐, 组件上边界对齐;

 

代码 : 

 

 

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     android:layout_width=“fill_parent”
  4.     android:layout_height=“fill_parent” >
  5.     <Button
  6.         android:id=“@+id/bt1”
  7.         android:layout_width=“wrap_content”
  8.         android:layout_height=“wrap_content”
  9.         android:text=“按钮1”
  10.         android:layout_centerInParent=“true”/>
  11.     <Button
  12.         android:id=“@+id/bt2”
  13.         android:layout_width=“wrap_content”
  14.         android:layout_height=“wrap_content”
  15.         android:text=“按钮2”
  16.         android:layout_above=“@+id/bt1”
  17.         android:layout_alignLeft=“@+id/bt1”/>
  18.     <Button
  19.         android:id=“@+id/bt3”
  20.         android:layout_width=“wrap_content”
  21.         android:layout_height=“wrap_content”
  22.         android:text=“按钮3”
  23.         android:layout_centerInParent=“true”
  24.         android:layout_below=“@+id/bt1”
  25.         android:layout_alignLeft=“@+id/bt1”/>
  26.     <Button
  27.         android:id=“@+id/bt4”
  28.         android:layout_width=“wrap_content”
  29.         android:layout_height=“wrap_content”
  30.         android:text=“按钮4”
  31.         android:layout_centerInParent=“true”
  32.         android:layout_toLeftOf=“@+id/bt1”
  33.         android:layout_alignTop=“@+id/bt1”/>
  34.     <Button
  35.         android:id=“@+id/bt5”
  36.         android:layout_width=“wrap_content”
  37.         android:layout_height=“wrap_content”
  38.         android:text=“按钮5”
  39.         android:layout_centerInParent=“true”
  40.         android:layout_toRightOf=“@+id/bt1”
  41.         android:layout_alignTop=“@+id/bt1”/>
  42. </RelativeLayout>

 

 

4. 相对布局常用方法

(1) 获取屏幕中一个组件的位置

 

创建数组 : 要先创建一个整型数组, 数组大小2位; 这个数组传入getLocationOnScreen()方法;

将位置信息传入数组 : 可以调用View.getLocationOnScreen()方法, 返回的是一个数组 int[2], int[0] 是横坐标, int[1] 是纵坐标;

 

 

  1. //获取组件
  2. Button b = (Button) this.findViewById(R.id.Button01);
  3. //创建数组, 将该数组传入getLocationOnScreen()方法
  4. int locations[] = new int[2];
  5. //获取位置信息
  6. b.getLocationOnScreen(locations);
  7. //获取宽度
  8. int width = locations[0];
  9. //获取高度
  10. int height = locations[1];

 

(2) LayoutParams的使用设置所有属性

 

属性设置方法少 : Android SDK中View类只提供了很少用于设置属性的方法,大多数属性没有直接对应的获得和设置属性值的方法, 看起来貌似不是很好用;

使用LayoutParams设置属性值 : Android中可以对任何属性进行设置, 这里我们需要一个LayoutParams对象, 使用这个LayoutParams.addRule()方法, 可以设置所有组件的属性值; 设置完之后调用View.setLayoutParams()方法, 传入刚才创建的LayoutParams对象, 并更新View的相应的LayoutParams属性值, 向容器中添加该组件;

 

代码中动态设置布局属性

a. 创建LayoutParams对象

b. 调用LayoutParams对象的addRule()方法设置对应属性;

c. 调用View.setLayoutParams()方法将设置好的LayoutParams对象设置给组件;

d. 调用addView方法将View对象设置到布局中去;

 

使用代码设置android:layout_toRightOf 和 android:layout_below属性 : 

 

 

  1. //装载布局文件
  2. RelativeLayout relativeLayout = (RelativeLayout) getLayoutInflater().inflate(R.layout.relative, null);
  3. //装载要动态添加的布局文件
  4. Button button = (Button) relativeLayout.findViewById(R.id.bt1);
  5. //创建一个LayoutParams对象
  6. LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
  7. //设置android:layout_toRightOf属性
  8. layoutParams.addRule(RelativeLayout.RIGHT_OF, R.id.bt2);
  9. //设置android:layout_below
  10. layoutParams.addRule(RelativeLayout.BELOW, R.id.bt2);
  11. //更新Button按钮的属性
  12. button.setLayoutParams(layoutParams);
  13. //向布局中动态添加按钮
  14. relativeLayout.addView(button);

 

 

三. 帧布局FrameLayout

帧布局容器为每个组件创建一个空白区域, 一个区域成为一帧, 这些帧会根据FrameLayout中定义的gravity属性自动对齐;

 

1. 绘制霓虹灯布局

 

绘制一个霓虹灯效果的层叠布局, 如下图 : 

 


 

要点 : 

后挡前 : 后面的View组件会遮挡前面的View组件,越在前面, 被遮挡的概率越大;

界面居中 : 将所有的TextView组件的对齐方式 android:layout_gravity 设置为center;

正方形 : 所有的TextView都设置android:height 和 android:width 属性, 用来设置其宽高, 这里设置成正方形, 宽高一样, 后面的组件比前面的边长依次少40;

颜色 : 每个TextView的背景都设置成不一样的;

 

代码 : 

 

 

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <FrameLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     android:layout_width=“match_parent”
  4.     android:layout_height=“match_parent” >
  5.     <TextView
  6.         android:id=“@+id/tv_1”
  7.         android:layout_width=“wrap_content”
  8.         android:layout_height=“wrap_content”
  9.         android:layout_gravity=“center”
  10.         android:width=“320px”
  11.         android:height=“320px”
  12.         android:background=“#f00”/>
  13.     <TextView
  14.         android:id=“@+id/tv_2”
  15.         android:layout_width=“wrap_content”
  16.         android:layout_height=“wrap_content”
  17.         android:layout_gravity=“center”
  18.         android:width=“280px”
  19.         android:height=“280px”
  20.         android:background=“#0f0”/>
  21.     <TextView
  22.         android:id=“@+id/tv_3”
  23.         android:layout_width=“wrap_content”
  24.         android:layout_height=“wrap_content”
  25.         android:layout_gravity=“center”
  26.         android:width=“240px”
  27.         android:height=“240px”
  28.         android:background=“#00f”/>
  29.     <TextView
  30.         android:id=“@+id/tv_4”
  31.         android:layout_width=“wrap_content”
  32.         android:layout_height=“wrap_content”
  33.         android:layout_gravity=“center”
  34.         android:width=“200px”
  35.         android:height=“200px”
  36.         android:background=“#ff0”/>
  37.     <TextView
  38.         android:id=“@+id/tv_5”
  39.         android:layout_width=“wrap_content”
  40.         android:layout_height=“wrap_content”
  41.         android:layout_gravity=“center”
  42.         android:width=“160px”
  43.         android:height=“160px”
  44.         android:background=“#f0f”/>
  45.     <TextView
  46.         android:id=“@+id/tv_6”
  47.         android:layout_width=“wrap_content”
  48.         android:layout_height=“wrap_content”
  49.         android:layout_gravity=“center”
  50.         android:width=“120px”
  51.         android:height=“120px”
  52.         android:background=“#0ff”/>
  53. </FrameLayout>

 

.

作者 :万境绝尘 

转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/18964835

.

 

2. 使用代码使上面的霓虹灯效果动起来

 

(1) 图片效果 

 


 

(2) 颜色资源

 

创建颜色资源, 在跟节点<resources>下面创建<color>子节点, color属性标签 name 自定义, 子文本为颜色代码;

 

(3) 定时器控制handler

 

创建Handler对象, 实现handleMessage()方法, 在这个方法中循环设置 TextView对象的颜色变量, 使用color[(i + currentColor)%colors.length]每调用一次, 就将所有的TextView颜色依次调换一次;

在onCreate()方法中, 开启一个定时器Timer, 每隔0.2s就调用一个handler中的方法, 这样动态的霓虹灯代码就显示出来了.

 

(4) 代码

 

颜色资源代码 : 

 

 

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <resources>
  3.     <color name = “color1”>#f00</color>
  4.     <color name = “color2”>#0f0</color>
  5.     <color name = “color3”>#00f</color>
  6.     <color name = “color4”>#ff0</color>
  7.     <color name = “color5”>#f0f</color>
  8.     <color name = “color6”>#0ff</color>
  9. </resources>

 

 

代码 : 

 

 

  1. package com.example.framelayout;
  2. import java.util.Timer;
  3. import java.util.TimerTask;
  4. import android.app.Activity;
  5. import android.os.Bundle;
  6. import android.os.Handler;
  7. import android.os.Message;
  8. import android.widget.TextView;
  9. public class MainActivity extends Activity {
  10.     //这个变量用来控制霓虹灯颜色变化
  11.     private int currentColor = 0;
  12.     //颜色对应的资源id
  13.     final int[] colors = new int[]{
  14.             R.color.color1,
  15.             R.color.color2,
  16.             R.color.color3,
  17.             R.color.color4,
  18.             R.color.color5,
  19.             R.color.color6
  20.     };
  21.     //View组件对应的资源id
  22.     final int[] names = new int[]{
  23.             R.id.tv_1,
  24.             R.id.tv_2,
  25.             R.id.tv_3,
  26.             R.id.tv_4,
  27.             R.id.tv_5,
  28.             R.id.tv_6
  29.     };
  30.     //组件数组
  31.     TextView[] views = new TextView[names.length];
  32.     //定义这个Handler, 为了在定时器中固定调用handleMessage方法
  33.     Handler handler = new Handler(){
  34.         public void handleMessage(Message msg) {
  35.             if(msg.what == 0x123){
  36.                 for(int i = 0; i < names.length; i ++){
  37.                     views[i].setBackgroundResource(colors[(i + currentColor) % names.length]);
  38.                 }
  39.                 currentColor ++;
  40.             }
  41.         };
  42.     };
  43.     @Override
  44.     public void onCreate(Bundle savedInstanceState) {
  45.         super.onCreate(savedInstanceState);
  46.         setContentView(R.layout.frame);
  47.         //初始化组件数组
  48.         for(int i = 0; i < names.length; i ++){
  49.             views[i] = (TextView) findViewById(names[i]);
  50.         }
  51.         //每隔0.2秒更换一次颜色
  52.         new Timer().schedule(new TimerTask() {
  53.             @Override
  54.             public void run() {
  55.                 handler.sendEmptyMessage(0x123);
  56.             }
  57.         }, 0200);
  58.     }
  59. }

 

3. 三个水平方向的按钮分别左对齐,居中对齐,右对齐

 


 

要点 : 给FrameLayout中的三个按钮分别设置 不同的layout_gravity,left ,center_horizontal,right, 就能设置成上图的样式;

 

代码 : 

 

 

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <FrameLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     android:layout_width=“match_parent”
  4.     android:layout_height=“match_parent” >
  5.     <Button
  6.         android:layout_width=“wrap_content”
  7.         android:layout_height=“wrap_content”
  8.         android:text=“按钮1”
  9.         android:layout_gravity=“left”/>
  10.     <Button
  11.         android:layout_width=“wrap_content”
  12.         android:layout_height=“wrap_content”
  13.         android:text=“按钮2”
  14.         android:layout_gravity=“center_horizontal”/>
  15.     <Button
  16.         android:layout_width=“wrap_content”
  17.         android:layout_height=“wrap_content”
  18.         android:text=“按钮3”
  19.         android:layout_gravity=“right”/>
  20. </FrameLayout>

 

 

四. 表格布局TableLayout

 

1. 表格布局的一些概念

 

继承关系 : 表格布局继承了LinearLayout, 其本质是线性布局管理器; 

控制组件 : 表格布局采用 行, 列 形式管理子组件, 但是并不需要声明有多少 行列, 只需要添加TableRow 和 组件 就可以控制表格的行数和列数, 这一点与网格布局有所不同, 网格布局需要指定行列数;

增加行的方法

a. TableRow增加行列 : 向TableLayout中添加一个TableRow,一个TableRow就是一个表格行, 同时TableRow也是容器, 可以向其中添加子元素, 每添加一个组件, 就增加了一列;

b. 组件增加行 : 如果直接向TableLayout中添加组件, 就相当于直接添加了一行;

 

列宽 : TableLayout中, 列的宽度由该列最宽的单元格决定, 整个表格的宽度默认充满父容器本身;

 

2. 单元格行为方式 

 

(1) 行为方式概念

 

a. 收缩 :Shrinkable, 如果某列被设为Shrinkable, 那么该列所有单元格宽度可以被收缩, 保证表格能适应父容器的宽度;

b. 拉伸 :Stretchable, 如果某列被设为Stretchable, 那么该列所有单元格的宽度可以被拉伸, 保证表格能完全填满表格剩余空间;

d. 隐藏 :Collapsed, 如果某列被设置成Collapsed, 那么该列所有单元格会被隐藏;

 

(2) 行为方式属性

 

a. 隐藏

xml属性 : android:collapsedColumns;

设置方法 : setColumnCollapsed(int, boolean);

作用 : 设置需要被隐藏的列的序号, 在xml文件中, 如果隐藏多列, 多列序号间用逗号隔开;

 

b. 拉伸

xml属性 : android:stretchColumns;

设置方法 : setStretchAllColumns(boolean);

作用 : 设置允许被拉伸的列的序列号, xml文件中多个序列号之间用逗号隔开;

 

c. 收缩

xml属性 : android:shrinkableColumns;

设置方法 : setShrinkableAllColumns(boolean);

作用 : 设置允许被收缩的列的序号, xml文件中, 多个序号之间可以用逗号隔开;

 

3. 表格布局实例

 


 

实现要点

独自一行按钮 : 向TableLayout中添加按钮, 这个按钮就会独自占据一行;

收缩按钮: 在TableLayout标签中,设置android:stretchable属性标签, 属性值是要收缩的列, 注意,列标从0开始;

拉伸按钮 : 在TableLayout标签中,设置android:shrinkable属性标签, 属性值是要拉伸的列, 注意, 列表从0开始;

 

代码 : 

 

 

  1. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  2.     xmlns:tools=“http://schemas.android.com/tools”
  3.     android:orientation=“vertical”
  4.     android:layout_width=“match_parent”
  5.     android:layout_height=“match_parent” >
  6.     <!– LinearLayout默认是水平的, 这里设置其方向为垂直 –>
  7.     <!– 表格布局, 第2列允许收缩, 第3列允许拉伸, 注意这里行列的计数都是从0开始的 –>
  8.     <TableLayout
  9.         android:layout_width=“fill_parent”
  10.         android:layout_height=“wrap_content”
  11.         android:shrinkColumns=“1”
  12.         android:stretchColumns=“2”>
  13.         <!– 向TableLayout中直接添加组件, 独占一行 –>
  14.         <Button
  15.             android:layout_width=“fill_parent”
  16.             android:layout_height=“wrap_content”
  17.             android:text=“独自一行的按钮”/>
  18.         <TableRow>
  19.             <Button
  20.                 android:layout_width=“wrap_content”
  21.                 android:layout_height=“wrap_content”
  22.                 android:text=“普通的按钮”/>
  23.             <Button
  24.                 android:layout_width=“wrap_content”
  25.                 android:layout_height=“wrap_content”
  26.                 android:text=“收缩的按钮”/>
  27.             <Button
  28.                 android:layout_width=“wrap_content”
  29.                 android:layout_height=“wrap_content”
  30.                 android:text=“拉伸的按钮”/>
  31.         </TableRow>
  32.     </TableLayout>
  33.     <!– 第二个按钮会隐藏掉 –>
  34.     <TableLayout
  35.         android:layout_width=“fill_parent”
  36.         android:layout_height=“wrap_content”
  37.         android:collapseColumns=“1”>
  38.         <Button
  39.             android:layout_width=“fill_parent”
  40.             android:layout_height=“wrap_content”
  41.             android:text=“独自一行的按钮”/>
  42.         <TableRow >
  43.             <Button
  44.                 android:layout_width=“wrap_content”
  45.                 android:layout_height=“wrap_content”
  46.                 android:text=“普通按钮1”/>
  47.             <Button
  48.                 android:layout_width=“wrap_content”
  49.                 android:layout_height=“wrap_content”
  50.                 android:text=“普通按钮2”/>
  51.             <Button
  52.                 android:layout_width=“wrap_content”
  53.                 android:layout_height=“wrap_content”
  54.                 android:text=“普通按钮3”/>
  55.         </TableRow>
  56.     </TableLayout>
  57.     <!– 指定第二列和第三列可以被拉伸 –>
  58.     <TableLayout
  59.         android:layout_height=“wrap_content”
  60.         android:layout_width=“fill_parent”
  61.         android:stretchColumns=“1,2”>
  62.         <Button
  63.             android:layout_width=“fill_parent”
  64.             android:layout_height=“wrap_content”
  65.             android:text=“独自占一行的按钮”/>
  66.         <TableRow >
  67.             <Button
  68.                 android:layout_width=“wrap_content”
  69.                 android:layout_height=“wrap_content”
  70.                 android:text=“普通按钮1”/>
  71.             <Button
  72.                 android:layout_width=“wrap_content”
  73.                 android:layout_height=“wrap_content”
  74.                 android:text=“拉伸的按钮”/>
  75.             <Button
  76.                 android:layout_width=“wrap_content”
  77.                 android:layout_height=“wrap_content”
  78.                 android:text=“拉伸的按钮”/>
  79.         </TableRow>
  80.         <TableRow >
  81.             <Button
  82.                 android:layout_width=“wrap_content”
  83.                 android:layout_height=“wrap_content”
  84.                 android:text=“普通的按钮”/>
  85.             <Button
  86.                 android:layout_width=“wrap_content”
  87.                 android:layout_height=“wrap_content”
  88.                 android:text=“拉伸的按钮”/>
  89.         </TableRow>
  90.     </TableLayout>
  91. </LinearLayout>

 

五. 网格布局

 

1. 网格布局介绍

 

网格布局时Android4.0版本才有的, 在低版本使用该布局需要导入对应支撑库;

GridLayout将整个容器划分成rows * columns个网格, 每个网格可以放置一个组件. 还可以设置一个组件横跨多少列, 多少行. 不存在一个网格放多个组件情况;

 

2. 网格布局常用属性

 

(1) 设置对齐模式

 

xml属性 : android:alignmentMode;

设置方法 : setAlignmentMode(int);

作用 : 设置网格布局管理器的对齐模式

 

(2) 设置列数

 

xml属性 : android:columnCount;

设置方法 : setColumnCount(int);

作用 : 设置该网格布局的列数;

 

(3) 设置是否保留列序列号

 

xml属性 : android:columnOrderPreserved;

设置方法 : setColumnOrderPreserved(boolean);

作用 : 设置网格容器是否保留列序列号;

 

(4) 设置行数

 

xml属性 : android:rowCount;

设置方法 : setRowCount(int);

作用 : 设置该网格的行数;

 

(5) 设置是否保留行序列号

 

xml属性 : android:rowOrderPreserved;

设置方法 : setRowOrderPreserved(int);

作用 : 设置该网格容器是否保留行序列号;

 

(6) 页边距

 

xml属性 : android:useDefaultMargins;

设置方法 : setUseDefaultMargins(boolean);

作用 : 设置该布局是否使用默认的页边距;

 

3. GridLayout的LayoutParams属性

 

(1) 设置位置列

 

xml属性 : android:layout_column;

作用 : 设置子组件在GridLayout的哪一列;

 

(2) 横向跨列

 

xml属性 : android:layout_columnSpan;

作用 : 设置该子组件在GridLayout中横向跨几列;

 

(3) 占据空间方式

 

xml属性 : android:layout_gravity;

设置方法 : setGravity(int);

作用 : 设置该组件采用何种方式占据该网格的空间;

 

(4) 设置行位置

 

xml属性 : android:layout_row;

作用 : 设置该子组件在GridLayout的第几行;

 

(5) 设置横跨行数

 

xml属性 : android:layout_rowSpan;

作用 : 设置该子组件在GridLayout纵向横跨几行;

 

4. 实现一个计算机界面

 

 

 


 

(1) 布局代码

 

设置行列 : 设置GridLayout的android:rowCount为6, 设置android:columnCount为4, 这个网格为 6行 * 4列 的;

设置横跨四列 : 设置TextView和按钮横跨四列android:layout_columnSpan 为4, 列的合并 就是占了一行;

textView的一些设置

设置textView中的文本与边框有5像素间隔 : android:padding = “5px”;

 

代码 : 

 

 

  1. <GridLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  2.     xmlns:tools=“http://schemas.android.com/tools”
  3.     android:layout_width=“match_parent”
  4.     android:layout_height=“match_parent”
  5.     android:rowCount=“6”
  6.     android:columnCount=“4”
  7.     android:id=“@+id/root”>
  8.     <!–
  9.         定义一个  6行 * 4列 GridLayout, 在里面定义两个组件
  10.         两个组件都横跨4列, 单独占一行
  11.      —>
  12.     <TextView
  13.         android:layout_width=“match_parent”
  14.         android:layout_height=“wrap_content”
  15.         android:layout_columnSpan=“4”
  16.         android:textSize=“50sp”
  17.         android:layout_marginLeft=“4px”
  18.         android:layout_marginRight=“4px”
  19.         android:padding=“5px”
  20.         android:layout_gravity=“right”
  21.         android:background=“#eee”
  22.         android:textColor=“#000”
  23.         android:text=“0”/>
  24.     <Button
  25.         android:layout_width=“match_parent”
  26.         android:layout_height=“wrap_content”
  27.         android:layout_columnSpan=“4”
  28.         android:text=“清除”/>
  29. </GridLayout>

 

(2) Activity代码

 

将组件设置给GridLayout网格流程

指定组件所在行 : GridLayout.SpecrowSpec = GridLayout.spec(int)

指定组件所在列 : GridLayout.SpeccolumnSpec = GridLayout.spec(int);

创建LayoutParams对象 : GridLayout.LayoutParams params =new GridLayout.LayoutParams(rowSpec, columnSpec);

指定组件占满容器 : params.setGravity(Gravity.FILL);

将组件添加到布局中 : gridLayout.addView(view, params);

 

代码 : 

 

 

  1. package com.example.caculator;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.view.Gravity;
  5. import android.widget.Button;
  6. import android.widget.GridLayout;
  7. import android.widget.GridLayout.LayoutParams;
  8. import android.widget.GridLayout.Spec;
  9. public class MainActivity extends Activity {
  10.     private GridLayout gridLayout;
  11.     //需要放到按钮上的字符串
  12.     String chars[] = new String[]{
  13.         “7”“8”“9”“/”,
  14.         “4”“5”“6”“*”,
  15.         “1”“2”“3”“-“,
  16.         “.”“0”“=”“+”
  17.     };
  18.     @Override
  19.     public void onCreate(Bundle savedInstanceState) {
  20.         super.onCreate(savedInstanceState);
  21.         setContentView(R.layout.activity_main);
  22.         gridLayout = (GridLayout) findViewById(R.id.root);
  23.         for(int i = 0; i < chars.length; i ++){
  24.             Button button = new Button(this);
  25.             button.setText(chars[i]);
  26.             button.setTextSize(40);
  27.             //指定组件所在行
  28.             Spec rowSpec = GridLayout.spec(i / 4 + 2);
  29.             //指定组件所在列
  30.             Spec columnSpec = GridLayout.spec(i % 4);
  31.             //生成LayoutParams对象
  32.             LayoutParams layoutParams = new LayoutParams(rowSpec, columnSpec);
  33.             //指定组件充满网格
  34.             layoutParams.setGravity(Gravity.FILL);
  35.             //将组件设置给GridLayout
  36.             gridLayout.addView(button, layoutParams);
  37.         }
  38.     }
  39. }

 

 

六. 绝对布局 AbsoluteLayout

 

1. 绝对布局介绍 

 

绝对布局特点 : 在绝对布局中,组件位置通过x, y坐标来控制, 布局容器不再管理组件位置, 大小, 这些都可以自定义; 

绝对布局使用情况 : 绝对布局不能适配不同的分辨率, 屏幕大小, 这种布局已经过时, 如果只为一种设备开发这种布局的话, 可以考虑使用这种布局;

 

2. 绝对布局的属性

 

android:layout_x: 指定组件的x坐标;

android:layout_y: 指定组件的y坐标;

 

android:layout_width 是指定宽度是否充满父容器, 或者仅仅包含子元素的,

android:width : 指定组件的宽度, 可以指定一个 数字 + 单位 , 如 100px 或者 100dp; 同理 android:layout_height 和 android:height;

 

3. 各种单位介绍

 

px : 像素, 每个px对应屏幕上的一个点;

dip/dp : device independent pixels, 设备的独立像素, 这种单位基于屏幕密度, 在每英寸160点的显示器上 1dp = 1px, 随着屏幕密度改变, dp 与 px 换算会发生改变;

sp : scale pixels, 比例像素, 处理字体的大小, 可以根据用户字体大小进行缩放;

in : 英寸, 标准长度单位

mm : 毫米, 标准长度单位

pt : 磅, 标准长度单位, 1/72英寸;

 

 

七. Android 分辨率 dip 与 px 转换

 


 

 

1. 术语介绍

 

px : pixel, 像素, 屏幕分辨率就是像素, 分辨率用 宽度 * 长度 表示, 分辨率不是长宽比, Android中一般不直接处理分辨率;

density : 密度, 是以分辨率为基础, 沿长宽方向排列的像素,密度低的屏幕像素少,密度高的屏幕像素多; 如果以像素为单位, 同一个按钮在高密度屏幕 要比 在低密度屏幕要大.

dip : device independent pixel, 设备独立像素, 程序用dip来定义界面元素,dip与实际密度无关.

 

2. 屏幕密度与大小

 

手机屏幕密度分类 : 高 hdpi 240 , 中 mdpi 160, 小 ldpi 120, 在res下有对应密度的标签资源, 注意这些资源与屏幕大小无关;

手机屏幕大小分类 : 大屏幕 4.8英寸以上, 普通屏幕 3.0 ~ 4.0英寸, 小屏幕 2.6 ~ 3.0英寸;

基准屏幕 : 正常尺寸, 与中密度120dpi,HAVG 320 * 480 是基准屏幕, 中密度 px == dip;

 

3. dip 与 px 换算

 

dip -> px :px = dip * (densityDpi / 160);

px -> dip :dip = px / (densityDpi / 160);

 

在中密度 mdpi 下, dip == px;

在高密度 hdpi 下, px > dip;

在低密度 ldpi 下, px < dip;

 

获取密度 :DisplayMetrics dm = getResources().getDisplayMetrics();

密度 : int density =dm.densityDpi;

像素 :dm.widthPixel * dm.heightPixel;  

 

 

七. 获取View对象宽高

 

如果在Activity中直接调用View组件的宽高, 获得的宽高一定是0;

重写 onWindowFocusChanged() .方法, 在这个方法中获取宽高, 就能成功获取到view组件的准确宽高值;

参考 : http://blog.csdn.net/sodino/article/details/10086633

 

 

作者 :万境绝尘 

转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/18964835

 

(转)setScale,preScale和postScale的区别

上面讲到,Matrix由3*3矩阵中9个值来决定。而我们对Matrix的所有设置,也是对这9个值的各种不同的改变,来达到我们想要的效果。

下面是Matrix3*3的矩阵结构

  1. {MSCALE_X,MSKEW_X,MTRANS_X,
  2. MSKEW_Y,MSCALE_Y,MTRANS_Y,
  3. MPERSP_0,MPERSP_1,MPERSP_2}

一、首先介绍Scale缩放的控制

scale就是缩放,我们调用Matrix的setScale、preScale、postScale,实际在内部,就是通过修改MSCALE_X和MSCALE_Y来实现的。

下面就是一个简单的例子

  1. public class MatrixTestActivity extends Activity {
  2.     private int screenWidth;
  3.     private int screenHeight;
  4.     private int bitmapWidth;
  5.     private int bitmapHeight;
  6.     private float baseScale;
  7.     private float originalScale;
  8.     @Override
  9.     public void onCreate(Bundle savedInstanceState) {
  10.         super.onCreate(savedInstanceState);
  11.         setContentView(R.layout.main);
  12.         // 获得屏幕的宽高
  13.         screenWidth = getWindow().getWindowManager().getDefaultDisplay().getWidth();
  14.         screenHeight = getWindow().getWindowManager().getDefaultDisplay().getHeight();
  15.         // 加载Imageview和获得图片的信息
  16.         final ImageView imageView = (ImageView) findViewById(R.id.imgView);
  17.         final Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.a);
  18.         bitmapWidth = bitmap.getWidth();
  19.         bitmapHeight = bitmap.getHeight();
  20.         // 计算缩放比,因为如果图片的尺寸超过屏幕,那么就会自动匹配到屏幕的尺寸去显示。
  21.         // 那么,我们就不知道图片实际上在屏幕上显示的宽高,所以先计算需要全部显示的缩放比,
  22.         // 在去计算图片显示时候的实际宽高,然后,才好进行下一步的缩放。
  23.         // 要不然,会导致缩小和放大没效果,或者内存泄漏等等
  24.         float scaleX = screenWidth / (float) bitmapWidth;
  25.         float scaleY = screenHeight / (float) bitmapHeight;
  26.         baseScale = Math.min(scaleX, scaleY);// 获得缩放比例最大的那个缩放比,即scaleX和scaleY中小的那个
  27.         originalScale = baseScale;
  28.         final Matrix matrix = new Matrix();
  29.         matrix.setScale(originalScale, originalScale);
  30.         // 关于setScale和preScale和postScale的区别以后再说
  31.         // matrix.preScale(originalScale, originalScale);
  32.         // matrix.postScale(originalScale, originalScale);
  33.         Bitmap bitmap2 = Bitmap
  34.                 .createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, matrix, false);
  35.         imageView.setImageBitmap(bitmap2);
  36.         final Button scale_btn = (Button) findViewById(R.id.scale_btn);
  37.         final EditText scale_text = (EditText) findViewById(R.id.scale_editView);
  38.         scale_btn.setOnClickListener(new View.OnClickListener() {
  39.             public void onClick(View v) {
  40.                 String scaleStr = scale_text.getText().toString();
  41.                 if (scaleStr == null || “”.equals(scaleStr))
  42.                     return;
  43.                 float scale = 0.0f;
  44.                 try {
  45.                     scale = Float.parseFloat(scaleStr);
  46.                 } catch (NumberFormatException e) {
  47.                     return;
  48.                 }
  49.                 matrix.reset();
  50.                 originalScale = scale * originalScale;//看
  51.                 if (originalScale < 0.05 ){
  52.                     originalScale=0.05f;
  53.                 }
  54.                 if(originalScale > baseScale){
  55.                     originalScale=baseScale;
  56.                 }
  57.                 matrix.setScale(originalScale, originalScale);
  58.                 Bitmap bitmapChange = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight,
  59.                         matrix, false);
  60.                 imageView.setImageBitmap(bitmapChange);
  61.             }
  62.         });
  63.     }
  64. }

可以发现,对于Scale的设置,Matrix提供了3中不同的方式来设置。

setScale、preScale、postScale。

这三种方法之间有什么区别呢?看下面的。

 

二、set….、pre….、post…的区别

1、setScale(sx,sy),首先会将该Matrix设置为对角矩阵,即相当于调用reset()方法,然后在设置该Matrix的MSCALE_X和MSCALE_Y直接设置为sx,sy的值

2、preScale(sx,sy),不会重置Matrix,而是直接与Matrix之前的MSCALE_X和MSCALE_Y值结合起来(相乘),M’ = M * S(sx, sy)。

3、postScale(sx,sy),不会重置Matrix,而是直接与Matrix之前的MSCALE_X和MSCALE_Y值结合起来(相乘),M’ = S(sx, sy) * M。

preScale和post都是与之前的Matrix结合起来,那它们之间又有什么区别呢?

举几个例子测试下关于pre….和post….的区别:

对一个Matrix如下设置

1、pre….的执行顺序

  1. Matrix matrix=new Matrix();
  2. float[] points=new float[]{10.0f,10.0f};
  3. matrix.preScale(2.0f, 3.0f);//
  4. matrix.preTranslate(8.0f,7.0f);//
  5. matrix.mapPoints(points);
  6. Log.i(“test”, points[0]+””);
  7. Log.i(“test”, points[1]+””);

结果为点坐标为(36.0,51.0)

可以得出结论,进行变换的顺序是先执行preTranslate(8.0f,7.0f),在执行的preScale(2.0f,3.0f)。这也是为什么有的人比喻为pre…是向后生长的,即对于一个Matrix的设置中,

所有pre….是倒着向后执行的。

 

2、post…的执行顺序

  1. Matrix matrix=new Matrix();
  2. float[] points=new float[]{10.0f,10.0f};
  3. matrix.postScale(2.0f, 3.0f);//
  4. matrix.postTranslate(8.0f,7.0f);//
  5. matrix.mapPoints(points);
  6. Log.i(“test”, points[0]+””);
  7. Log.i(“test”, points[1]+””);

结果为点坐标为(28.0,37.0)

可以得出结论,进行变换的顺序是先执行postScale(2.0f,3.0f),在执行的postTranslate(8.0f,7.0f)。这
也是为什么有的人比喻为post…是向前生长的,即对于一个Matrix的设置中,所有post….是顺着向前执行的。

 

3、当pre和post交替出现的执行顺序

  1. Matrix matrix=new Matrix();
  2. float[] points=new float[]{10.0f,10.0f};
  3. matrix.postScale(2.0f, 3.0f);
  4. matrix.preRotate(90);
  5. matrix.mapPoints(points);
  6. Log.i(“test”, points[0]+””);
  7. Log.i(“test”, points[1]+””);

结果为点坐标为(-20.0,30.0)

将pre…和post顺序换一下

  1. Matrix matrix=new Matrix();
  2. float[] points=new float[]{10.0f,10.0f};
  3. matrix.preRotate(90);
  4. matrix.postScale(2.0f, 3.0f);
  5. matrix.mapPoints(points);
  6. Log.i(“test”, points[0]+””);
  7. Log.i(“test”, points[1]+””);

结果为点坐标依旧为为(-20.0,30.0)

可见,总是pre先执行的。在看下面的:

  1. Matrix matrix = new Matrix();
  2. float[] points = new float[] { 10.0f, 10.0f };
  3. matrix.postScale(2.0f, 3.0f);// 第1步
  4. matrix.preRotate(90);// 第2步
  5. matrix.postTranslate(8.0f, 7.0f);// 第3步
  6. matrix.preScale(1.5f, 2.5f);// 第4步
  7. matrix.mapPoints(points);
  8. Log.i(“test”, points[0] + “”);
  9. Log.i(“test”, points[1] + “”);

结果为点坐标依旧为为(-42.0,52.0)
经过前面的结论和推算,可以发现执行的顺序是   4—-2—-1—3

 

在看下面的,增加了setScale的一段代码:

  1. Matrix matrix = new Matrix();
  2. float[] points = new float[] { 10.0f, 10.0f };
  3. matrix.postScale(2.0f, 3.0f);// 第1步
  4. matrix.preRotate(90);// 第2步
  5. matrix.setScale(1.4f, 2.6f);// 第3步
  6. matrix.postTranslate(8.0f, 7.0f);// 第4步
  7. matrix.preScale(1.5f, 2.5f);// 第5步
  8. matrix.mapPoints(points);
  9. Log.i(“test”, points[0] + “”);
  10. Log.i(“test”, points[1] + “”);

结果为点坐标依旧为为(29.0,72.0)
经过计算,可以发现,在第3步setScale之前的第1、2步根本就没有用了,直接被第3步setScale覆盖,在从第3开始执行的。

顺序为2—1—-3—-5—-4,因为2、1被覆盖了,所以没有效果,相当于直接执行3—–5—-4

 

总结:最后可以得出结论,在对matrix该次变换之前的所有设置中,先检测有没有setScale,如果有,直接跳到setScale那一步开始
执行变换,然后在倒着执行下面所有的pre…变换,在顺着执行所有post….的变换。所以在对Matrix变换设置的时候,一定要注意顺序,不
同的顺序,会有不同的结果。

 

获取Android设备唯一标识码

 

概述

有时需要对用户设备进行标识,所以希望能够得到一个稳定可靠并且唯一的识别码。虽然Android系统中提供了这样设备识别码,但是由于Android系统版本、厂商定制系统中的Bug等限制,稳定性和唯一性并不理想。而通过其他硬件信息标识也因为系统版本、手机硬件等限制存在不同程度的问题。

下面收集了一些“有能力”或“有一定能力”作为设备标识的串码。

DEVICE_ID

这是Android系统为开发者提供的用于标识手机设备的串号,也是各种方法中普适性较高的,可以说几乎所有的设备都可以返回这个串号,并且唯一性良好。

这个DEVICE_ID可以同通过下面的方法获取:

TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); String DEVICE_ID = tm.getDeviceId();

它会根据不同的手机设备返回IMEI,MEID或者ESN码,但在使用的过程中有以下问题:

  • 非手机设备:最开始搭载Android系统都手机设备,而现在也出现了非手机设备:如平板电脑、电子书、电视、音乐播放器等。这些设备没有通话的硬件功能,系统中也就没有TELEPHONY_SERVICE,自然也就无法通过上面的方法获得DEVICE_ID。
  • 权限问题:获取DEVICE_ID需要READ_PHONE_STATE权限,如果只是为了获取DEVICE_ID而没有用到其他的通话功能,申请这个权限一来大才小用,二来部分用户会怀疑软件的安全性。
  • 厂商定制系统中的Bug:少数手机设备上,由于该实现有漏洞,会返回垃圾,如:zeros或者asterisks

MAC ADDRESS

可以使用手机Wifi或蓝牙的MAC地址作为设备标识,但是并不推荐这么做,原因有以下两点:

  • 硬件限制:并不是所有的设备都有Wifi和蓝牙硬件,硬件不存在自然也就得不到这一信息。
  • 获取的限制:如果Wifi没有打开过,是无法获取其Mac地址的;而蓝牙是只有在打开的时候才能获取到其Mac地址。

获取Wifi Mac地址:

获取蓝牙 Mac地址:

Sim Serial Number

装有SIM卡的设备,可以通过下面的方法获取到Sim Serial Number:

TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); String SimSerialNumber = tm.getSimSerialNumber();

注意:对于CDMA设备,返回的是一个空值!

ANDROID_ID

在设备首次启动时,系统会随机生成一个64位的数字,并把这个数字以16进制字符串的形式保存下来,这个16进制的字符串就是ANDROID_ID,当设备被wipe后该值会被重置。可以通过下面的方法获取:

import android.provider.Settings;   String ANDROID_ID = Settings.System.getString(getContentResolver(), Settings.System.ANDROID_ID);

ANDROID_ID可以作为设备标识,但需要注意:

  • 厂商定制系统的Bug:不同的设备可能会产生相同的ANDROID_ID:9774d56d682e549c。
  • 厂商定制系统的Bug:有些设备返回的值为null。
  • 设备差异:对于CDMA设备,ANDROID_ID和TelephonyManager.getDeviceId() 返回相同的值。

Serial Number

Android系统2.3版本以上可以通过下面的方法得到Serial Number,且非手机设备也可以通过该接口获取。

String SerialNumber = android.os.Build.SERIAL;

Installtion ID

以上几种方式都或多或少存在一定的局限性或者Bug,如果并不是确实需要对硬件本身进行绑定,使用自己生成的UUID也是一个不错的选择,因为该方法无需访问设备的资源,也跟设备类型无关。

这种方式的原理是在程序安装后第一次运行时生成一个ID,该方式和设备唯一标识不一样,不同的应用程序会产生不同的ID,同一个程序重新安装也会不同。所以这不是设备的唯一ID,但是可以保证每个用户的ID是不同的。可以说是用来标识每一份应用程序的唯一ID(即Installtion ID),可以用来跟踪应用的安装数量等。

Google Developer Blog提供了这样的一个框架:

public class Installation { private static String sID = null; private static final String INSTALLATION = "INSTALLATION";   public synchronized static String id(Context context) { if (sID == null) { File installation = new File(context.getFilesDir(), INSTALLATION); try { if (!installation.exists()) writeInstallationFile(installation); sID = readInstallationFile(installation); } catch (Exception e) { throw new RuntimeException(e); } } return sID; }   private static String readInstallationFile(File installation) throws IOException { RandomAccessFile f = new RandomAccessFile(installation, "r"); byte[] bytes = new byte[(int) f.length()]; f.readFully(bytes); f.close(); return new String(bytes); }   private static void writeInstallationFile(File installation) throws IOException { FileOutputStream out = new FileOutputStream(installation); String id = UUID.randomUUID().toString(); out.write(id.getBytes()); out.close(); } }

设备唯一ID

上文可以看出,Android系统中并没有可以可靠获取所有厂商设备唯一ID的方法,各个方法都有自己的使用范围和局限性,这也是目前流行的Android系统版本过多,设备也是来自不同厂商,且没有统一标准等原因造成的。

从目前发展来看,Android系统多版本共存还会持续较长的时间,而Android系统也不会被某个设备生产厂商垄断,长远看Android基础系统将会趋于稳定,设备标识也将会作为系统基础部分而标准化,届时这一问题才有望彻底解决。

目前的解决办法,比较可行的是一一适配,在保证大多数设备方便的前提下,如果获取不到,使用其他备选信息作为标识,即自己再封装一个设备ID出来,通过内部算法保证尽量和设备硬件信息相关,以及标识的唯一性。

 

 

android 底层是
Linux
,我们还是用Linux的方法来获取:
1 cpu号:

文件在: /proc/cpuinfo

通过Adb shell 查看:

adb shell cat /proc/cpuinfo

2 mac 地址

文件路径 /sys/class/net/wlan0/address

adb shell  cat /sys/class/net/wlan0/address
xx:xx:xx:xx:xx:aa

这样可以获取两者的序列号,
方法确定,剩下的就是写代码了

以Mac地址为例:

String getMac() {
String macSerial = null;
String str = “”;
try {
Process pp = Runtime.getRuntime().exec(
“cat /sys/class/net/wlan0/address “);
InputStreamReader ir = new InputStreamReader(pp.getInputStream());
LineNumberReader input = new LineNumberReader(ir);
for (; null != str;) {
str = input.readLine();
if (str != null) {
macSerial = str.trim();// 去空格
break;
}
}
} catch (IOException ex) {
// 赋予默认值
ex.printStackTrace();
}
return macSerial;
}

 

 

 

Android 手机上获取物理唯一标识码

唯一标识码这东西在网络应用中非常有用,例如检测是否重复注册之类的。

import android.provider.Settings.Secure;
private String android_id = Secure.getString(getContext().getContentResolver(), Secure.ANDROID_ID);

我们在项目过程中或多或少会使用到设备的唯一识别码,我们希望能够得到一个稳定、可靠的设备唯一识别码。今天我们将介绍几种方式。

1. DEVICE_ID

假设我们确实需要用到真实设备的标识,可能就需要用到DEVICE_ID。在以前,我们的Android设备是手机,这个DEVICE_ID可以同通过TelephonyManager.getDeviceId()获取,它根据不同的手机设备返回IMEI,MEID或者ESN码,但它在使用的过程中会遇到很多问题:

  • 非手机设备: 如果只带有Wifi的设备或者音乐播放器没有通话的硬件功能的话就没有这个DEVICE_ID
  • 权限: 获取DEVICE_ID需要READ_PHONE_STATE权限,但如果我们只为了获取它,没有用到其他的通话功能,那这个权限有点大才小用
  • bug:在少数的一些手机设备上,该实现有漏洞,会返回垃圾,如:zeros或者asterisks的产品

2. MAC ADDRESS

我们也可以通过手机的Wifi或者蓝牙设备获取MAC ADDRESS作为DEVICE ID,但是并不建议这么做,因为并不是所有的设备都有Wifi,并且,如果Wifi没有打开,那硬件设备无法返回MAC ADDRESS.

3. Serial Number

在Android 2.3可以通过android.os.Build.SERIAL获取,非手机设备可以通过该接口获取。

4. ANDROID_ID

ANDROID_ID是设备第一次启动时产生和存储的64bit的一个数,当设备被wipe后该数重置

ANDROID_ID似乎是获取Device ID的一个好选择,但它也有缺陷:

  • 它在Android <=2.1 or Android >=2.3的版本是可靠、稳定的,但在2.2的版本并不是100%可靠的
  • 在主流厂商生产的设备上,有一个很经常的bug,就是每个设备都会产生相同的ANDROID_ID:9774d56d682e549c

5. Installtion ID : UUID

以上四种方式都有或多或少存在的一定的局限性或者bug,在这里,有另外一种方式解决,就是使用UUID,该方法无需访问设备的资源,也跟设备类型无关。

这种方式是通过在程序安装后第一次运行后生成一个ID实现的,但该方式跟设备唯一标识不一样,它会因为不同的应用程序而产生不同的ID,而不是设备唯一ID。因此经常用来标识在某个应用中的唯一ID(即Installtion ID),或者跟踪应用的安装数量。很幸运的,Google Developer Blog提供了这样的一个框架:

public class Installation {
private static String sID = null;
private static final String INSTALLATION = “INSTALLATION”;

public synchronized static String id(Context context) {
if (sID == null) {
File installation = new File(context.getFilesDir(), INSTALLATION);
try {
if (!installation.exists())
writeInstallationFile(installation);
sID = readInstallationFile(installation);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return sID;
}

private static String readInstallationFile(File installation) throws IOException {
RandomAccessFile f = new RandomAccessFile(installation, “r”);
byte[] bytes = new byte[(int) f.length()];
f.readFully(bytes);
f.close();
return new String(bytes);
}

private static void writeInstallationFile(File installation) throws IOException {
FileOutputStream out = new FileOutputStream(installation);
String id = UUID.randomUUID().toString();
out.write(id.getBytes());
out.close();
}
}

总结

综合以上所述,为了实现在设备上更通用的获取设备唯一标识,我们可以实现这样的一个类,为每个设备产生唯一的UUID,以ANDROID_ID为基础,在获取失败时以TelephonyManager.getDeviceId()为备选方法,如果再失败,使用UUID的生成策略。

重申下,以下方法是生成Device ID,在大多数情况下Installtion ID能够满足我们的需求,但是如果确实需要用到Device ID,那可以通过以下方式实现:

import android.content.Context;
import android.content.SharedPreferences;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;

import java.io.UnsupportedEncodingException;
import java.util.UUID;

public class DeviceUuidFactory {
protected static final String PREFS_FILE = “device_id.xml”;
protected static final String PREFS_DEVICE_ID = “device_id”;

protected static UUID uuid;

public DeviceUuidFactory(Context context) {

if( uuid ==null ) {
synchronized (DeviceUuidFactory.class) {
if( uuid == null) {
final SharedPreferences prefs = context.getSharedPreferences( PREFS_FILE, 0);
final String id = prefs.getString(PREFS_DEVICE_ID, null );

if (id != null) {
// Use the ids previously computed and stored in the prefs file
uuid = UUID.fromString(id);

} else {

final String androidId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);

// Use the Android ID unless it’s broken, in which case fallback on deviceId,
// unless it’s not available, then fallback on a random number which we store
// to a prefs file
try {
if (!”9774d56d682e549c”.equals(androidId)) {
uuid = UUID.nameUUIDFromBytes(androidId.getBytes(“utf8”));
} else {
final String deviceId = ((TelephonyManager) context.getSystemService( Context.TELEPHONY_SERVICE )).getDeviceId();
uuid = deviceId!=null ? UUID.nameUUIDFromBytes(deviceId.getBytes(“utf8”)) : UUID.randomUUID();
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}

// Write the value out to the prefs file
prefs.edit().putString(PREFS_DEVICE_ID, uuid.toString() ).commit();

}

}
}
}

}
/**
* Returns a unique UUID for the current android device.  As with all UUIDs, this unique ID is “very highly likely”
* to be unique across all Android devices.  Much more so than ANDROID_ID is.
*
* The UUID is generated by using ANDROID_ID as the base key if appropriate, falling back on
* TelephonyManager.getDeviceID() if ANDROID_ID is known to be incorrect, and finally falling back
* on a random UUID that’s persisted to SharedPreferences if getDeviceID() does not return a
* usable value.
*
* In some rare circumstances, this ID may change.  In particular, if the device is factory reset a new device ID
* may be generated.  In addition, if a user upgrades their phone from certain buggy implementations of Android 2.2
* to a newer, non-buggy version of Android, the device ID may change.  Or, if a user uninstalls your app on
* a device that has neither a proper Android ID nor a Device ID, this ID may change on reinstallation.
*
* Note that if the code falls back on using TelephonyManager.getDeviceId(), the resulting ID will NOT
* change after a factory reset.  Something to be aware of.
*
* Works around a bug in Android 2.2 for many devices when using ANDROID_ID directly.
*
* @see http://code.google.com/p/android/issues/detail?id=10603
*
* @return a UUID that may be used to uniquely identify your device for most purposes.
*/
public UUID getDeviceUuid() {
return uuid;
}
}

如何获取Android手机的唯一标识?

代码: 这里是你在Android里读出 唯一的 IMSI-ID / IMEI-ID 的方法。
Java:
String myIMSI = android.os.SystemProperties.get(android.telephony.TelephonyProperties.PROPERTY_IMSI);

// within my emulator it returns: 310995000000000

String myIMEI = android.os.SystemProperties.get(android.telephony.TelephonyProperties.PROPERTY_IMEI);

// within my emulator it returns: 000000000000000

注:android.os.SystemProperties的标签被打上@hide了,所以sdk中并不会存在。如果需要使用,需要有android的source code支持。

 

Android-Looper类介绍

   Android中的Looper类,是用来封装消息循环和消息队列的一个类,用于在android线程中进行消息处理。handler其实可以看做是一个工具类,用来向消息队列中插入消息的。

(1) Looper类用来为一个线程开启一个消息循环。

默认情况下android中新诞生的线程是没有开启消息循环的。(主线程除外,主线程系统会自动为其创建Looper对象,开启消息循环。)

Looper对象通过MessageQueue来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。

(2) 通常是通过Handler对象来与Looper进行交互的。Handler可看做是Looper的一个接口,用来向指定的Looper发送消息及定义处理方法。

默认情况下Handler会与其被定义时所在线程的Looper绑定,比如,Handler在主线程中定义,那么它是与主线程的Looper绑定。

mainHandler = new Handler() 等价于new Handler(Looper.myLooper()).

Looper.myLooper():获取当前线程的looper对象,类似的 Looper.getMainLooper() 用于获取主线程的Looper对象。

(3) 在非主线程中直接new Handler() 会报如下的错误:

E/AndroidRuntime( 6173): Uncaught handler: thread Thread-8 exiting due to uncaught exception

E/AndroidRuntime( 6173): java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()

原因是非主线程中默认没有创建Looper对象,需要先调用Looper.prepare()启用Looper。

(4) Looper.loop(); 让Looper开始工作,从消息队列里取消息,处理消息。

注意:写在Looper.loop()之后的代码不会被执行,这个函数内部应该是一个循环,当调用mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。

(5) 基于以上知识,可实现主线程给子线程(非主线程)发送消息。

把下面例子中的mHandler声明成类成员,在主线程通过mHandler发送消息即可。

Android官方文档中Looper的介绍:

Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.

Most interaction with a message loop is through the Handler class.

This is a typical example of the implementation of a Looper thread, using the separation of prepare() and loop() to create an initial Handler to communicate with the Looper.

class LooperThread extends Thread {
      public Handler mHandler;
      
      public void run() {
          Looper.prepare();
          
          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };
          
          Looper.loop();
      }
}

转自:
http://vinny-w.iteye.com/blog/1334641

Android图像处理之Bitmap类

田海立@CSDN2011/09/08

 

Bitmap是Android系统中的图像处理的最重要类之一。用它可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。本文从应用的角度,着重介绍怎么用Bitmap来实现这些功能。

 

一、Bitmap的生成

1.1 BitmapFactory decode出Bitmap

Bitmap实现在android.graphics包中。但是Bitmap类的构造函数是私有的,外面并不能实例化,只能是通过JNI实例化。这必然是 某个辅助类提供了创建Bitmap的接口,而这个类的实现通过JNI接口来实例化Bitmap的,这个类就是BitmapFactory。

Android图像处理之Bitmap类

图一、BitmapFactory主要方法及Options选项

 

利用BitmapFactory可以从一个指定文件中,利用decodeFile()解出Bitmap;也可以定义的图片资源中,利用decodeResource()解出Bitmap。

 

1.2 decode时的选项

在使用方法decodeFile()/decodeResource()时,都可以指定一个BitmapFacotry.Options

利用Options的下列属性,可以指定decode的选项:

  • inPreferredConfig 指定decode到内存中,手机中所采用的编码,可选值定义在Bitmap.Config中。缺省值是ARGB_8888。
  • inJustDecodeBounds 如果设置为true,并不会把图像的数据完全解码,亦即decodeXyz()返回值为null,但是Options的outAbc中解出了图像的基本信息。
  • inSampleSize 设置decode时的缩放比例。

 

利用Options的这些值就可以高效的得到一幅缩略图。

Android图像处理之Bitmap类

图二、BitmapFactory.decodeFile()

 

先设置inJustDecodeBounds= true,调用decodeFile()得到图像的基本信息[Step#2~4];

利用图像的宽度(或者高度,或综合)以及目标的宽度,得到inSampleSize值,再设置inJustDecodeBounds= false,调用decodeFile()得到完整的图像数据[Step#5~8]。

先获取比例,再读入数据,如果欲读入大比例缩小的图,将显著的节约内容资源。有时候还会读入大量的缩略图,这效果就更明显了。

 

二、利用Bitmap和Matrix实现图像变换

Bitmap可以和Matrix结合实现图像的剪切、旋转、缩放等操作。

Android图像处理之Bitmap类

图三、Bitmap方法

 

用源Bitmap通过变换生成新的Bitmap的方法:

    public static Bitmap createBitmap(Bitmap source, int x, int y, intwidth, int height,  
                Matrix m, boolean filter)  
    public static Bitmap createBitmap(Bitmap source, int x, int y, intwidth, int height)  
    public static Bitmap createScaledBitmap(Bitmap src, int dstWidth,  
                int dstHeight,boolean filter)

第一个方法是最终的实现,后两种只是对第一种方法的封装。

第二个方法可以从源Bitmap中指定区域(x,y, width, height)中挖出一块来实现剪切;第三个方法可以把源Bitmap缩放为dstWidth x dstHeight的Bitmap。

 

设置Matrix的Rotate(通过setRotate())或者Scale(通过setScale()),传入第一个方法,可实现旋转或缩放。

Android图像处理之Bitmap类

图四、Bitmap实现旋转

 

三、保存图像文件

经过图像变换之后的Bitmap里的数据可以保存到图像压缩文件里(JPG/PNG)。

Android图像处理之Bitmap类

图五、保存Bitmap数据到文件

 

这个操作过程中,Bitmap.compress()方法的参数format可设置JPEG或PNG格式;quality可选择压缩质量;fOut是输出流(OutputStream),这里的FileOutputStream是OutputStream的一个子类。

 

总结一下,本文介绍Bitmap的使用方法——用Bitmap实现图像文件的读取和写入,并用Bitmap实现图像的剪切、旋转和缩放变换。

详解android:scaleType属性

Android:scaleType是控制图片如何resized/moved来匹对ImageView的size。

ImageView.ScaleType / android:scaleType值的意义区别:

CENTER /center  按图片的原来size居中显示,当图片长/宽超过View的长/宽,则截取图片的居中部分显示

CENTER_CROP / centerCrop  按比例扩大图片的size居中显示,使得图片长(宽)等于或大于View的长(宽)

CENTER_INSIDE / centerInside  将图片的内容完整居中显示,通过按比例缩小或原来的size使得图片长/宽等于或小于View的长/宽

FIT_CENTER / fitCenter  把图片按比例扩大/缩小到View的宽度,居中显示

FIT_END / fitEnd   把图片按比例扩大/缩小到View的宽度,显示在View的下部分位置

FIT_START / fitStart  把图片按比例扩大/缩小到View的宽度,显示在View的上部分位置

FIT_XY / fitXY  把图片不按比例扩大/缩小到View的大小显示

MATRIX / matrix 用矩阵来绘制,动态缩小放大图片来显示。

** 要注意一点,Drawable文件夹里面的图片命名是不能大写的

onSaveInstanceState和onRestoreInstanceState触发的时机

当某个activity变得“容易”被系统销毁时,该activity的onSaveInstanceState就会被执行,除非该activity是被用户主动销毁的,例如当用户按BACK键的时候。

注意上面的双引号,何为“容易”?言下之意就是该activity还没有被销毁,而仅仅是一种可能性。这种可能性有哪些?通过重写一个activity的所有生命周期的onXXX方法,包括onSaveInstanceState和onRestoreInstanceState方法,我们可以清楚地知道当某个activity(假定为activity A)显示在当前task的最上层时,其onSaveInstanceState方法会在什么时候被执行,有这么几种情况:

1、当用户按下HOME键时。

这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,故系统会调用onSaveInstanceState,让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则

2、长按HOME键,选择运行其他的程序时。

3、按下电源按键(关闭屏幕显示)时。

4、从activity A中启动一个新的activity时。

5、屏幕方向切换时,例如从竖屏切换到横屏时。

在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState一定会被执行。

总而言之,onSaveInstanceState的调用遵循一个重要原则,即当系统“未经你许可”时销毁了你的activity,则onSaveInstanceState会被系统调用,这是系统的责任,因为它必须要提供一个机会让你保存你的数据(当然你不保存那就随便你了)。

至于onRestoreInstanceState方法,需要注意的是,onSaveInstanceState方法和onRestoreInstanceState方法“不一定”是成对的被调用的,onRestoreInstanceState被调用的前提是,activity A“确实”被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用,例如,当正在显示activity A的时候,用户按下HOME键回到主界面,然后用户紧接着又返回到activity A,这种情况下activity A一般不会因为内存的原因被系统销毁,故activity A的onRestoreInstanceState方法不会被执行。

另外,onRestoreInstanceState的bundle参数也会传递到onCreate方法中,你也可以选择在onCreate方法中做数据还原。

http://justsee.iteye.com/blog/1113104

http://www.cnblogs.com/hanyonglu/archive/2012/03/28/2420515.html