类Browser.java是整个应用的Application.其代码例如以下:

  1. public class Browser extends Application {
  2.  
  3. @Override
  4. public void onCreate() {
  5. super.onCreate();
  6.  
  7. // create CookieSyncManager with current Context
  8. CookieSyncManager.createInstance(this);
  9. BrowserSettings.initialize(getApplicationContext());
  10. Preloader.initialize(getApplicationContext());
  11. }
  12.  
  13. }

在Browser的创建方法onCreate方法中进行了三个操作。

1、创建了一个CookieSyncManager单例对象。CookieSyncManager用于管理存储在本地数据库的cookies。

2、初始化BrowserSettings。BrowserSettings是浏览器配置的管理单例类。

3、初始化Preloader。Preloader是处理预载入请求的单例类。

关于CookieSyncManager

我们来看看CookieSyncManager类及createInstance方法,

  1. public final class CookieSyncManager extends WebSyncManager {
  2.  
  3. private static CookieSyncManager sRef;
  4.  
  5. private CookieSyncManager(Context context) {
  6. super(context, "CookieSyncManager");
  7. }
  8. //获取CookieSyncManager 对象
  9. public static synchronized CookieSyncManager getInstance() {
  10. return sRef;
  11. }
  12. //创建CookieSyncManager 对象
  13. public static synchronized CookieSyncManager createInstance(
  14. Context context) {
  15. if (sRef == null) {
  16. sRef = new CookieSyncManager(context);
  17. }
  18. return sRef;
  19. }
  20.  
  21. protected void syncFromRamToFlash() {
  22. CookieManager manager = CookieManager.getInstance();
  23. if (!manager.acceptCookie()) {
  24. return;
  25. }
  26. manager.flushCookieStore();
  27. }
  28. }

CookieSyncManager是一个final类,这里用到了单例模式。没什么好讲的。CookieSyncManager继承自WebSyncManager 类,syncFromRamToFlash是个什么方法,后面将会介绍。再来看看WebSyncManager类。

  1. abstract class WebSyncManager implements Runnable {
  2. // 同步消息的消息码
  3. private static final int SYNC_MESSAGE = 101;
  4. // 以毫秒为单位的同步消息(即时)的时延
  5. private static int SYNC_NOW_INTERVAL = 100; // 100 毫秒
  6. // 以毫秒为单位的同步消息(稍后)的时延
  7. private static int SYNC_LATER_INTERVAL = 5 * 60 * 1000; // 5分钟
  8. // 同步线程
  9. private Thread mSyncThread;
  10. // 线程名
  11. private String mThreadName;
  12. // 同步线程的处理Handler
  13. protected Handler mHandler;
  14. // 持久存储的数据库
  15. protected WebViewDatabase mDataBase;
  16. // 调用開始同步和停止同步的參考次数
  17. private int mStartSyncRefCount;
  18.  
  19. private class SyncHandler extends Handler {
  20. @Override
  21. public void handleMessage(Message msg) {
  22. if (msg.what == SYNC_MESSAGE) {
  23. syncFromRamToFlash();
  24. // 发送延时消息来请求稍后的同步,时间间隔5分钟
  25. Message newmsg = obtainMessage(SYNC_MESSAGE);
  26. sendMessageDelayed(newmsg, SYNC_LATER_INTERVAL);
  27. }
  28. }
  29. }
  30.  
  31. protected WebSyncManager(Context context, String name) {
  32. mThreadName = name;
  33. if (context != null) {
  34. mDataBase = WebViewDatabase.getInstance(context);
  35. mSyncThread = new Thread(this);
  36. mSyncThread.setName(mThreadName);
  37. mSyncThread.start();
  38. } else {
  39. //exception
  40. }
  41. }
  42.  
  43. protected Object clone() throws CloneNotSupportedException {
  44. //throw exception
  45. }
  46.  
  47. public void run() {
  48. Looper.prepare(); // 为同步handler准备Looper对象
  49. mHandler = new SyncHandler();
  50. onSyncInit();
  51. // 在onSyncInit() 完毕之后减少优先级
  52. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  53.  
  54. Message msg = mHandler.obtainMessage(SYNC_MESSAGE);
  55. mHandler.sendMessageDelayed(msg, SYNC_LATER_INTERVAL);
  56.  
  57. Looper.loop();
  58. }
  59.  
  60. public void sync() {
  61. //...
  62. mHandler.removeMessages(SYNC_MESSAGE);
  63. Message msg = mHandler.obtainMessage(SYNC_MESSAGE);
  64. mHandler.sendMessageDelayed(msg, SYNC_NOW_INTERVAL);
  65. }
  66.  
  67. public void resetSync() {
  68. //...
  69. mHandler.removeMessages(SYNC_MESSAGE);
  70. Message msg = mHandler.obtainMessage(SYNC_MESSAGE);
  71. mHandler.sendMessageDelayed(msg, SYNC_LATER_INTERVAL);
  72. }
  73.  
  74. public void startSync() {
  75. //...
  76. if (++mStartSyncRefCount == 1) {
  77. Message msg = mHandler.obtainMessage(SYNC_MESSAGE);
  78. mHandler.sendMessageDelayed(msg, SYNC_LATER_INTERVAL);
  79. }
  80. }
  81.  
  82. public void stopSync() {
  83. //...
  84. if (--mStartSyncRefCount == 0) {
  85. mHandler.removeMessages(SYNC_MESSAGE);
  86. }
  87. }
  88.  
  89. protected void onSyncInit() {
  90. }
  91.  
  92. abstract void syncFromRamToFlash();
  93. }

由此可见。WebSyncManager是实现了Runnable 接口的抽象类,当中的抽象方法就是上面提到的syncFromRamToFlash法,CookieSyncManager是WebSyncManager 的实现类并覆写了该方法。WebSyncManager中默认的同步时间间隔是5分钟,也就是Cookies的同步周期,WebSyncManager在创建的时候就会启动自身的线程。并依照同步周期来对cookies做同步。同步的详细实现即是syncFromRamToFlash()方法,WebSyncManager类中有下面几个重要的方法:

resetSync() 又一次同步,即清除消息队列的同步消息。又一次发送延时5分钟的延时同步消息。

startSync() 開始同步,即參考次数为0时,发送延时5分钟的延时同步消息,次数加1.

stopSync() 停止同步。即清除消息队列的同步消息

sync() 马上同步。即清除消息队列的同步消息,又一次发送延时100毫秒的延时同步消息。

我们来看看CookieSyncManager中覆写WebSyncManager 中的syncFromRamToFlash方法,这种方法也就是cookies同步的方法。同步数据从RAM到FLASH。

  1. protected void syncFromRamToFlash() {
  2. CookieManager manager = CookieManager.getInstance();
  3. if (!manager.acceptCookie()) {
  4. return;
  5. }
  6. manager.flushCookieStore();
  7. }

进行了三步操作:

1、通过getInstance()获取CookieManager 对象。

2、推断CookieManager 对象能否够接受cookies。不能则返回。

3、调用CookieManager 对象的flushCookieStore()方法来实现同步。

先来看看getInstance()方法。

  1. public static synchronized CookieManager getInstance() {
  2. return WebViewFactory.getProvider().getCookieManager();
  3. }

看看WebViewFactory的getProvider()方法

  1. static WebViewFactoryProvider getProvider() {
  2. synchronized (sProviderLock) {
  3. if (sProviderInstance != null) return sProviderInstance;
  4. //....
  5. if (sProviderInstance == null) {
  6. sProviderInstance = getFactoryByName(DEFAULT_WEBVIEW_FACTORY,
  7. WebViewFactory.class.getClassLoader());
  8. if (sProviderInstance == null) {
  9. sProviderInstance = new WebViewClassic.Factory();
  10. }
  11. }
  12. return sProviderInstance;
  13. }
  14. }

是通过getFactoryByName获取的,DEFAULT_WEBVIEW_FACTORY定义例如以下:

  1. private static final String DEFAULT_WEBVIEW_FACTORY = "android.webkit.WebViewClassic$Factory";

所以实现上是获取了WebViewClassic.Factory对象。

WebViewClassic.Factory对象的getCookieManager()例如以下:

  1. public CookieManager getCookieManager() {
  2. return CookieManagerClassic.getInstance();
  3. }

总结一下:getInstance()得到了一个CookieManagerClassic对象。

  1. class CookieManagerClassic extends CookieManager

CookieManagerClassic 继承自CookieManager,并覆写了CookieManager中的大部分方法。

并终于通过本地方法实现详细的操作:

比如步骤二中的acceptCookie()方法,

  1. public synchronized boolean acceptCookie() {
  2. return nativeAcceptCookie();
  3. }
  4. private static native boolean nativeAcceptCookie();

还有步骤三中flushCookieStore()方法。

  1. protected void flushCookieStore() {
  2. nativeFlushCookieStore();
  3. }
  4. private static native void nativeFlushCookieStore();

CookieManager中另一些经常使用的方法,比如:

removeSessionCookie()

getCookie()

removeAllCookie()

setCookie()

setAcceptCookie()

...

CookieManager中的方法大多会MustOverrideException异常。所以必须用一个类来继承它。正如上面的CookieManagerClassic 。

关于BrowserSettings

BrowserSettings是整个浏览器配置的管理类。先看initialize()方法。

  1. public static void initialize(final Context context) {
  2. sInstance = new BrowserSettings(context);
  3. }
  4. private BrowserSettings(Context context) {
  5. mContext = context.getApplicationContext(); //获取应用的Context对象
  6. //获取应用的SharedPreferences
  7. mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
  8. mAutofillHandler = new AutofillHandler(mContext);
  9. mManagedSettings = new LinkedList<WeakReference<WebSettings>>();
  10. mCustomUserAgents = new WeakHashMap<WebSettings, String>();
  11. mAutofillHandler.asyncLoadFromDb();
  12. BackgroundHandler.execute(mSetup);
  13. }

initialize()实际上就是new了一个BrowserSettings对象。AutofillHandler是关于账户个人信息的,asyncLoadFromDb方法是异步来载入个人账户信息的,关于这部分后面会简单提到。

创建了LinkedListWeakHashMap中都用到了WebSettings。看看WebSettings是什么吧。

WebSettings在package android.webkit下,是一个抽象类。它用于管理WebView的配置状态。当一个WebView第一次被创建时,将会获得默认的配置集合,默认的配置将会通过调用随意的getter方法返回。通过WebView.getSettings()获取的WebSettings与WebView的生存周期结合在一起。假设一个WebView被销毁了。不论什么关于WebSettings的方法将会抛出IllegalStateException异常。

WebSettings中的方法大多会MustOverrideException异常,所以必须用一个类来继承它。framework中是用WebSettingsClassic 继承它的。

关于这点后面再讲。

  1. public class WebSettingsClassic extends WebSettings

这个LinkedList是在方法startManagingSettings(WebSettings settings) 加入条目的,在stopManagingSettings(WebSettings settings)中删除条目,在syncManagedSettings()中同步每个WebSettings。

这个WeakHashMap 是与用户代理相关的,通过WebSettings的setUserAgentString()来为WebView设置代理。

再看这句:BackgroundHandler.execute(mSetup);

BackgroundHandler的代码例如以下:

  1. public class BackgroundHandler {
  2.  
  3. static HandlerThread sLooperThread;
  4. static ExecutorService mThreadPool;
  5.  
  6. static {
  7. sLooperThread = new HandlerThread("BackgroundHandler", HandlerThread.MIN_PRIORITY);
  8. sLooperThread.start();
  9. mThreadPool = Executors.newCachedThreadPool();
  10. }
  11.  
  12. public static void execute(Runnable runnable) {
  13. mThreadPool.execute(runnable);
  14. }
  15.  
  16. public static Looper getLooper() {
  17. return sLooperThread.getLooper();
  18. }
  19.  
  20. private BackgroundHandler() {}
  21. }

整个BackgroundHandler 能够看成两个部分。一个线程池ExecutorService 对象,一个HandlerThread 对象。

所以它的作用主要是两个:

1、利用线程池ExecutorService 对象来运行线程Runnable对象。

比如:BackgroundHandler.execute(mSetup); //mSetup是一个Runnable对象

2、利用HandlerThread 来获取Looper对象,用于创建接收在其它线程中发送的消息的Handler对象。

比如:

  1. Handler mForegroundHandler = new Handler();
  2. Handler mBackgroundHandler = new Handler(BackgroundHandler.getLooper()) {
  3. @Override
  4. public void handleMessage(Message msg) {
  5.  
  6. }
  7. };
  8. private Runnable mCreateState = new Runnable() {
  9. @Override
  10. public void run() {
  11. Message.obtain(mBackgroundHandler, what, obj).sendToTarget();
  12. }
  13. };

知道了BackgroundHandler ,就知道了BackgroundHandler.execute(mSetup);就是在线程池中运行了mSetup这个Runnable对象。

看看mSetup的定义:

  1. private Runnable mSetup = new Runnable() {
  2.  
  3. @Override
  4. public void run() {
  5. DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
  6. mFontSizeMult = metrics.scaledDensity / metrics.density;
  7.  
  8. if (ActivityManager.staticGetMemoryClass() > 64) {
  9. mPageCacheCapacity = 5;
  10. }
  11. mWebStorageSizeManager = new WebStorageSizeManager(mContext,
  12. new WebStorageSizeManager.StatFsDiskInfo(getAppCachePath()),
  13. new WebStorageSizeManager.WebKitAppCacheInfo(getAppCachePath()));
  14. mPrefs.registerOnSharedPreferenceChangeListener(BrowserSettings.this);
  15. //...
  16. sFactoryResetUrl = mContext.getResources().getString(R.string.homepage_base);
  17. //...
  18. synchronized (BrowserSettings.class) {
  19. sInitialized = true;
  20. BrowserSettings.class.notifyAll();
  21. }
  22. }
  23. };

主要做了例如以下几件事:

1、获取字体缩放因子(metrics.scaledDensity(字体缩放比例)/metrics.density(显示密度))

2、依据ActivityManager.staticGetMemoryClass()的值设置缓存页面的数量,默认是1,看看staticGetMemoryClass()

  1. public static int staticGetMemoryClass() {
  2. // Really brain dead right now -- just take this from the configured
  3. // vm heap size, and assume it is in megabytes and thus ends with "m".
  4. String vmHeapSize = SystemProperties.get("dalvik.vm.heapgrowthlimit", "");
  5. if (vmHeapSize != null && !"".equals(vmHeapSize)) {
  6. return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1));
  7. }
  8. return staticGetLargeMemoryClass();
  9. }

能够看出虚拟机堆的大小是从"dalvik.vm.heapgrowthlimit"读出来的,上面的程序段显示假设堆的大小大于64M,则将缓存页面的值设为5。否则默觉得1,这样能够避免OOM。

3、创建一个WebStorageSizeManager对象。用以管理缓存的磁盘空间

4、为应用的SharedPreference注冊改变的监听器,里面的配置值改变将会实现同步设置操作

  1. @Override
  2. public void onSharedPreferenceChanged(
  3. SharedPreferences sharedPreferences, String key) {
  4. syncManagedSettings();
  5. }

5、获取浏览器的主页URL的路径sFactoryResetUrl

6、通知大家初始化完成。

再来看看AutofillHandler ,Google用来实现表单自己主动填充功能的。Android浏览器的自己主动填充条目有:Full name、Company name、Address、Zip code、Country、Phone、Email等。AutoFillProfileDatabase 类是用来对这些条目进行存储的数据库操作类。

数据将被存放在autofill.db 数据库中。

使用自己主动填充功能须要注意下面几个方面:

1、由于涉及到隐私,须要在浏览器的设置中开启Form Auto-fill选项;

2、用户须要比較勤快,事先要将上面的个人信息录入;

3、须要站点的支持,这些信息怎样和表单上的字段相应,这就是RFC 3106所定义的,表单控件的命名须要遵守规范。国内的站点。包含京东、亚马逊中国、当当、淘宝等均不支持。

4、某些站点可能会试图捕获隐藏字段或难以发现的字段中的信息。因此,请勿在您不信任的站点上使用自己主动填充功能。

5、某些站点会阻止浏览器保存您输入的内容。因此,无法在这些站点上填写表单。

由上,表单自己主动填充功能基本上在国内是用不上的。

我们还是简单的看一看它的实现,前面看到它的异步载入方法asyncLoadFromDb() .

  1. public void asyncLoadFromDb() {
  2. new LoadFromDb().start();
  3. }

启用了一个线程。

  1. private class LoadFromDb extends Thread {
  2.  
  3. @Override
  4. public void run() {
  5. SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(mContext);
  6.  
  7. // 从SharedPreferences中读出近期使用的自己主动填充条目的ID.
  8. mAutoFillActiveProfileId = p.getInt(
  9. PreferenceKeys.PREF_AUTOFILL_ACTIVE_PROFILE_ID,
  10. mAutoFillActiveProfileId);
  11. //获取存储数据的数据库操作管理对象
  12. AutoFillProfileDatabase autoFillDb = AutoFillProfileDatabase.getInstance(mContext);
  13. Cursor c = autoFillDb.getProfile(mAutoFillActiveProfileId);
  14.  
  15. if (c.getCount() > 0) {
  16. c.moveToFirst();
  17.  
  18. String fullName = c.getString(c.getColumnIndex(
  19. AutoFillProfileDatabase.Profiles.FULL_NAME));
  20. //从cursor中获取全部的字段的值
  21. ...
  22. mAutoFillProfile = new AutoFillProfile(mAutoFillActiveProfileId,
  23. fullName, email, company, addressLine1, addressLine2, city,
  24. state, zip, country, phone);
  25. }
  26. c.close();
  27. autoFillDb.close();
  28. mLoaded.countDown();
  29. //假设没有值。从联系人数据库中取值
  30. if (mAutoFillProfile == null) {
  31. final Uri profileUri = Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
  32. ContactsContract.Contacts.Data.CONTENT_DIRECTORY);
  33. String name = getContactField(profileUri,
  34. ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
  35. ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
  36. if (name != null) {
  37. String email = getContactField(profileUri,
  38. ContactsContract.CommonDataKinds.Email.ADDRESS,
  39. ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
  40. ...
  41.  
  42. synchronized(AutofillHandler.this) {
  43. if (mAutoFillProfile == null) {
  44. setAutoFillProfile(new AutoFillProfile(1, name, email, company,
  45. null, null, null, null, null, null, phone), null);
  46. }
  47. }
  48. }
  49. }
  50. }

详细的操作就不用多说了,就是简单的数据库操作。我们看到一个mLoaded 。定义例如以下

  1. private CountDownLatch mLoaded = new CountDownLatch(1);

CountDownLatch 主要用于在完毕一组正在其它线程中运行的操作之前,它同意一个或多个线程一直等待。

主要方法

public CountDownLatch(int count);

public void countDown();

public void await() throws InterruptedException

--CountDownLatch(int count)构造方法传了指定计次的数目。

--countDown方法,当前线程调用此方法,则计数减一

--await方法,调用此方法会一直堵塞当前线程,直到计时器的值为0

程序中计次数目为1。此处调用countDown()是为了减1来唤醒获取AutoFillProfile之前堵塞的线程mSetup 。

为什么是mSetup ?由于在BrowserSettings的构造方法中有例如以下的代码。

  1. private BrowserSettings(Context context) {
  2. ...
  3. mAutofillHandler.asyncLoadFromDb();
  4. BackgroundHandler.execute(mSetup);
  5. }

BackgroundHandler.execute(mSetup)是在另外的线程中运行的操作。

mSetup中注冊了SharedPreference的监听器。在onSharedPreferenceChanged中依次调用syncManagedSettings()-->syncSetting(settings)--> settings.setAutoFillProfile(getAutoFillProfile());

getAutoFillProfile()是调用mAutofillHandler 的getAutoFillProfile()

  1. public AutoFillProfile getAutoFillProfile() {
  2. return mAutofillHandler.getAutoFillProfile();
  3. }

mAutofillHandler 的getAutoFillProfile()定义例如以下:

  1. public synchronized AutoFillProfile getAutoFillProfile() {
  2. waitForLoad();
  3. return mAutoFillProfile;
  4. }

再看waitForLoad()方法:

  1. private void waitForLoad() {
  2. try {
  3. mLoaded.await();
  4. } catch (InterruptedException e) {
  5. Log.w(LOGTAG, "...");
  6. }
  7. }

这里用到了CountDownLatch 的await()方法来是线程堵塞,整个这一段的逻辑就是在构造BrowserSettings是在主线程载入表单自己主动填充,同步浏览器数据的操作是另开的线程中实现的,但有一个值须要主线程的操作完毕后才干获取,没有值的时候就会堵塞在那里,主线程操作结束获得值之后就会唤醒这个另开的线程,完毕值的存储工作。我用下图来表示。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYW5kcm9pZF9oYXNlbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

关于Preloader

Preloader的initialize 方法也仅仅是创建了一个Preloader对象。没有进行其它的操作。

Application是整个应用的入口,一般完毕一些初始化的操作,到这里就简单分析了一下。

android原生browser分析(一)--Application的更多相关文章

  1. [Android Pro] android 4.4 Android原生权限管理:AppOps

    reference : http://m.blog.csdn.net/blog/langzxz/45308199 reference : http://blog.csdn.net/hyhyl1990/ ...

  2. android 原生camera——设置模块修改

    , 此篇博客是记一次客户需求修改,从上周五到现在正好一周时间,期间的各种酸爽,就不说了,还是来看大家关注的技术问题吧. 首先看下以前效果和修改后的效果: 修改前:修改后: 不知道有没有看明白,我在简单 ...

  3. Android源码分析-全面理解Context

    前言 Context在android中的作用不言而喻,当我们访问当前应用的资源,启动一个新的activity的时候都需要提供Context,而这个Context到底是什么呢,这个问题好像很好回答又好像 ...

  4. 【android原生应用】之闹钟应用搭起篇

    由于工作原因接触android开发一段时间了,对于开发有了一些了解,于是萌生了搭起android原生应用进行分析和学习的想法.先从闹钟应用开始吧. 1.首先要下载原生应用,原生应用在原生系统里面(当然 ...

  5. Android 内存泄漏分析与解决方法

    在分析Android内存泄漏之前,先了解一下JAVA的一些知识 1. JAVA中的对象的创建 使用new指令生成对象时,堆内存将会为此开辟一份空间存放该对象 垃圾回收器回收非存活的对象,并释放对应的内 ...

  6. Android原生跳转React不同页面(undefined is not an object)

    继续上篇文章的demo,由于现在的项目是原生的,打算用部分页面试下react native,那么问题来了:react貌似只有一个入口 index.android.js,那么在不同的原生页面需要跳转到不 ...

  7. Android原生(Native)C开发之四:SDL移植笔记

    http://www.apkbus.com/forum.php?mod=viewthread&tid=1989 SDL(Simple DirectMedia Layer)是一套开放源码的跨平台 ...

  8. Android原生和H5交互;Android和H5混合开发;WebView点击H5界面跳转到Android原生界面。

    当时业务的需求是这样的,H5有一个活动商品列表的界面,IOS和Android共用这一个界面,点击商品可以跳转到Android原生的商品详情界面并传递商品ID:  大概就是点击H5界面跳转到Androi ...

  9. Android HttpURLConnection源代码分析

    Android HttpURLConnection源代码分析 之前写过HttpURLConnection与HttpClient的差别及选择.后来又分析了Volley的源代码. 近期又遇到了问题,想在V ...

随机推荐

  1. Git 配置editor编辑器

    Git 配置editor编辑器 在ubuntu系统下,Git默认的编辑器是命令行,学名叫V什么的,使用起来诸多不便 在编辑提交日志的时候,用的比较多. 可以选择unbuntu默认的文档编辑器作为git ...

  2. css如何li中选中后加上class属性js控制

    <ul> <li class=""pageson"><span>1</span></li> <li> ...

  3. 二、Mongo命令初识

    简单介绍mongo的一些基本命令 1.   连接与登陆mongo 在命令行输入“mongo”命令即可登陆Mongo数据库(PS:默认讨论被信任的环境,也就是不需要用户名和密码进行登陆). 查看当前所使 ...

  4. 3DShader之投影贴图(Projective Texturing)

    相信大家都应该玩过CS或者CF吧,游戏里面有个喷图功能,就是按一个T键就能在墙上或者地板上喷出自己预先设定的图案. 而刚好这就是我们这个Shader所需实现的内容.由于没有潜伏者的贴图,我只有从这个图 ...

  5. linux BC命令行计算器

    1. 基本使用: $ bc <<< 5*4 20 $ bc <<< 5+4 9 $ bc <<< 5-4 1 或者 $ echo "5* ...

  6. Linux命令: ln

    每天一个linux命令(35):ln 命令 实例1:给文件创建软链接 命令: ln -s log2013.log link2013 输出: [root@localhost test]# ll -rw- ...

  7. awk内置变量 awk有许多内置变量用来设置环境信息,这些变量可以被改变,下面给出了最常用的一些变量。

    ARGC 命令行参数个数 ARGV 命令行参数排列 ENVIRON 支持队列中系统环境变量的使用 FILENAME awk浏览的文件名 FNR 浏览文件的记录数 FS 设置输入域分隔符,等价于命令行 ...

  8. NGINX服务器打开目录浏览功能

    我们做文件服务器的时候,希望打开目录浏览的功能.但是Nginx默认是不允许列出目录功能的.若需要此功能,需要在配置文件中手动开启. 首先需要打开开关.autoindex on;autoindex_ex ...

  9. python-Day4-迭代器-yield异步处理--装饰器--斐波那契--递归--二分算法--二维数组旋转90度--正则表达式

    本节大纲 迭代器&生成器 装饰器  基本装饰器 多参数装饰器 递归 算法基础:二分查找.二维数组转换 正则表达式 常用模块学习 作业:计算器开发 实现加减乘除及拓号优先级解析 用户输入 1 - ...

  10. 轻量化ViewControllers,读文章做的总结

    推荐一个网站 http://objccn.io/ 我这两天才开始看 获益匪浅 看了第一篇文章 <更轻量的View Controllers>感觉写的不错 感觉作者 原文地址 http://o ...