首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >[Xamarin][转载]基于Xamarin.Android实现的简单的浏览器

[Xamarin][转载]基于Xamarin.Android实现的简单的浏览器

作者头像
云未归来
发布2025-07-18 14:28:30
发布2025-07-18 14:28:30
890
举报

基于Xamarin Android实现的简单的浏览器

  最近做了一个Android浏览器,当然功能比较简单,主要实现了自己想要的一些功能……现在有好多浏览器为什么还要自己写?当你使用的时候总有那么一些地方不如意,于是就想自己写一个。

  开发环境:Xamarin Android(非Forms)+联想机子(5.0)+荣耀机子(8.0)

  【开发目标】

  1、浏览器的基本功能,关联Http和Https(在另一个APP中打开网页时,可以弹出本应用)

  2、创建应用目录,用来存放离线网页文件

  3、可以离线保存网页(格式为mht)

  4、关联mht和mhtml格式的文件

  【涉及到的技术点】

  1、重写Activity中的OnBackPressed方法,实现webview回退和再按一次退出程序的功能

  2、重写Activity中的OnConfigurationChanged方法,实现横竖屏功能

  【webview相关技术点】

  1、开启一些常用的设置:JavaScriptEnabled、DomStorageEnabled(如果DomStorageEnabled不启用,网页中的下拉刷新和加载更多将不起作用;例子:百度首页加载新闻)

  2、重写WebViewClient中的ShouldOverrideUrlLoading方法,在点击打开网页中的链接时,用自己的webview中打开连接,而不是打开其他的浏览器

  3、重写WebChromeClient中的OnReceivedTitle和OnProgressChanged方法,分别获取页面标题(作为离线文件的名称)和加载进度

  4、采用事件的方式,通知主Activity关于页面加载开始、加载结束、标题、加载进度等的一些事情,进而更新UI(这里和Java的写法有些不同)

  5、页面加载进度条

  【悬浮按钮】1、全屏(退出)按钮  2、保存网页  3、扫描二维码(版本兼容问题尚未实现)

  【网址输入框】

  1、输入正确的网址之后点击输入法中的“前往”调转

  2、隐藏输入法

  以上列到的功能基本实现,最后在荣耀V10上测试时,其他的功能还好,就是在打开离线文件时也不报错,就是打不开……郁闷啊!最后查了一下也没有找到原因。这里说一下场景,以方便大神发现问题,希望大神不吝赐教。在我的联想手机上测试时发现本地文件路径是这样的:file:///storage/emulated/0/DDZMyBrowser/SavePages/1.mht  此时可以正常浏览,而V10中得到的路径是这样的,内部存储:content://com.huawei.hidisk.fileprovider/root/storage/emulated/0/DDZMyBrowser/SavePages/1.mht   SD卡:content://com.huawei.hidisk.fileprovider/root/storage/0ABF-6213/1.mht  这两个都打不开。我查询的结果,这路径应该是利用FileProvider生成的(7.0以上),哎,并非真正的android开发,并不太懂,一脸懵逼,不知道是不是因为这个原因……开始我还寄希望于将content://转为file:///格式的,但是都失败了,最后想想网上说的webview支持content://开头的啊,自己在输入框中手动修改为:file:///storage/emulated/0/DDZMyBrowser/SavePages/1.mht  发现是可以征程浏览的……

  上一下截图:

  1、应用首页

  2、再按一次退出程序

  3、横屏

  4、竖屏

  5、网页中的下拉刷新

  6、加载更多

  7、用自己的webview中打开连接,而不是打开其他的浏览器;进度条

  8、全屏

  9、离线保存

  10、关联MHT

  11、关联HTTP和HTTPS

  12、actionGo

  13、最后再来一张V10加载异常的图片

  去去去,传上去之后发现图片太大了,全是百度的图片……这事儿弄得

  最后在贴一下代码,记录一下

  CS代码:

复制代码

1 using Android.App;

2 using Android.Widget;

3 using Android.OS;

4 using Android.Webkit;

5 using System;

6 using Android.Support.Design.Widget;

7 using Android.Content;

8 using Android.Views;

9 using Java.IO;

10 using Android.Views.InputMethods;

11 using Android.Content.PM;

12 using Android.Content.Res;

13 using Android.Provider;

14 using Android.Database;

15

16 namespace DDZ.MyBrowser

17 {

18 /// <summary>

19 /// 获取网页Title

20 /// </summary>

21 /// <param name="title"></param>

22 public delegate void MyWebViewTitleDelegate(string title);

23

24 /// <summary>

25 /// 获取网页加载进度

26 /// </summary>

27 public delegate void MyWebViewProgressChangedDelegate(int newProgress);

28

29 /// <summary>

30 /// 网页加载完成事件

31 /// </summary>

32 public delegate void MyWebViewPageFinishedDelegate();

33

34 [IntentFilter(

35 new[] { Intent.ActionView },

36 Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },

37 DataSchemes = new[] { "http", "https" })]

38 [IntentFilter(

39 new[] { Intent.ActionView },

40 Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },

41 DataSchemes = new[] { "file", "content" }, DataMimeType = "*/*", DataHost = "*", DataPathPattern = ".*\\\\.mhtml")]

42 [IntentFilter(

43 new[] { Intent.ActionView },

44 Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },

45 DataMimeType = "*/*", DataSchemes = new[] { "file", "content" }, DataHost = "*", DataPathPattern = ".*\\\\.mht")]

46 [Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true,

47 ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.KeyboardHidden)]

48 public class MainActivity : Activity

49 {

50 WebView myBrowser;

51 EditText edtTxtUrl;

52 FloatingActionButton fabMain;

53 FloatingActionButton fabSubQRcodeScan;

54 FloatingActionButton fabSubToggleFullScreen;

55 FloatingActionButton fabSubSaveMHT;

56 ProgressBar myBrowserPBar;

57

58 private static bool isFabOpen;

59 private static bool isFullScreen;

60 private static DateTime lastClickGoBack = DateTime.Now;

61

62 private static string currentPageTitle;

63 private readonly string externalStorageDirPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath;

64 private readonly string selfFolderName = "DDZMyBrowser";

65 private static string selfApplicationDirPath;

66 protected override void OnCreate(Bundle savedInstanceState)

67 {

68 // https://blog.csdn.net/niunan/article/details/71774292

69 base.OnCreate(savedInstanceState);

70 // Set our view from the "main" layout resource

71 SetContentView(Resource.Layout.activity_main);

72

73 // 1、浏览器控件相关

74 myBrowser = FindViewById<WebView>(Resource.Id.myBrowser);

75 // 要与Javascript交互,则webview必须设置支持Javascript

76 myBrowser.Settings.JavaScriptEnabled = true;

77 // 支持通过JS打开新窗口

78 myBrowser.Settings.JavaScriptCanOpenWindowsAutomatically = true;

79 myBrowser.Settings.DomStorageEnabled = true;

80 myBrowser.Settings.AllowFileAccessFromFileURLs = true;

81

82 var myWebViewClient = new MyWebViewClient();

83 myWebViewClient.GetWebViewPageFinishedDelegate += MyWebViewClient_GetWebViewPageFinishedDelegate;

84 myBrowser.SetWebViewClient(myWebViewClient);

85 var myWebChromeClient = new MyWebChromeClient();

86 myWebChromeClient.GetWebViewTitleDelegate += MyWebChromeClient_GetWebViewTitleDelegate;

87 myWebChromeClient.GetWebViewProgressChangedDelegate += MyWebChromeClient_GetWebViewProgressChangedDelegate;

88 myBrowser.SetWebChromeClient(myWebChromeClient);

89 edtTxtUrl = FindViewById<EditText>(Resource.Id.edtTxtUrl);

90 edtTxtUrl.EditorAction += EdtTxtUrl_EditorAction;

91 myBrowserPBar = FindViewById<ProgressBar>(Resource.Id.myBrowserPBar);

92

93 // 2、右下方悬浮控件

94 fabMain = FindViewById<FloatingActionButton>(Resource.Id.fabMain);

95 fabSubQRcodeScan = FindViewById<FloatingActionButton>(Resource.Id.fabSubQRcodeScan);

96 fabSubToggleFullScreen = FindViewById<FloatingActionButton>(Resource.Id.fabSubToggleFullScreen);

97 fabSubSaveMHT = FindViewById<FloatingActionButton>(Resource.Id.fabSubSaveMHT);

98 fabMain.Click += FabMain_Click;

99 fabSubQRcodeScan.Click += FabSubQRcodeScan_Click;

100 fabSubToggleFullScreen.Click += FabSubToggleFullScreen_Click; ;

101 fabSubSaveMHT.Click += FabSubSaveMHT_Click;

102

103 // 3、第三方应用使用该应用打开网页时,"this.Intent.DataString" 获取需要打开的网址

104 // 自己打开时,"this.Intent.DataString" 的值为空

105 String url = this.Intent.DataString;

106 if (!String.IsNullOrEmpty(url))

107 {

108 if (this.Intent.Data.Scheme == "content")

109 {

110 // DocumentsContract.IsDocumentUri(this, this.Intent.Data):false

111

112 // this.Intent.Data.Authority:com.huawei.hidisk.fileprovider

113 // this.Intent.Data.Host:com.huawei.hidisk.fileprovider

114 // this.Intent.Data.Path:/root/storage/0ABF-6213/xxx.mht

115 // this.Intent.Data.PathSegments:this.Intent.Data.Path的数组形式

116

117 // Android.Support.V4.Content.FileProvider.GetUriForFile()

118 // this.Intent.SetFlags(ActivityFlags.GrantReadUriPermission).SetFlags(ActivityFlags.GrantWriteUriPermission);

119 }

120 }

121 edtTxtUrl.Text = url;

122 LoadOnePage(url);

123

124 // 4、创建应用目录

125 CreateSelfApplicationFolder();

126 }

127

128 private void EdtTxtUrl_EditorAction(object sender, TextView.EditorActionEventArgs e)

129 {

130 string inputUrl = edtTxtUrl.Text.Trim();

131 if (e.ActionId == ImeAction.Go)

132 {

133 HideSoftInputFn();

134 LoadOnePage(inputUrl);

135 }

136 }

137

138 #region 获取WebView加载页面相关信息的一些自定义事件

139 private void MyWebViewClient_GetWebViewPageFinishedDelegate()

140 {

141 Toast.MakeText(this, "加载完成", ToastLength.Long).Show();

142 }

143

144 private void MyWebChromeClient_GetWebViewProgressChangedDelegate(int newProgress)

145 {

146 myBrowserPBar.Visibility = ViewStates.Visible;

147 myBrowserPBar.Progress = newProgress;

148 if (newProgress == 100)

149 {

150 myBrowserPBar.Visibility = ViewStates.Gone;

151 }

152 }

153

154 private void MyWebChromeClient_GetWebViewTitleDelegate(string title)

155 {

156 currentPageTitle = title;

157 }

158 #endregion

159

160 #region 悬浮按钮

161 private void FabMain_Click(object sender, EventArgs e)

162 {

163 if (!isFabOpen)

164 {

165 HideSoftInputFn();

166 ShowFabMenu();

167 }

168 else

169 {

170 CloseFabMenu();

171 }

172 SetToggleFullScreenBtnImg();

173 }

174

175 private void FabSubQRcodeScan_Click(object sender, EventArgs e)

176 {

177 Toast.MakeText(this, "扫描二维码", ToastLength.Long).Show();

178 }

179

180 private void FabSubSaveMHT_Click(object sender, EventArgs e)

181 {

182 string savePageDirPath = $"{selfApplicationDirPath}{File.Separator}SavePages";

183 File dir = new File(savePageDirPath);

184 if (!dir.Exists())

185 {

186 bool retBool = dir.Mkdir();

187 }

188 myBrowser.SaveWebArchive($"{savePageDirPath}{File.Separator}{currentPageTitle}.mht");

189 }

190

191 private void FabSubToggleFullScreen_Click(object sender, EventArgs e)

192 {

193 if (isFullScreen)

194 { // 目前为全屏状态,修改为非全屏

195 edtTxtUrl.Visibility = ViewStates.Visible;

196 this.Window.ClearFlags(WindowManagerFlags.Fullscreen);

197 }

198 else

199 { // 目前为非全屏状态,修改为全屏

200 edtTxtUrl.Visibility = ViewStates.Gone;

201 this.Window.SetFlags(WindowManagerFlags.Fullscreen, WindowManagerFlags.Fullscreen);

202 }

203 isFullScreen = !isFullScreen;

204 SetToggleFullScreenBtnImg();

205 }

206

207 private void ShowFabMenu()

208 {

209 isFabOpen = true;

210 fabSubQRcodeScan.Visibility = ViewStates.Visible;

211 fabSubToggleFullScreen.Visibility = ViewStates.Visible;

212 fabSubSaveMHT.Visibility = ViewStates.Visible;

213

214 fabMain.Animate().Rotation(135f);

215 fabSubQRcodeScan.Animate()

216 .TranslationY(-600f)

217 .Rotation(0f);

218 fabSubToggleFullScreen.Animate()

219 .TranslationY(-410f)

220 .Rotation(0f);

221 fabSubSaveMHT.Animate()

222 .TranslationY(-220f)

223 .Rotation(0f);

224 }

225

226 private void CloseFabMenu()

227 {

228 isFabOpen = false;

229

230 fabMain.Animate().Rotation(0f);

231 fabSubQRcodeScan.Animate()

232 .TranslationY(0f)

233 .Rotation(90f);

234 fabSubToggleFullScreen.Animate()

235 .TranslationY(0f)

236 .Rotation(90f);

237 fabSubSaveMHT.Animate()

238 .TranslationY(0f)

239 .Rotation(90f);

240

241 fabSubQRcodeScan.Visibility = ViewStates.Gone;

242 fabSubToggleFullScreen.Visibility = ViewStates.Gone;

243 fabSubSaveMHT.Visibility = ViewStates.Gone;

244 }

245

246 private void SetToggleFullScreenBtnImg()

247 {

248 if (isFullScreen)

249 {

250 fabSubToggleFullScreen.SetImageResource(Resource.Drawable.fullscreenExit);

251 }

252 else

253 {

254 fabSubToggleFullScreen.SetImageResource(Resource.Drawable.fullscreen);

255 }

256 }

257 #endregion

258

259 #region 重写基类 Activity 方法

260 public override void OnConfigurationChanged(Configuration newConfig)

261 {

262 base.OnConfigurationChanged(newConfig);

263 if (newConfig.Orientation == Android.Content.Res.Orientation.Portrait)

264 {

265 edtTxtUrl.Visibility = ViewStates.Visible;

266 fabMain.Visibility = ViewStates.Visible;

267 this.Window.ClearFlags(WindowManagerFlags.Fullscreen);

268 isFullScreen = false;

269 Toast.MakeText(Application.Context, "竖屏模式!", ToastLength.Long).Show();

270 }

271 else

272 {

273 CloseFabMenu();

274 edtTxtUrl.Visibility = ViewStates.Gone;

275 fabMain.Visibility = ViewStates.Gone;

276 this.Window.SetFlags(WindowManagerFlags.Fullscreen, WindowManagerFlags.Fullscreen);

277 isFullScreen = true;

278 Toast.MakeText(Application.Context, "横屏模式!", ToastLength.Long).Show();

279 }

280 }

281 public override void OnBackPressed()

282 {

283 if (myBrowser.CanGoBack())

284 {

285 myBrowser.GoBack();

286 }

287 else

288 {

289 if ((DateTime.Now - lastClickGoBack).Seconds > 2)

290 {

291 Toast.MakeText(this, $"再按一次退出程序", ToastLength.Long).Show();

292 lastClickGoBack = DateTime.Now;

293 }

294 else

295 {

296 this.Finish();

297 }

298 }

299 }

300 #endregion

301

302 private void LoadOnePage(String url = "")

303 {

304 currentPageTitle = null;

305 if (String.IsNullOrEmpty(url)) url = "https://www.baidu.com/";

306 myBrowser.LoadUrl(url);

307 }

308

309 private void HideSoftInputFn()

310 {

311 // 隐藏键盘

312 InputMethodManager imm = (InputMethodManager)this.GetSystemService(Context.InputMethodService);

313 imm.HideSoftInputFromWindow(edtTxtUrl.WindowToken, 0);

314 }

315

316 private void CreateSelfApplicationFolder()

317 {

318 selfApplicationDirPath = $"{externalStorageDirPath}{File.Separator}{selfFolderName}";

319 File dir = new File(selfApplicationDirPath);

320 if (!dir.Exists())

321 {

322 bool retBool = dir.Mkdir();

323 }

324 }

325

326 private String GetRealPathFromURI(Context context, Android.Net.Uri uri)

327 {

328 String retPath = null;

329 if (context == null || uri == null) return retPath;

330 Boolean isKitKat = Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat;

331 if (isKitKat && DocumentsContract.IsDocumentUri(context, uri))

332 {

333 if (uri.Authority.Equals("com.android.externalstorage.documents", StringComparison.OrdinalIgnoreCase))

334 {

335 String docId = DocumentsContract.GetDocumentId(uri);

336 String[] split = docId.Split(':');

337 if (split[0].Equals("primary", StringComparison.OrdinalIgnoreCase))

338 {

339 retPath = Android.OS.Environment.ExternalStorageDirectory + "/" + split[1];

340 }

341 }

342 }

343 return retPath;

344 }

345

346 private String GetFilePathFromContentUri(Context context,Android.Net.Uri url)

347 {

348 String filePath = null;

349 String[] filePathColumn = { MediaStore.MediaColumns.Data };

350 using (ICursor cursor = context.ContentResolver.Query(url, filePathColumn, null, null, null))

351 {

352 if (cursor != null && cursor.MoveToFirst())

353 {

354 int columnIndex = cursor.GetColumnIndexOrThrow(filePathColumn[0]);

355 if (columnIndex > -1)

356 {

357 filePath = cursor.GetString(columnIndex);

358 }

359 }

360 }

361 return filePath;

362 }

363 }

364

365 public class MyWebViewClient : WebViewClient

366 {

367 public event MyWebViewPageFinishedDelegate GetWebViewPageFinishedDelegate;

368 public override bool ShouldOverrideUrlLoading(WebView view, IWebResourceRequest request)

369 {

370 view.LoadUrl(request.Url.ToString());

371 return true;

372 }

373

374 public override void OnPageFinished(WebView view, string url)

375 {

376 base.OnPageFinished(view, url);

377 GetWebViewPageFinishedDelegate();

378 }

379 }

380

381 public class MyWebChromeClient : WebChromeClient

382 {

383 public event MyWebViewTitleDelegate GetWebViewTitleDelegate;//声明一个事件

384

385 public event MyWebViewProgressChangedDelegate GetWebViewProgressChangedDelegate;

386 public override void OnReceivedTitle(WebView view, string title)

387 {

388 base.OnReceivedTitle(view, title);

389 GetWebViewTitleDelegate(title);

390 }

391

392 public override void OnProgressChanged(WebView view, int newProgress)

393 {

394 base.OnProgressChanged(view, newProgress);

395 GetWebViewProgressChangedDelegate(newProgress);

396 }

397 }

398 }

复制代码

  布局代码:

复制代码

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

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent">

<LinearLayout

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent">

<ProgressBar

style="?android:attr/progressBarStyleHorizontal"

android:layout_width="match_parent"

android:layout_height="3dip"

android:max="100"

android:progress="0"

android:visibility="gone"

android:id="@+id/myBrowserPBar" />

<android.webkit.WebView

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_weight="1"

android:id="@+id/myBrowser" />

<LinearLayout

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="horizontal">

<EditText

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:id="@+id/edtTxtUrl"

android:inputType="text"

android:singleLine="true"

android:imeOptions="actionGo"

android:hint="请输入网址" />

</LinearLayout>

</LinearLayout>

<android.support.design.widget.FloatingActionButton

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentBottom="true"

android:layout_alignParentRight="true"

android:layout_margin="18dp"

android:visibility="gone"

android:rotation="90"

android:src="@drawable/qrcodeScan"

app:fabSize="mini"

android:id="@+id/fabSubQRcodeScan" />

<android.support.design.widget.FloatingActionButton

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentBottom="true"

android:layout_alignParentRight="true"

android:layout_margin="18dp"

android:visibility="gone"

android:rotation="90"

android:src="@drawable/saveMht"

app:fabSize="mini"

android:id="@+id/fabSubSaveMHT" />

<android.support.design.widget.FloatingActionButton

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentBottom="true"

android:layout_alignParentRight="true"

android:layout_margin="18dp"

android:visibility="gone"

android:rotation="90"

app:fabSize="mini"

android:id="@+id/fabSubToggleFullScreen" />

<android.support.design.widget.FloatingActionButton

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentBottom="true"

android:layout_alignParentRight="true"

android:layout_margin="10dp"

android:src="@drawable/plus"

app:fabSize="normal"

android:id="@+id/fabMain" />

</RelativeLayout>

复制代码

  用到的一些权限

<uses-permission android:name="android.permission.INTERNET" />

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

<uses-permission android:name="android.permission.BIND_INPUT_METHOD" />

  到此就结束了,webview在8.0下打开content://文件失败的问题,如果有大神看到,希望帮忙解答,十分感谢!以后有机会在慢慢完善他的功能。

  【2018-06-26更新】

  昨天在小米note3测试本应用,本来是想测试一下看看能不能打开离线文件的问题,但是连关联MHT都不行,打开的时候,应用列表没有该应用……郁闷了,越淌水越深啊!

  【2018-11-08更新】

  开始因为版本兼容的问题,扫描二维码未处理,一直想着过后看看官方能不能解决,但是今天看看,还是不行……但是VS给出了解决方案:

  VS会依次提示单独安装上述的几个包,开始这个项目只安装了一个包,现在的依赖如下:

  这次添加的相机权限

<uses-permission android:name="android.permission.CAMERA" />

  相机相关代码

  1、初始化

复制代码

protected override void OnCreate(Bundle savedInstanceState)

{

………………

………………

// 5、初始化

MobileBarcodeScanner.Initialize(Application);

}

复制代码

  2、点击扫描二维码执行的代码

复制代码

private void FabSubQRcodeScan_Click(object sender, EventArgs e)

{

//Toast.MakeText(this, "扫描二维码", ToastLength.Long).Show();

Task.Run(() =>

{

var scanner = new MobileBarcodeScanner();

var result = scanner.Scan();

if (result != null)

{

string scanResult = result.Result.Text;

this.RunOnUiThread(new Java.Lang.Runnable(() =>

{

edtTxtUrl.Text = scanResult;

LoadOnePage(scanResult);

}));

}

});

}

复制代码

  经过这次更新,就可以扫描二维码了……

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-07-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档