假設 Nginx 啟動了多個 Worker 進程,并且在 Master 進程中通過 socket 套接字監(jiān)聽80端口。這些 Worker 進程 fork 自 Master 進程,然后每個worker進程都可以去 accept 這個監(jiān)聽的 socket。 當一個連接進來后,所有在 accept 在這個 socket 上面的進程,都會收到消息,但是只有一個進程可以 accept 這個連接,其它的則 accept 失敗,這便是所謂的驚群現象。Nginx 處理這種情況的方式就是加鎖。有了鎖之后,在同一時刻,就只會有一個 Worker 進程在 accpet 連接,這樣就不會有驚群問題了。在 Worker 進程拿到 Http 請求后,就開始按照前面介紹的 11個階段處理該 Http 請求,最后返回響應結果并斷開連接。
POST_READ 階段是 Nginx 接收到 Http 請求完整頭部后的處理階段,這里主要使用的是 realip 模塊獲取用戶的真實地址,方便后續(xù)對該 IP 進行限速或者過濾其請求等。
2009 年發(fā)布 Node.js 的發(fā)布,意味著前端程序員可以用較低的成本跨入服務端開發(fā)。Node.js 提供了開發(fā)服務端所需要的特性,如 HTTP 服務、本地文件讀寫操作等。開發(fā)者可以使用 JavaScript 語言開發(fā) Node.js 應用。Node.js
搭建 Spring MVC 項目,可以使用 JAVA 方式進行組件信息配置,也可以使用 XML 方式,甚至可以使用 JAVA 和 XML 的混合模式?,F在主流框架都流行使用 JAVA 方式(注解),逐步減少對 XML 方式的依賴。主要是 JAVA 方法更適合 JAVA 開發(fā)者的習慣,并簡化了配置過程。Spring MVC 項目是基于 Spring 的核心基礎功能(IOC、AOP)之上的,Spring 建議 Spring MVC 項目中創(chuàng)建 2 個 WebApplication 對象(也稱為上下文容器對象)。這 2 個上下文對象分別依賴于 DispatcherServlet 和 ContextLoaderListener 組件完成創(chuàng)建。DispatcherServlet 是整個程序的調度中心(本質就是一個 Servlet),Spring MVC 程序啟動時必須要初始化完成對 DispatcherServlet 組件的創(chuàng)建。此組件還會創(chuàng)建一個與自己有關聯的 WebApplication 工廠對象,即 Web 上下文對象;ContextLoaderListener 是由 Spring 提供的監(jiān)聽器組件,它可以監(jiān)聽程序的啟動過程,并在程序啟動時創(chuàng)建另一個 WebApplication 工廠對象,即 Root 上下文對象。所以,必須配置好 DispatcherServlet 和 ContextLoaderListener ,保證程序啟動時創(chuàng)建它們。使用 XML 配置之前,先準備好 3 個 XML 文件:web.xml: 理論上,在新建 Spring MVC WEB 項目時, WEB-INF 目錄中會自動創(chuàng)建這個文件。如果不存在,就需要創(chuàng)建一個;spring-mvc.xml: 由開發(fā)者新建,一般放在項目的 src/main/resources 目錄下面;application.xml: 由開發(fā)者新建,放在項目的 src/main/resources 目錄下面。Tips: spring-mvc.xml 和 application.xml 的文件名可由開發(fā)者根據語義自行指定。這 2 個文件也可以根據需要更改存放目錄。 這 2 個 XML 文件中必須至少包括 beans 根元素說明。<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"></beans>
資源在請求的過程中有可能被轉發(fā),Referer 字段記錄了請求的原始地址比如在 www.google.com 里有一個 www.baidu.com 鏈接,那么點擊這個 www.baidu.com ,它的 header信息里就有如下:Referer=http://www.google.com
我們打開百度搜索"websocket在線測試",找到 websocket 的在線測試網站??梢钥吹?121.40.165.18:8800 是該網站提供 websocket 連接的后端服務地址。我們借助這個地址來完成一個簡單的測試。我們找一臺公網上的云主機,其 ip 地址為 180.76.152.113,在上面搭建 Nginx 服務,添加監(jiān)聽9000端口的服務配置如下:...http { ... server { listen 9000; default_type text/plain; access_log logs/ws.log; location / { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_pass http://121.40.165.18:8800; } } ...}...然后我們將 websocket 在線測試網站中的測試地址改成 ws://180.76.152.113:9000,斷開后再次連接,發(fā)現也能成功,同時能實現原服務的功能。這說明我們的 Nginx 服務成功完成了 Websocket 代理功能。
Spring Security 的核心特性包括:認證和授權、常規(guī)攻擊防范、與 Servlet 接口集成、與 Spring MVC 集成等。認證和授權的目的是,讓系統(tǒng)知道使用者是誰(認證)?是什么樣的身份?允許他做什么?禁止他做什么?通常的做法是要求用戶輸入自己的用戶名和密碼,來實現登錄和鑒權的過程。常規(guī)攻擊防范在 Spring Security 安全框架中是默認開啟的,常見的威脅抵御方式有:防止偽造跨站請求(CSRF)安全響應頭(HTTP Response headers)HTTP 通訊安全作為 Spring 大家族的一員,Spring Security 在與 Spring 引用,尤其是與 Spring boot 應用的結合時,顯得極為便利。Spring Security 三大功能
配置 Java 的 lint 檢查要專門對 Android 項目中的某個類或方法停用 lint 檢查,請向該代碼添加 @SuppressLint 注釋。以下示例展示了如何對 onCreate 方法中的 NewApi 問題關閉 lint 檢查。lint 工具會繼續(xù)檢查該類的其他方法中的 NewApi 問題。@SuppressLint("NewApi")@Overridepublic void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);以下示例展示了如何對 FeedProvider 類中的 ParserError 問題關閉 lint 檢查:@SuppressLint("ParserError")public class FeedProvider extends ContentProvider {要禁止 lint 檢查文件中的所有問題,請使用 all 關鍵字,如下所示:@SuppressLint("all")配置 XML 的 lint 檢查我們通過 tools:ignore 屬性對 XML 文件的特定部分停用 lint 檢查。在 lint.xml 文件中添加以下命名空間值,以便 lint 工具能夠識別該屬性:namespace xmlns:tools="http://schemas.android.com/tools"以下示例展示了如何對 XML 布局文件的 < LinearLayout > 元素中的 UnusedResources 問題關閉 lint 檢查。如果某個父元素聲明了 ignore 屬性,則該元素的子元素會繼承此屬性。在本例中,也會對 <TextView> 子元素停用 lint 檢查。<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:ignore="UnusedResources" > <TextView android:text="@string/auto_update_prompt" /></LinearLayout>要禁止檢查多個問題,請使用以逗號分隔的字符串列出要禁止檢查的問題。例如:tools:ignore="NewApi,StringFormatInvalid"要禁止 lint 檢查 XML 元素中的所有問題,請使用 all 關鍵字,如下所示:tools:ignore="all"
Nginx 的進程模型采用了 Master/Workers 進程池的機制,即通常情況下,Nginx 會啟動一個 Master 進程(當然,也可以無 master 進程)和多個 Worker 進程對外提供服務。Master 進程是監(jiān)控進程,本身并不處理具體的 TCP 和 HTTP 請求,只負責接受 UNIX 信號,管理 Worker 進程,類似于工地的包工頭。Worker 進程是比較累的,負責處理客戶端的連接請求,它充分利用了 Linux 系統(tǒng)中的 epoll、kqueue 等機制,高效處理 TCP 和 HTTP 請求,利用這些特點,Nginx 充分挖掘了服務器的潛能,讓服務器更快響應用戶請求。一般情況下,10000 個非活躍的 HTTP Keep-Alive 連接在 Nginx 中僅僅消耗 2.5M 內存,這是 Nginx 支持高并發(fā)連接的基礎,體現了 Nginx 高性能的特點。另外,由于官方提供的模塊都非常穩(wěn)定,每個 Worker 進程都相對獨立,Woker 進程出錯時,Master 進程會立馬感知到并快速拉起新的 Worker 子進程不間斷提供服務,保證服務的穩(wěn)定性。nginx的進程模型
2.3.1 基本認證的流程基本認證也是常用的認證方式?;菊J證分兩種場景:如果直接在瀏覽器里訪問頁面,瀏覽器會彈出登錄窗口,如下圖:如果發(fā)送未經認證的 http 請求,服務端會返回 401 錯誤。實現基本認證有兩種方式:在請求頭中添加 Authorization: "Basic Base64(用戶名+密碼)";在請求參數中增加用戶名和密碼。在 Spring Security 中,具體的認證過程如下:用戶請求受保護資源;(與表單認證相同)Spring Security 的 FilterSecurityInterceptor 對象,檢測到當前用戶認證未通過,應予以拒絕,并拋出 AccessDeniedException;(與表單認證相同)當 AccessDeniedException 被 ExceptionTranslationFilter 接收后,其認定需要發(fā)起認證流程,此時用戶被要求登錄,認證服務器將認證頭 WWW-Authenticate (默認由 BasicAuthenticationEntryPoint 提供)返回給客戶端。當客戶端收到 WWW-Authenticate 頭后,客戶端提供用戶名和密碼參數用于認證。2.3.2 基本認證的配置默認情況下,Spring Security 開啟了基本認證功能。如果我們需要顯式配置,可用如下方式實現。protected void configure(HttpSecurity http) { http // ... .httpBasic(withDefaults());}
上節(jié)我們剛了解完 URI ,知道它是一種資源標識,而 URL 是 schema = Http 的子標識,本節(jié)要講的 Restful 從小的講是對 URL 格式提出了限制,對接口設計規(guī)范的倡導,大的說它是一種通信架構的指導。
依次新建以下類結構GoodsDo:商品類,對應 goods 商品表;GoodsDao:商品數據訪問類,用于訪問數據庫;GoodsService:商品服務類,用于封裝對商品的操作;GoodsController:商品控制器類,用于對外提供 HTTP 接口;CorsConfig:跨域配置類,允許前端頁面跨域訪問后端接口。此時項目目錄如下:
首先,我們準備環(huán)境,首先是新建一個目錄 third(全路徑為/root/test/third),再該目錄下新建一個文件 3.txt, 里面只有一行內容 ‘hello, world’。接下來,我們準備一個 server 塊,加到 Http 指令塊中:server { server_name rewrite.test.com; listen 8009; # 打開rewrite日志,可以看到對應的rewrite結果 rewrite_log on; error_log logs/rewrite_error.log notice; root /root/test/; location /first { rewrite /first/(.*) /second/$1 last; return 200 'first!'; } location /second { rewrite /second/(.*) /third/$1 break; # rewrite /second/(.*) /third/$1; return 200 'second!'; } location /third { return 200 'third!'; }}上述配置中,要打開 rewrite_log指令,這樣我們可以看到 rewrite 指令的相應日志,方便查看結果。當我們在 /second 配置中,使用 break 時,請求命令:$ curl http://主機ip:8009/first/3.txt hello, world如果是不使用 break 標識,則請求結果如下:$ curl http://主機ip:8009/first/3.txt second!首先是 /first/3.txt 請求在 /first 中匹配,并被替換為 /second/3.txt, last 標識表示將繼續(xù)進行匹配,在 /second 中,uri 又被 rewrite 成 /third/3.txt, 如果后面跟了 break 標識,表示 rewrite 到此結束,不會執(zhí)行后面的 return 指令,直接請求靜態(tài)資源 /third/3.txt,得到其內容’hello, world’;如果是沒有 break 標識,則會在執(zhí)行 return 指令后直接返回,并不會繼續(xù)執(zhí)行下去,最后返回’second!'字符串。
Nginx 有內置變量和自定義變量兩種。內置變量往往代表著客戶端請求頭的內容,如 $http_user_agent , ? $http_cookie 等。Nginx 支持的常用內置變量有:變量名內容$arg_name請求中的參數名,比如請求http://localhost?a=x&b=y, 則 $arg_a 表示的就是字符串’x’$args請求中的參數值$binary_remote_addr客戶端地址的二進制形式, 固定長度為4個字節(jié)$body_bytes_sent傳輸給客戶端的字節(jié)數,響應頭不計算在內$bytes_sent傳輸給客戶端的字節(jié)數$content_length“Content-Length” 請求頭字段$remote_addr客戶端地址$remote_user用于 HTTP 基礎認證服務的用戶名$request_body客戶端的請求主體$request_length請求的長度 (包括請求的地址, http請求頭和請求主體)$request_methodHTTP 請求方法$request_time處理客戶端請求使用的時間,從讀取客戶端的第一個字節(jié)開始計時$request_uri這個變量等于包含一些客戶端請求參數的原始 URI ,它不包含主機名$server_addr服務器端地址, 注意:為了避免訪問 linux 系統(tǒng)內核,應將ip地址提前設置在配置文件中$statusHTTP 響應代碼$time_local服務器時間$uri請求中的當前 URI, 不帶請求參數,且不包含主機名
我們在啟動項目, Spring Boot Web 項目默認啟動端口為 8080 ,所以直接訪問 http://127.0.0.1:8080 ,顯示如下:Spring Boot 默認異常信息提示頁面如上圖所示,Spring Boot 默認的異常處理機制生效,當出現異常時會自動轉向 /error 路徑。
請求方法有 POST GET 這類,客戶端訪問的方法跟服務端能夠提供的不一樣,當請求狀態(tài)是 405 的時候,響應信息頭會帶上 Allow 字段,告訴客戶端被允許的請求方法是哪些。HTTP/1.1 405 Method Not AllowedContent-Type: text/htmlAllow: GET, HEAD, OPTIONS, PUT
面試官提問: 當你在瀏覽器中輸入了一個網址URL,例如http://idcbgp.cn并且按下回車到頁面展示內容的這個過程,發(fā)生了什么?可以從瀏覽器、服務器、計算機網絡相關嘗試分析。
FBV 全稱是 function base views, 即在視圖中是使用函數來對應處理請求。如下示例:# 在django中會對跨域請求做攔截處理,這里使用@csrf_exempt注解表示不作處理,主要是針對POST請求@csrf_exemptdef hello_world(request, *args, **kwargs): if request.method == 'GET': return HttpResponse('Hello, get request', content_type="text/plain") elif request.method == 'POST': return HttpResponse('Hello, post request', content_type="text/plain") return HttpResponse("Hello, world.", content_type="text/plain")urlpatterns = [ path('admin/', admin.site.urls), path('hello/', hello_world),]注意: 視圖函數可以接受 request 參數,用于存放瀏覽器傳遞過來的所有數據。它是 WSGIRequest 類的一個實例,而 WSGIRequest 類又繼承自 HttpRequest 。我們通過 request 這個實例可以拿到客戶端 http 請求的方法,請求路徑、傳遞的參數以及上傳的文件等等。CBV 全稱是 class base views 就是在視圖中使用類處理請求。在 Django 中加入了 CBV 的模式,讓我們用類去處理響應,這樣做有以下幾個好處:可以使用面向對象的技術,比如 Mixin(多繼承),這樣可以有效復用增刪改查數據庫的代碼;在視圖類中我們定義如 get 方法就能處理對應的 GET 請求,這樣無需使用 if 再進行判斷,一方面提高了代碼的可讀性,另一方面也規(guī)范了 web api 的接口設計。示例代碼:from django.http import HttpResponsefrom django.views import Viewclass HelloView(View): def get(self, request, *args, **kwargs): return HttpResponse('get\n') def post(self, request, *args, **kwargs): return HttpResponse('post\n') def put(self, request, *args, **kwargs): return HttpResponse('put\n') def delete(self, request, *args, **kwargs): return HttpResponse('delete\n') # 注意,給CBV加上@csrf_exempt注解,需要加到dispatch方法上,后續(xù)會詳解介紹這個函數的作用 @csrf_exempt def dispatch(self, request, *args, **kwargs): return super(HelloView, self).dispatch(request, *args, **kwargs) urlpatterns = [ path('admin/', admin.site.urls), path('hello/', HelloView.as_view()),]將包含這個視圖層的 django demo 工程啟動后,請求 /hello/ 路徑:[root@server ~]# curl http://127.0.0.1:8881/hello/get[root@server ~]# curl -XPOST http://127.0.0.1:8881/hello/post[root@server ~]# curl -XPUT http://127.0.0.1:8881/hello/put[root@server ~]# curl -XDELETE http://127.0.0.1:8881/hello/delete可以看到,這樣封裝的視圖類對應的 web api 接口具有良好的 restful 風格,而且代碼可讀性也非常好。 后面我們會深入學習這種視圖模式以及 Django 內部封裝的各種 view 類。
有多個重定向的值,需要客戶端自己選擇, Location 的值是服務端建議的值。HTTP/1.1 300 Multiple ChoicesAccess-Control-Allow-Headers: Content-Type,User-AgentAccess-Control-Allow-Origin: *Link: </foo> rel="alternate"Link: </bar> rel="alternate"Content-Type: text/htmlLocation: /foo
默認情況下,Spring Security 為了防止 XSS 攻擊增加了 X-XSS-Protection 頭,我們可以通過以下方式對其配置進行修改:@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.headers(headers -> headers.xssProtection(xss -> xss.block(false))); }}
減少網頁的 http 請求。 利用雪碧圖能夠很好地減少網頁的 http 請求,從而大大的提高頁面的性能,這也是其最大的優(yōu)點,也是其被廣泛傳播和應用的主要原因。減少圖片的大小。 雪碧圖能夠減少圖片的大小,3 張圖片合并成 1 張圖片的大小比這 3 張圖片加起來的大小還要小。簡化圖片命名。 解決了在圖片命名上的困擾,只需對一張雪碧圖命名就可以了,不需要對每一個小元素進行命名,從而減少了掉頭發(fā)的次數。方便更換主題。 只需要在一張或少張圖片上修改圖片的顏色或樣式,整個網頁的風格就可以改變。維護起來更加方便。
realip 模塊是在 postread 階段生效的,它的作用是:當本機的 nginx 處于一個反向代理的后端時獲取到真實的用戶 ip。 如果沒有 realip 模塊,Nginx 中的 $remote_addr 可能就不是客戶端的真實 ip 了,而是代理主機的 ip。realip模塊的配置實例如下: set_real_ip_from 10.10.10.10; # real_ip_recursive off; real_ip_recursive on; real_ip_header X-Forwarded-For;set_real_ip_from 是指定我們信任的后端代理服務器,real_ip_header 是告訴 nginx 真正的用戶 ip 是存在 X-Forwarded-For 請求頭中的。當 real_ip_recursive 設置為 off 時,nginx 會把 real_ip_header 指定的 Http頭中的最后一個 ip 當成真實 ip;而當 real_ip_recursive 為 on 時,nginx 會把 real_ip_header 指定的 Http頭中的最后一個不是信任服務器的 ip (前面設置的set_real_ip_from)當成真實 ip。通過這樣的手段,最后拿到用戶的真實 ip。
Cookie 是一個請求首部,其中含有先前由服務器通過 Set-Cookie 首部投放并存儲到客戶端的 HTTP cookies。cookie 可以作為單獨知識了解,這里其實介紹的是 document.cookie 。
默認情況下,Spring Security 通過 X-Frame-Options 頭方式禁止 iframe 層渲染,我們可以通過如下方式修改其配置:@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.headers(headers -> headers.frameOptions(frameOptions -> frameOptions.sameOrigin())); }}
運行啟動類,然后訪問 http://127.0.0.1:8080/getRankList ,結果如下:[{"id":1,"name":"雞蛋Thu May 28 22:47:33 CST 2020","price":null,"pic":null}]稍等會再次訪問,結果如下:[{"id":1,"name":"雞蛋Thu May 28 22:48:09 CST 2020","price":null,"pic":null}]說明我們設計的緩存機制生效了。
發(fā)布 Gem 要使用這一行命令:gem build my_gem_duxiao.gemspec命令行下顯示內容:WARNING: licenses is empty, but is recommended. Use a license abbreviation from:http://opensource.org/licenses/alphabeticalWARNING: See http://guides.rubygems.org/specification-reference/ for help Successfully built RubyGem Name: my_gem_duxiao Version: 0.1.0 File: my_gem_duxiao-0.1.0.gem然后我們會看到生成了 my_gem_duxiao-0.1.0.gem。這個就是我們想要的Gem。
核心邏輯是在 HttpTask 里面,MainActivity 里面主要是設置觸發(fā)按鈕監(jiān)聽器,然后在點擊事件中啟動 HttpTask 即可:package com.emercy.myapplication;import android.app.Activity;import android.os.Bundle;import android.view.View;public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.start_http).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new HttpTask().execute(); } }); }}這里要記得,雖然在 HttpTask 中我們的代碼是運行在doInBackground()方法中,但是啟動 AsyncTask 的方法是調用eexecute()。編譯運行界面如下:點擊“發(fā)起 HTTP 請求”啟動 AsyncTask 啟動 http 連接,然后輸入接收到的數據到 Logcat,過濾“HttpTask”字段,觀察日志:以上是日志的片段,可以看到 Logcat 中的內容就是http://idcbgp.cn/wiki/androidlesson頁面的源代碼,到此就獲取成功了。
其實 Netty 開發(fā) Http 協議在我們的開發(fā)當中其實并不常用,其主要的的應用場景是開發(fā)類型 Tomcat 這種類型的 Web 容器,有了成熟的 Tomcat、Jboss、WebLogic,不需要我們去重新造一遍輪子,但是為什么還需要去學習它呢?學習本節(jié)主要有兩個目的:有助于以后學習 Tomcat 的原理,Tomcat 的通訊部分是基于 Netty 去實現的;有助于理解整個 Java 體系的通訊架構原理,很多我們平時使用最多、接觸最多、熟練使用的技術,但是我們往往不懂得其底層原理是什么,Tomcat 和 Http 就是其中被廣泛熟知,但是很少同學有興趣去了解其原理的。
在測試機器的 /root/test/web 目錄下有 2 個 html 文件,分別為 web.html 和 web2.html, 沒有 web3.html。我們編寫如下的 server 塊,監(jiān)聽 8013 端口。首先訪問 http://主機ip:8013/web 時,根據配置情況,Nginx 首先查找是否存在 /root/test/web/web3.html 文件,沒有找到會繼續(xù)向下,找$uri,也就是/root/test/web 文件,不存在。繼續(xù)找 KaTeX parse error: Expected 'EOF', got ',' at position 15: uri/index.html,?即/root/test/web…uri/web1.html時文件存在,故返回/root/test/web/web1.html文件內容。如果該文件還不存在,則還會繼續(xù)批評額哦@lasturi,最后返回’lasturi!'這樣的字符串。而在訪問 http://主機ip:8013/return_code 時,由于無法匹配靜態(tài)資源,根據配置最后返回404錯誤碼,出現 Nginx 默認的 404 錯誤頁面。server { server_name try_files.com; listen 8013; root /root/test/; default_type text/plain; location /web { # 找/root/test/index.html # try_files /index.html try_files /web/web3.html $uri $uri/index.html $uri/web1.html @lasturi; #最后匹配這個 } location @lasturi { eturn 200 'lasturi!\n'; } location /return_code { try_files $uri $uri/index.html $uri.html =404; }}
首先我們來編寫布局文件,核心內容很簡單,主要有四個元素:輸入框: 接收需要保存的數據數據文本: 展示從文件中讀取的數據保存/加載: 點擊觸發(fā)數據的輸入和輸入布局代碼如下:<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="30dp" tools:context=".MainActivity"> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:text="文件存儲" android:textSize="35sp" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/title" android:layout_centerHorizontal="true" android:text="慕課Android教程" android:textColor="#ff7aff24" android:textSize="35sp" /> <Button android:id="@+id/save" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignStart="@+id/textView" android:layout_alignParentBottom="true" android:text="保存" /> <EditText android:id="@+id/editText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/imageView" android:layout_centerHorizontal="true" android:layout_marginTop="42dp" android:hint="輸入要保存的內容" android:textColorHighlight="#ff7eff15" android:textColorHint="#ffff25e6" /> <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/textView" android:layout_centerHorizontal="true" android:src="@mipmap/ic_launcher" /> <Button android:id="@+id/load" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignTop="@+id/save" android:layout_alignEnd="@+id/editText" android:text="加載文件" /> <TextView android:id="@+id/content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/editText" android:layout_centerHorizontal="true" android:textColor="#ff5bff1f" android:textSize="25sp" /></RelativeLayout>